mirror of
https://github.com/vcmi/vcmi.git
synced 2025-01-14 02:33:51 +02:00
Merge pull request #470 from nullkiller/ai-pathfinder-extensibility
AI: add some pathfinder extensibility and replace SectorMap.
This commit is contained in:
commit
f9054c06b3
@ -1 +1 @@
|
|||||||
Subproject commit 42c30e157e999b45aee9b53bb849bb6ec3a2be22
|
Subproject commit 9751a751a17c0682ed5d02e583c6a0cda8bc88e5
|
@ -36,6 +36,11 @@ ObjectIdRef::operator const CGObjectInstance *() const
|
|||||||
return cb->getObj(id, false);
|
return cb->getObj(id, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ObjectIdRef::operator bool() const
|
||||||
|
{
|
||||||
|
return cb->getObj(id, false);
|
||||||
|
}
|
||||||
|
|
||||||
ObjectIdRef::ObjectIdRef(ObjectInstanceID _id)
|
ObjectIdRef::ObjectIdRef(ObjectInstanceID _id)
|
||||||
: id(_id)
|
: id(_id)
|
||||||
{
|
{
|
||||||
@ -338,14 +343,18 @@ bool compareDanger(const CGObjectInstance * lhs, const CGObjectInstance * rhs)
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool isSafeToVisit(HeroPtr h, crint3 tile)
|
bool isSafeToVisit(HeroPtr h, crint3 tile)
|
||||||
|
{
|
||||||
|
return isSafeToVisit(h, evaluateDanger(tile));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isSafeToVisit(HeroPtr h, uint64_t dangerStrength)
|
||||||
{
|
{
|
||||||
const ui64 heroStrength = h->getTotalStrength();
|
const ui64 heroStrength = h->getTotalStrength();
|
||||||
const ui64 dangerStrength = evaluateDanger(tile, *h);
|
|
||||||
if(dangerStrength)
|
if(dangerStrength)
|
||||||
{
|
{
|
||||||
if(heroStrength / SAFE_ATTACK_CONSTANT > dangerStrength)
|
if(heroStrength / SAFE_ATTACK_CONSTANT > dangerStrength)
|
||||||
{
|
{
|
||||||
logAi->trace("It's safe for %s to visit tile %s", h->name, tile.toString());
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -398,11 +407,11 @@ int3 whereToExplore(HeroPtr h)
|
|||||||
TimeCheck tc("where to explore");
|
TimeCheck tc("where to explore");
|
||||||
int radius = h->getSightRadius();
|
int radius = h->getSightRadius();
|
||||||
int3 hpos = h->visitablePos();
|
int3 hpos = h->visitablePos();
|
||||||
|
|
||||||
auto sm = ai->getCachedSectorMap(h);
|
|
||||||
|
|
||||||
//look for nearby objs -> visit them if they're close enouh
|
//look for nearby objs -> visit them if they're close enouh
|
||||||
const int DIST_LIMIT = 3;
|
const int DIST_LIMIT = 3;
|
||||||
|
const int MP_LIMIT = DIST_LIMIT * 150; // aproximate cost of diagonal movement
|
||||||
|
|
||||||
std::vector<const CGObjectInstance *> nearbyVisitableObjs;
|
std::vector<const CGObjectInstance *> nearbyVisitableObjs;
|
||||||
for(int x = hpos.x - DIST_LIMIT; x <= hpos.x + DIST_LIMIT; ++x) //get only local objects instead of all possible objects on the map
|
for(int x = hpos.x - DIST_LIMIT; x <= hpos.x + DIST_LIMIT; ++x) //get only local objects instead of all possible objects on the map
|
||||||
{
|
{
|
||||||
@ -410,13 +419,9 @@ int3 whereToExplore(HeroPtr h)
|
|||||||
{
|
{
|
||||||
for(auto obj : cb->getVisitableObjs(int3(x, y, hpos.z), false))
|
for(auto obj : cb->getVisitableObjs(int3(x, y, hpos.z), false))
|
||||||
{
|
{
|
||||||
int3 op = obj->visitablePos();
|
if(ai->isGoodForVisit(obj, h, MP_LIMIT))
|
||||||
CGPath p;
|
|
||||||
ai->myCb->getPathsInfo(h.get())->getPath(p, op);
|
|
||||||
if(p.nodes.size() && p.endPos() == op && p.nodes.size() <= DIST_LIMIT)
|
|
||||||
{
|
{
|
||||||
if(ai->isGoodForVisit(obj, h, *sm))
|
nearbyVisitableObjs.push_back(obj);
|
||||||
nearbyVisitableObjs.push_back(obj);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -518,7 +523,7 @@ creInfo infoFromDC(const dwellingContent & dc)
|
|||||||
return ci;
|
return ci;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui64 howManyReinforcementsCanBuy(HeroPtr h, const CGTownInstance * t)
|
ui64 howManyReinforcementsCanBuy(HeroPtr h, const CGDwelling * t)
|
||||||
{
|
{
|
||||||
ui64 aivalue = 0;
|
ui64 aivalue = 0;
|
||||||
|
|
||||||
@ -597,3 +602,14 @@ bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2
|
|||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t distanceToTile(const CGHeroInstance * hero, int3 pos)
|
||||||
|
{
|
||||||
|
auto pathInfo = cb->getPathsInfo(hero)->getPathInfo(pos);
|
||||||
|
uint32_t totalMovementPoints = pathInfo->turns * hero->maxMovePoints(true) + hero->movement;
|
||||||
|
|
||||||
|
if(totalMovementPoints < pathInfo->moveRemains) // should not be but who knows
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return totalMovementPoints - pathInfo->moveRemains;
|
||||||
|
}
|
@ -59,6 +59,10 @@ public:
|
|||||||
const CGHeroInstance * operator->() const;
|
const CGHeroInstance * operator->() const;
|
||||||
const CGHeroInstance * operator*() const; //not that consistent with -> but all interfaces use CGHeroInstance*, so it's convenient
|
const CGHeroInstance * operator*() const; //not that consistent with -> but all interfaces use CGHeroInstance*, so it's convenient
|
||||||
bool operator==(const HeroPtr & rhs) const;
|
bool operator==(const HeroPtr & rhs) const;
|
||||||
|
bool operator!=(const HeroPtr & rhs) const
|
||||||
|
{
|
||||||
|
return !(*this == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
const CGHeroInstance * get(bool doWeExpectNull = false) const;
|
const CGHeroInstance * get(bool doWeExpectNull = false) const;
|
||||||
bool validAndSet() const;
|
bool validAndSet() const;
|
||||||
@ -88,6 +92,7 @@ struct ObjectIdRef
|
|||||||
|
|
||||||
const CGObjectInstance * operator->() const;
|
const CGObjectInstance * operator->() const;
|
||||||
operator const CGObjectInstance *() const;
|
operator const CGObjectInstance *() const;
|
||||||
|
operator bool() const;
|
||||||
|
|
||||||
ObjectIdRef(ObjectInstanceID _id);
|
ObjectIdRef(ObjectInstanceID _id);
|
||||||
ObjectIdRef(const CGObjectInstance * obj);
|
ObjectIdRef(const CGObjectInstance * obj);
|
||||||
@ -166,16 +171,18 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj);
|
|||||||
|
|
||||||
ui64 evaluateDanger(const CGObjectInstance * obj);
|
ui64 evaluateDanger(const CGObjectInstance * obj);
|
||||||
ui64 evaluateDanger(crint3 tile, const CGHeroInstance * visitor);
|
ui64 evaluateDanger(crint3 tile, const CGHeroInstance * visitor);
|
||||||
bool isSafeToVisit(HeroPtr h, crint3 tile);
|
|
||||||
bool isObjectRemovable(const CGObjectInstance * obj); //FIXME FIXME: move logic to object property!
|
bool isObjectRemovable(const CGObjectInstance * obj); //FIXME FIXME: move logic to object property!
|
||||||
|
bool isSafeToVisit(HeroPtr h, uint64_t dangerStrength);
|
||||||
|
bool isSafeToVisit(HeroPtr h, crint3 tile);
|
||||||
|
|
||||||
bool compareMovement(HeroPtr lhs, HeroPtr rhs);
|
bool compareMovement(HeroPtr lhs, HeroPtr rhs);
|
||||||
bool compareHeroStrength(HeroPtr h1, HeroPtr h2);
|
bool compareHeroStrength(HeroPtr h1, HeroPtr h2);
|
||||||
bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2);
|
bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2);
|
||||||
bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2);
|
bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2);
|
||||||
ui64 howManyReinforcementsCanBuy(HeroPtr h, const CGTownInstance * t);
|
ui64 howManyReinforcementsCanBuy(HeroPtr h, const CGDwelling * t);
|
||||||
ui64 howManyReinforcementsCanGet(HeroPtr h, const CGTownInstance * t);
|
ui64 howManyReinforcementsCanGet(HeroPtr h, const CGTownInstance * t);
|
||||||
int3 whereToExplore(HeroPtr h);
|
int3 whereToExplore(HeroPtr h);
|
||||||
|
uint32_t distanceToTile(const CGHeroInstance * hero, int3 pos);
|
||||||
|
|
||||||
class CDistanceSorter
|
class CDistanceSorter
|
||||||
{
|
{
|
||||||
|
@ -17,7 +17,7 @@ AIhelper::AIhelper()
|
|||||||
{
|
{
|
||||||
resourceManager.reset(new ResourceManager());
|
resourceManager.reset(new ResourceManager());
|
||||||
buildingManager.reset(new BuildingManager());
|
buildingManager.reset(new BuildingManager());
|
||||||
//TODO: push to vector
|
pathfindingManager.reset(new PathfindingManager());
|
||||||
}
|
}
|
||||||
|
|
||||||
AIhelper::~AIhelper()
|
AIhelper::~AIhelper()
|
||||||
@ -31,16 +31,16 @@ bool AIhelper::notifyGoalCompleted(Goals::TSubgoal goal)
|
|||||||
|
|
||||||
void AIhelper::setCB(CPlayerSpecificInfoCallback * CB)
|
void AIhelper::setCB(CPlayerSpecificInfoCallback * CB)
|
||||||
{
|
{
|
||||||
//TODO: for
|
|
||||||
resourceManager->setCB(CB);
|
resourceManager->setCB(CB);
|
||||||
buildingManager->setCB(CB);
|
buildingManager->setCB(CB);
|
||||||
|
pathfindingManager->setCB(CB);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AIhelper::setAI(VCAI * AI)
|
void AIhelper::setAI(VCAI * AI)
|
||||||
{
|
{
|
||||||
//TODO: for loop
|
|
||||||
resourceManager->setAI(AI);
|
resourceManager->setAI(AI);
|
||||||
buildingManager->setAI(AI);
|
buildingManager->setAI(AI);
|
||||||
|
pathfindingManager->setAI(AI);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AIhelper::getBuildingOptions(const CGTownInstance * t)
|
bool AIhelper::getBuildingOptions(const CGTownInstance * t)
|
||||||
@ -112,3 +112,33 @@ TResource AIhelper::allGold() const
|
|||||||
{
|
{
|
||||||
return resourceManager->allGold();
|
return resourceManager->allGold();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Goals::TGoalVec AIhelper::howToVisitTile(int3 tile)
|
||||||
|
{
|
||||||
|
return pathfindingManager->howToVisitTile(tile);
|
||||||
|
}
|
||||||
|
|
||||||
|
Goals::TGoalVec AIhelper::howToVisitObj(ObjectIdRef obj)
|
||||||
|
{
|
||||||
|
return pathfindingManager->howToVisitObj(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
Goals::TGoalVec AIhelper::howToVisitTile(HeroPtr hero, int3 tile, bool allowGatherArmy)
|
||||||
|
{
|
||||||
|
return pathfindingManager->howToVisitTile(hero, tile, allowGatherArmy);
|
||||||
|
}
|
||||||
|
|
||||||
|
Goals::TGoalVec AIhelper::howToVisitObj(HeroPtr hero, ObjectIdRef obj, bool allowGatherArmy)
|
||||||
|
{
|
||||||
|
return pathfindingManager->howToVisitObj(hero, obj, allowGatherArmy);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<AIPath> AIhelper::getPathsToTile(HeroPtr hero, int3 tile)
|
||||||
|
{
|
||||||
|
return pathfindingManager->getPathsToTile(hero, tile);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AIhelper::resetPaths()
|
||||||
|
{
|
||||||
|
pathfindingManager->resetPaths();
|
||||||
|
}
|
||||||
|
@ -16,25 +16,26 @@
|
|||||||
|
|
||||||
#include "ResourceManager.h"
|
#include "ResourceManager.h"
|
||||||
#include "BuildingManager.h"
|
#include "BuildingManager.h"
|
||||||
|
#include "Pathfinding/PathfindingManager.h"
|
||||||
|
|
||||||
class ResourceManager;
|
class ResourceManager;
|
||||||
class BuildingManager;
|
class BuildingManager;
|
||||||
|
|
||||||
|
|
||||||
//indirection interface for various modules
|
//indirection interface for various modules
|
||||||
class DLL_EXPORT AIhelper : public IResourceManager, public IBuildingManager
|
class DLL_EXPORT AIhelper : public IResourceManager, public IBuildingManager, public IPathfindingManager
|
||||||
{
|
{
|
||||||
friend class VCAI;
|
friend class VCAI;
|
||||||
friend struct SetGlobalState; //mess?
|
friend struct SetGlobalState; //mess?
|
||||||
|
|
||||||
std::shared_ptr<ResourceManager> resourceManager;
|
std::shared_ptr<ResourceManager> resourceManager;
|
||||||
std::shared_ptr<BuildingManager> buildingManager;
|
std::shared_ptr<BuildingManager> buildingManager;
|
||||||
|
std::shared_ptr<PathfindingManager> pathfindingManager;
|
||||||
//TODO: vector<IAbstractManager>
|
//TODO: vector<IAbstractManager>
|
||||||
public:
|
public:
|
||||||
AIhelper();
|
AIhelper();
|
||||||
~AIhelper();
|
~AIhelper();
|
||||||
|
|
||||||
//from ResourceManager
|
|
||||||
bool canAfford(const TResources & cost) const;
|
bool canAfford(const TResources & cost) const;
|
||||||
TResources reservedResources() const override;
|
TResources reservedResources() const override;
|
||||||
TResources freeResources() const override;
|
TResources freeResources() const override;
|
||||||
@ -43,21 +44,26 @@ public:
|
|||||||
TResource allGold() const override;
|
TResource allGold() const override;
|
||||||
|
|
||||||
Goals::TSubgoal whatToDo(TResources &res, Goals::TSubgoal goal) override;
|
Goals::TSubgoal whatToDo(TResources &res, Goals::TSubgoal goal) override;
|
||||||
Goals::TSubgoal whatToDo() const override; //peek highest-priority goal
|
Goals::TSubgoal whatToDo() const override;
|
||||||
bool containsObjective(Goals::TSubgoal goal) const;
|
bool containsObjective(Goals::TSubgoal goal) const;
|
||||||
bool hasTasksLeft() const override;
|
bool hasTasksLeft() const override;
|
||||||
|
|
||||||
|
bool getBuildingOptions(const CGTownInstance * t) override;
|
||||||
|
boost::optional<PotentialBuilding> immediateBuilding() const override;
|
||||||
|
boost::optional<PotentialBuilding> expensiveBuilding() const override;
|
||||||
|
boost::optional<BuildingID> canBuildAnyStructure(const CGTownInstance * t, const std::vector<BuildingID> & buildList, unsigned int maxDays = 7) const override;
|
||||||
|
|
||||||
|
Goals::TGoalVec howToVisitTile(HeroPtr hero, int3 tile, bool allowGatherArmy = true) override;
|
||||||
|
Goals::TGoalVec howToVisitObj(HeroPtr hero, ObjectIdRef obj, bool allowGatherArmy = true) override;
|
||||||
|
Goals::TGoalVec howToVisitTile(int3 tile) override;
|
||||||
|
Goals::TGoalVec howToVisitObj(ObjectIdRef obj) override;
|
||||||
|
std::vector<AIPath> getPathsToTile(HeroPtr hero, int3 tile) override;
|
||||||
|
void resetPaths() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool notifyGoalCompleted(Goals::TSubgoal goal);
|
bool notifyGoalCompleted(Goals::TSubgoal goal);
|
||||||
|
|
||||||
void setCB(CPlayerSpecificInfoCallback * CB) override;
|
void setCB(CPlayerSpecificInfoCallback * CB) override;
|
||||||
void setAI(VCAI * AI) override;
|
void setAI(VCAI * AI) override;
|
||||||
|
|
||||||
//from BuildingManager
|
|
||||||
public:
|
|
||||||
bool getBuildingOptions(const CGTownInstance * t) override;
|
|
||||||
boost::optional<PotentialBuilding> immediateBuilding() const override;
|
|
||||||
boost::optional<PotentialBuilding> expensiveBuilding() const override;
|
|
||||||
boost::optional<BuildingID> canBuildAnyStructure(const CGTownInstance * t, const std::vector<BuildingID> & buildList, unsigned int maxDays = 7) const override;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -8,6 +8,10 @@ include_directories(${Boost_INCLUDE_DIRS} ${CMAKE_HOME_DIRECTORY} ${CMAKE_HOME_D
|
|||||||
set(VCAI_SRCS
|
set(VCAI_SRCS
|
||||||
StdInc.cpp
|
StdInc.cpp
|
||||||
|
|
||||||
|
Pathfinding/AIPathfinderConfig.cpp
|
||||||
|
Pathfinding/AIPathfinder.cpp
|
||||||
|
Pathfinding/AINodeStorage.cpp
|
||||||
|
Pathfinding/PathfindingManager.cpp
|
||||||
AIUtility.cpp
|
AIUtility.cpp
|
||||||
AIhelper.cpp
|
AIhelper.cpp
|
||||||
ResourceManager.cpp
|
ResourceManager.cpp
|
||||||
@ -24,7 +28,11 @@ set(VCAI_SRCS
|
|||||||
|
|
||||||
set(VCAI_HEADERS
|
set(VCAI_HEADERS
|
||||||
StdInc.h
|
StdInc.h
|
||||||
|
|
||||||
|
Pathfinding/AIPathfinderConfig.h
|
||||||
|
Pathfinding/AIPathfinder.h
|
||||||
|
Pathfinding/AINodeStorage.h
|
||||||
|
Pathfinding/PathfindingManager.h
|
||||||
AIUtility.h
|
AIUtility.h
|
||||||
AIhelper.h
|
AIhelper.h
|
||||||
ResourceManager.h
|
ResourceManager.h
|
||||||
|
@ -79,7 +79,7 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army)
|
|||||||
float HeroMovementGoalEngineBase::calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const
|
float HeroMovementGoalEngineBase::calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const
|
||||||
{
|
{
|
||||||
float turns = 0.0f;
|
float turns = 0.0f;
|
||||||
float distance = CPathfinderHelper::getMovementCost(h, tile);
|
float distance = distanceToTile(h, tile);
|
||||||
if(distance)
|
if(distance)
|
||||||
{
|
{
|
||||||
if(distance < h->movement) //we can move there within one turn
|
if(distance < h->movement) //we can move there within one turn
|
||||||
|
@ -19,10 +19,13 @@ extern boost::thread_specific_ptr<VCAI> ai;
|
|||||||
|
|
||||||
Goals::TSubgoal FuzzyHelper::chooseSolution(Goals::TGoalVec vec)
|
Goals::TSubgoal FuzzyHelper::chooseSolution(Goals::TGoalVec vec)
|
||||||
{
|
{
|
||||||
if(vec.empty()) //no possibilities found
|
if(vec.empty())
|
||||||
return sptr(Goals::Invalid());
|
{
|
||||||
|
logAi->debug("FuzzyHelper found no goals. Returning Goals::Invalid.");
|
||||||
|
|
||||||
ai->cachedSectorMaps.clear();
|
//no possibilities found
|
||||||
|
return sptr(Goals::Invalid());
|
||||||
|
}
|
||||||
|
|
||||||
//a trick to switch between heroes less often - calculatePaths is costly
|
//a trick to switch between heroes less often - calculatePaths is costly
|
||||||
auto sortByHeroes = [](const Goals::TSubgoal & lhs, const Goals::TSubgoal & rhs) -> bool
|
auto sortByHeroes = [](const Goals::TSubgoal & lhs, const Goals::TSubgoal & rhs) -> bool
|
||||||
@ -40,7 +43,17 @@ Goals::TSubgoal FuzzyHelper::chooseSolution(Goals::TGoalVec vec)
|
|||||||
{
|
{
|
||||||
return lhs->priority < rhs->priority;
|
return lhs->priority < rhs->priority;
|
||||||
};
|
};
|
||||||
return *boost::max_element(vec, compareGoals);
|
|
||||||
|
for(auto goal : vec)
|
||||||
|
{
|
||||||
|
logAi->debug("FuzzyHelper evaluated goal %s, priority=%i", goal->name(), goal->priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
Goals::TSubgoal result = *boost::max_element(vec, compareGoals);
|
||||||
|
|
||||||
|
logAi->debug("FuzzyHelper returned goal %s, priority=%i", result->name(), result->priority);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui64 FuzzyHelper::estimateBankDanger(const CBank * bank)
|
ui64 FuzzyHelper::estimateBankDanger(const CBank * bank)
|
||||||
@ -96,24 +109,7 @@ float FuzzyHelper::evaluate(Goals::ClearWayTo & g)
|
|||||||
if (!g.hero.h)
|
if (!g.hero.h)
|
||||||
return 0; //lowest priority
|
return 0; //lowest priority
|
||||||
|
|
||||||
int3 t = ai->getCachedSectorMap(g.hero)->firstTileToGet(g.hero, g.tile);
|
return g.whatToDoToAchieve()->accept(this);
|
||||||
|
|
||||||
if(t.valid())
|
|
||||||
{
|
|
||||||
if(isSafeToVisit(g.hero, t))
|
|
||||||
{
|
|
||||||
g.setpriority(Goals::VisitTile(g.tile).sethero(g.hero).setisAbstract(g.isAbstract).accept(this));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
g.setpriority (Goals::GatherArmy(evaluateDanger(t, g.hero.h)*SAFE_ATTACK_CONSTANT).
|
|
||||||
sethero(g.hero).setisAbstract(true).accept(this));
|
|
||||||
}
|
|
||||||
return g.priority;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float FuzzyHelper::evaluate(Goals::BuildThis & g)
|
float FuzzyHelper::evaluate(Goals::BuildThis & g)
|
||||||
|
@ -163,6 +163,9 @@ bool Goals::AbstractGoal::operator==(AbstractGoal & g)
|
|||||||
return (town == g.town && bid == g.bid); //build specific structure in specific town
|
return (town == g.town && bid == g.bid); //build specific structure in specific town
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case BUY_ARMY:
|
||||||
|
return town == g.town;
|
||||||
|
|
||||||
//no check atm
|
//no check atm
|
||||||
case COLLECT_RES:
|
case COLLECT_RES:
|
||||||
case TRADE: //TODO
|
case TRADE: //TODO
|
||||||
@ -698,63 +701,10 @@ TGoalVec ClearWayTo::getAllPossibleSubgoals()
|
|||||||
|
|
||||||
//if our hero is trapped, make sure we request clearing the way from OUR perspective
|
//if our hero is trapped, make sure we request clearing the way from OUR perspective
|
||||||
|
|
||||||
auto sm = ai->getCachedSectorMap(h);
|
vstd::concatenate(ret, ai->ah->howToVisitTile(h, tile));
|
||||||
|
|
||||||
int3 tileToHit = sm->firstTileToGet(h, tile);
|
|
||||||
if(!tileToHit.valid())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if(isBlockedBorderGate(tileToHit))
|
|
||||||
{
|
|
||||||
//FIXME: this way we'll not visit gate and activate quest :?
|
|
||||||
ret.push_back(sptr(Goals::FindObj(Obj::KEYMASTER, cb->getTile(tileToHit)->visitableObjects.back()->subID)));
|
|
||||||
return ret; //only option
|
|
||||||
}
|
|
||||||
|
|
||||||
auto topObj = cb->getTopObj(tileToHit);
|
|
||||||
if(topObj)
|
|
||||||
{
|
|
||||||
if(vstd::contains(ai->reservedObjs, topObj) && !vstd::contains(ai->reservedHeroesMap[h], topObj))
|
|
||||||
{
|
|
||||||
throw goalFulfilledException(sptr(Goals::ClearWayTo(tile, h)));
|
|
||||||
continue; //do not capure object reserved by other hero
|
|
||||||
}
|
|
||||||
|
|
||||||
if(topObj->ID == Obj::HERO && cb->getPlayerRelations(h->tempOwner, topObj->tempOwner) != PlayerRelations::ENEMIES)
|
|
||||||
{
|
|
||||||
if(topObj != hero.get(true)) //the hero we want to free
|
|
||||||
logAi->error("%s stands in the way of %s", topObj->getObjectName(), h->getObjectName());
|
|
||||||
}
|
|
||||||
if(topObj->ID == Obj::QUEST_GUARD || topObj->ID == Obj::BORDERGUARD)
|
|
||||||
{
|
|
||||||
if(shouldVisit(h, topObj))
|
|
||||||
{
|
|
||||||
//do NOT use VISIT_TILE, as tile with quets guard can't be visited
|
|
||||||
ret.push_back(sptr(Goals::VisitObj(topObj->id.getNum()).sethero(h))); //TODO: Recheck this code - object visit became elementar goal
|
|
||||||
continue; //do not try to visit tile or gather army
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//TODO: we should be able to return apriopriate quest here
|
|
||||||
//ret.push_back(ai->questToGoal());
|
|
||||||
//however, visiting obj for firts time will give us quest
|
|
||||||
logAi->debug("Quest guard blocks the way to %s", tile.toString());
|
|
||||||
continue; //do not access quets guard if we can't complete the quest
|
|
||||||
}
|
|
||||||
return ret; //try complete quest as the only option
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(isSafeToVisit(h, tileToHit)) //this makes sense only if tile is guarded, but there is no quest object
|
|
||||||
{
|
|
||||||
ret.push_back(sptr(Goals::VisitTile(tileToHit).sethero(h)));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ret.push_back(sptr(Goals::GatherArmy(evaluateDanger(tileToHit, h) * SAFE_ATTACK_CONSTANT).
|
|
||||||
sethero(h).setisAbstract(true)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if(ai->canRecruitAnyHero())
|
|
||||||
|
if(ret.empty() && ai->canRecruitAnyHero())
|
||||||
ret.push_back(sptr(Goals::RecruitHero()));
|
ret.push_back(sptr(Goals::RecruitHero()));
|
||||||
|
|
||||||
if(ret.empty())
|
if(ret.empty())
|
||||||
@ -860,13 +810,11 @@ TGoalVec Explore::getAllPossibleSubgoals()
|
|||||||
auto primaryHero = ai->primaryHero().h;
|
auto primaryHero = ai->primaryHero().h;
|
||||||
for(auto h : heroes)
|
for(auto h : heroes)
|
||||||
{
|
{
|
||||||
auto sm = ai->getCachedSectorMap(h);
|
|
||||||
|
|
||||||
for(auto obj : objs) //double loop, performance risk?
|
for(auto obj : objs) //double loop, performance risk?
|
||||||
{
|
{
|
||||||
auto t = sm->firstTileToGet(h, obj->visitablePos()); //we assume that no more than one tile on the way is guarded
|
auto waysToVisitObj = ai->ah->howToVisitObj(h, obj);
|
||||||
if(ai->isTileNotReserved(h, t))
|
|
||||||
ret.push_back(sptr(Goals::ClearWayTo(obj->visitablePos(), h).setisAbstract(true)));
|
vstd::concatenate(ret, waysToVisitObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
int3 t = whereToExplore(h);
|
int3 t = whereToExplore(h);
|
||||||
@ -883,7 +831,9 @@ TGoalVec Explore::getAllPossibleSubgoals()
|
|||||||
t = ai->explorationDesperate(h); //check this only ONCE, high cost
|
t = ai->explorationDesperate(h); //check this only ONCE, high cost
|
||||||
if (t.valid()) //don't waste time if we are completely blocked
|
if (t.valid()) //don't waste time if we are completely blocked
|
||||||
{
|
{
|
||||||
ret.push_back(sptr(Goals::ClearWayTo(t, h).setisAbstract(true)));
|
auto waysToVisitTile = ai->ah->howToVisitTile(h, t);
|
||||||
|
|
||||||
|
vstd::concatenate(ret, waysToVisitTile);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1131,7 +1081,6 @@ TGoalVec Goals::CollectRes::getAllPossibleSubgoals()
|
|||||||
}
|
}
|
||||||
for (auto h : cb->getHeroesInfo())
|
for (auto h : cb->getHeroesInfo())
|
||||||
{
|
{
|
||||||
auto sm = ai->getCachedSectorMap(h);
|
|
||||||
std::vector<const CGObjectInstance *> ourObjs(objs); //copy common objects
|
std::vector<const CGObjectInstance *> ourObjs(objs); //copy common objects
|
||||||
|
|
||||||
for (auto obj : ai->reservedHeroesMap[h]) //add objects reserved by this hero
|
for (auto obj : ai->reservedHeroesMap[h]) //add objects reserved by this hero
|
||||||
@ -1139,11 +1088,12 @@ TGoalVec Goals::CollectRes::getAllPossibleSubgoals()
|
|||||||
if (givesResource(obj))
|
if (givesResource(obj))
|
||||||
ourObjs.push_back(obj);
|
ourObjs.push_back(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto obj : ourObjs)
|
for (auto obj : ourObjs)
|
||||||
{
|
{
|
||||||
auto pos = obj->visitablePos();
|
auto waysToGo = ai->ah->howToVisitObj(h, ObjectIdRef(obj));
|
||||||
if (ai->isTileNotReserved(h, pos)) //further decomposition and evaluation will be handled by VisitObj
|
|
||||||
ret.push_back(sptr(Goals::VisitObj(obj->id.getNum()).sethero(h).setisAbstract(true)));
|
vstd::concatenate(ret, waysToGo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
@ -1384,7 +1334,6 @@ TGoalVec Conquer::getAllPossibleSubgoals()
|
|||||||
|
|
||||||
for(auto h : cb->getHeroesInfo())
|
for(auto h : cb->getHeroesInfo())
|
||||||
{
|
{
|
||||||
auto sm = ai->getCachedSectorMap(h);
|
|
||||||
std::vector<const CGObjectInstance *> ourObjs(objs); //copy common objects
|
std::vector<const CGObjectInstance *> ourObjs(objs); //copy common objects
|
||||||
|
|
||||||
for(auto obj : ai->reservedHeroesMap[h]) //add objects reserved by this hero
|
for(auto obj : ai->reservedHeroesMap[h]) //add objects reserved by this hero
|
||||||
@ -1394,32 +1343,9 @@ TGoalVec Conquer::getAllPossibleSubgoals()
|
|||||||
}
|
}
|
||||||
for(auto obj : ourObjs)
|
for(auto obj : ourObjs)
|
||||||
{
|
{
|
||||||
int3 dest = obj->visitablePos();
|
auto waysToGo = ai->ah->howToVisitObj(h, ObjectIdRef(obj));
|
||||||
auto t = sm->firstTileToGet(h, dest); //we assume that no more than one tile on the way is guarded
|
|
||||||
if(t.valid()) //we know any path at all
|
vstd::concatenate(ret, waysToGo);
|
||||||
{
|
|
||||||
if(ai->isTileNotReserved(h, t)) //no other hero wants to conquer that tile
|
|
||||||
{
|
|
||||||
if(isSafeToVisit(h, dest))
|
|
||||||
{
|
|
||||||
if(dest != t) //there is something blocking our way
|
|
||||||
ret.push_back(sptr(Goals::ClearWayTo(dest, h).setisAbstract(true)));
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if(obj->ID.num == Obj::HERO) //enemy hero may move to other position
|
|
||||||
{
|
|
||||||
ret.push_back(sptr(Goals::VisitHero(obj->id.getNum()).sethero(h).setisAbstract(true)));
|
|
||||||
}
|
|
||||||
else //just get that object
|
|
||||||
{
|
|
||||||
ret.push_back(sptr(Goals::VisitObj(obj->id.getNum()).sethero(h).setisAbstract(true)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else //we need to get army in order to conquer that place
|
|
||||||
ret.push_back(sptr(Goals::GatherArmy(evaluateDanger(dest, h) * SAFE_ATTACK_CONSTANT).sethero(h).setisAbstract(true)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!objs.empty() && ai->canRecruitAnyHero()) //probably no point to recruit hero if we see no objects to capture
|
if(!objs.empty() && ai->canRecruitAnyHero()) //probably no point to recruit hero if we see no objects to capture
|
||||||
@ -1500,6 +1426,11 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
|
|||||||
//get all possible towns, heroes and dwellings we may use
|
//get all possible towns, heroes and dwellings we may use
|
||||||
TGoalVec ret;
|
TGoalVec ret;
|
||||||
|
|
||||||
|
if(!hero.validAndSet())
|
||||||
|
{
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: include evaluation of monsters gather in calculation
|
//TODO: include evaluation of monsters gather in calculation
|
||||||
for(auto t : cb->getTownsInfo())
|
for(auto t : cb->getTownsInfo())
|
||||||
{
|
{
|
||||||
@ -1513,14 +1444,16 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
|
|||||||
ret.push_back(sptr(Goals::VisitTile(pos).sethero(hero)));
|
ret.push_back(sptr(Goals::VisitTile(pos).sethero(hero)));
|
||||||
}
|
}
|
||||||
//buy army in town
|
//buy army in town
|
||||||
if (!t->visitingHero || t->visitingHero != hero.get(true))
|
if (!t->visitingHero || t->visitingHero == hero.get(true))
|
||||||
{
|
{
|
||||||
ui32 val = std::min<ui32>(value, howManyReinforcementsCanBuy(hero, t));
|
ui32 val = std::min<ui32>(value, howManyReinforcementsCanBuy(hero, t));
|
||||||
if (val)
|
if (val)
|
||||||
{
|
{
|
||||||
auto goal = sptr(Goals::BuyArmy(t, val).sethero(hero));
|
auto goal = sptr(Goals::BuyArmy(t, val).sethero(hero));
|
||||||
if (!ai->ah->containsObjective(goal)) //avoid loops caused by reserving same objective twice
|
if(!ai->ah->containsObjective(goal)) //avoid loops caused by reserving same objective twice
|
||||||
ret.push_back(goal);
|
ret.push_back(goal);
|
||||||
|
else
|
||||||
|
logAi->debug("Can not buy army, because of ai->ah->containsObjective");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//build dwelling
|
//build dwelling
|
||||||
@ -1532,6 +1465,8 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
|
|||||||
auto goal = sptr(BuildThis(bid.get(), t).setpriority(priority));
|
auto goal = sptr(BuildThis(bid.get(), t).setpriority(priority));
|
||||||
if (!ai->ah->containsObjective(goal)) //avoid loops caused by reserving same objective twice
|
if (!ai->ah->containsObjective(goal)) //avoid loops caused by reserving same objective twice
|
||||||
ret.push_back(goal);
|
ret.push_back(goal);
|
||||||
|
else
|
||||||
|
logAi->debug("Can not build a structure, because of ai->ah->containsObjective");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1554,7 +1489,8 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
|
|||||||
for(auto h : otherHeroes)
|
for(auto h : otherHeroes)
|
||||||
{
|
{
|
||||||
// Go to the other hero if we are faster
|
// Go to the other hero if we are faster
|
||||||
if (!vstd::contains(ai->visitedHeroes[hero], h)) //visit only once each turn //FIXME: this is only bug workaround
|
if (!vstd::contains(ai->visitedHeroes[hero], h)
|
||||||
|
&& ai->isAccessibleForHero(h->visitablePos(), hero, true)) //visit only once each turn //FIXME: this is only bug workaround
|
||||||
ret.push_back(sptr(Goals::VisitHero(h->id.getNum()).setisAbstract(true).sethero(hero)));
|
ret.push_back(sptr(Goals::VisitHero(h->id.getNum()).setisAbstract(true).sethero(hero)));
|
||||||
// Let the other hero come to us
|
// Let the other hero come to us
|
||||||
if (!vstd::contains(ai->visitedHeroes[h], hero))
|
if (!vstd::contains(ai->visitedHeroes[h], hero))
|
||||||
@ -1572,15 +1508,21 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
|
|||||||
if(relationToOwner == PlayerRelations::SAME_PLAYER)
|
if(relationToOwner == PlayerRelations::SAME_PLAYER)
|
||||||
{
|
{
|
||||||
auto dwelling = dynamic_cast<const CGDwelling *>(obj);
|
auto dwelling = dynamic_cast<const CGDwelling *>(obj);
|
||||||
for(auto & creLevel : dwelling->creatures)
|
|
||||||
|
ui32 val = std::min<ui32>(value, howManyReinforcementsCanBuy(hero, dwelling));
|
||||||
|
|
||||||
|
if(val)
|
||||||
{
|
{
|
||||||
if(creLevel.first)
|
for(auto & creLevel : dwelling->creatures)
|
||||||
{
|
{
|
||||||
for(auto & creatureID : creLevel.second)
|
if(creLevel.first)
|
||||||
{
|
{
|
||||||
auto creature = VLC->creh->creatures[creatureID];
|
for(auto & creatureID : creLevel.second)
|
||||||
if(ai->ah->freeResources().canAfford(creature->cost))
|
{
|
||||||
objs.push_back(obj); //TODO: reserve resources?
|
auto creature = VLC->creh->creatures[creatureID];
|
||||||
|
if(ai->ah->freeResources().canAfford(creature->cost))
|
||||||
|
objs.push_back(obj); //TODO: reserve resources?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1589,13 +1531,13 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
|
|||||||
}
|
}
|
||||||
for(auto h : cb->getHeroesInfo())
|
for(auto h : cb->getHeroesInfo())
|
||||||
{
|
{
|
||||||
auto sm = ai->getCachedSectorMap(h);
|
|
||||||
for(auto obj : objs)
|
for(auto obj : objs)
|
||||||
{
|
{
|
||||||
//find safe dwelling
|
//find safe dwelling
|
||||||
auto pos = obj->visitablePos();
|
if(ai->isGoodForVisit(obj, h))
|
||||||
if(ai->isGoodForVisit(obj, h, *sm))
|
{
|
||||||
ret.push_back(sptr(Goals::VisitTile(pos).sethero(h)));
|
vstd::concatenate(ret, ai->ah->howToVisitObj(h, obj));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
237
AI/VCAI/Pathfinding/AINodeStorage.cpp
Normal file
237
AI/VCAI/Pathfinding/AINodeStorage.cpp
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
/*
|
||||||
|
* AIhelper.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
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include "StdInc.h"
|
||||||
|
#include "AINodeStorage.h"
|
||||||
|
|
||||||
|
|
||||||
|
AINodeStorage::AINodeStorage(const int3 & Sizes)
|
||||||
|
: sizes(Sizes)
|
||||||
|
{
|
||||||
|
nodes.resize(boost::extents[sizes.x][sizes.y][sizes.z][EPathfindingLayer::NUM_LAYERS][NUM_CHAINS]);
|
||||||
|
}
|
||||||
|
|
||||||
|
AINodeStorage::~AINodeStorage()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const AIPathNode * AINodeStorage::getAINode(const CGPathNode * node) const
|
||||||
|
{
|
||||||
|
return static_cast<const AIPathNode *>(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AINodeStorage::updateAINode(CGPathNode * node, std::function<void(AIPathNode *)> updater)
|
||||||
|
{
|
||||||
|
auto aiNode = static_cast<AIPathNode *>(node);
|
||||||
|
|
||||||
|
updater(aiNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AINodeStorage::isBattleNode(const CGPathNode * node) const
|
||||||
|
{
|
||||||
|
return getAINode(node)->chainMask & BATTLE_CHAIN > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
AIPathNode * AINodeStorage::getNode(const int3 & coord, const EPathfindingLayer layer, int chainNumber)
|
||||||
|
{
|
||||||
|
return &nodes[coord.x][coord.y][coord.z][layer][chainNumber];
|
||||||
|
}
|
||||||
|
|
||||||
|
CGPathNode * AINodeStorage::getInitialNode()
|
||||||
|
{
|
||||||
|
auto hpos = hero->getPosition(false);
|
||||||
|
auto initialNode = getNode(hpos, hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND, 0);
|
||||||
|
|
||||||
|
initialNode->turns = 0;
|
||||||
|
initialNode->moveRemains = hero->movement;
|
||||||
|
initialNode->danger = 0;
|
||||||
|
|
||||||
|
return initialNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility)
|
||||||
|
{
|
||||||
|
for(int i = 0; i < NUM_CHAINS; i++)
|
||||||
|
{
|
||||||
|
AIPathNode & heroNode = nodes[coord.x][coord.y][coord.z][layer][i];
|
||||||
|
|
||||||
|
heroNode.chainMask = i;
|
||||||
|
heroNode.update(coord, layer, accessibility);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInfo & source)
|
||||||
|
{
|
||||||
|
const AIPathNode * srcNode = getAINode(source.node);
|
||||||
|
|
||||||
|
updateAINode(destination.node, [&](AIPathNode * dstNode) {
|
||||||
|
dstNode->moveRemains = destination.movementLeft;
|
||||||
|
dstNode->turns = destination.turn;
|
||||||
|
dstNode->danger = srcNode->danger;
|
||||||
|
dstNode->action = destination.action;
|
||||||
|
dstNode->theNodeBefore = srcNode->theNodeBefore;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
|
||||||
|
const PathNodeInfo & source,
|
||||||
|
const PathfinderConfig * pathfinderConfig,
|
||||||
|
const CPathfinderHelper * pathfinderHelper)
|
||||||
|
{
|
||||||
|
std::vector<CGPathNode *> neighbours;
|
||||||
|
const AIPathNode * srcNode = getAINode(source.node);
|
||||||
|
auto accessibleNeighbourTiles = pathfinderHelper->getNeighbourTiles(source);
|
||||||
|
|
||||||
|
for(auto & neighbour : accessibleNeighbourTiles)
|
||||||
|
{
|
||||||
|
for(EPathfindingLayer i = EPathfindingLayer::LAND; i <= EPathfindingLayer::AIR; i.advance(1))
|
||||||
|
{
|
||||||
|
auto nextNode = getNode(neighbour, i, srcNode->chainMask);
|
||||||
|
|
||||||
|
if(nextNode->accessible == CGPathNode::NOT_SET)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
neighbours.push_back(nextNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return neighbours;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
|
||||||
|
const PathNodeInfo & source,
|
||||||
|
const PathfinderConfig * pathfinderConfig,
|
||||||
|
const CPathfinderHelper * pathfinderHelper)
|
||||||
|
{
|
||||||
|
std::vector<CGPathNode *> neighbours;
|
||||||
|
auto accessibleExits = pathfinderHelper->getTeleportExits(source);
|
||||||
|
auto srcNode = getAINode(source.node);
|
||||||
|
|
||||||
|
for(auto & neighbour : accessibleExits)
|
||||||
|
{
|
||||||
|
auto node = getNode(neighbour, source.node->layer, srcNode->chainMask);
|
||||||
|
|
||||||
|
neighbours.push_back(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
return neighbours;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
|
||||||
|
{
|
||||||
|
auto pos = destination.coord;
|
||||||
|
auto chains = nodes[pos.x][pos.y][pos.z][EPathfindingLayer::LAND];
|
||||||
|
auto destinationNode = getAINode(destination.node);
|
||||||
|
|
||||||
|
for(const AIPathNode & node : chains)
|
||||||
|
{
|
||||||
|
auto sameNode = node.chainMask == destinationNode->chainMask;
|
||||||
|
if(sameNode || node.action == CGPathNode::ENodeAction::UNKNOWN)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(node.danger <= destinationNode->danger && destinationNode->chainMask == 1 && node.chainMask == 0)
|
||||||
|
{
|
||||||
|
if(node.turns < destinationNode->turns
|
||||||
|
|| node.turns == destinationNode->turns && node.moveRemains >= destinationNode->moveRemains)
|
||||||
|
{
|
||||||
|
logAi->trace(
|
||||||
|
"Block ineficient move %s:->%s, mask=%i, mp diff: %i",
|
||||||
|
source.coord.toString(),
|
||||||
|
destination.coord.toString(),
|
||||||
|
destinationNode->chainMask,
|
||||||
|
node.moveRemains - destinationNode->moveRemains);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<AIPath> AINodeStorage::getChainInfo(int3 pos) const
|
||||||
|
{
|
||||||
|
std::vector<AIPath> paths;
|
||||||
|
auto chains = nodes[pos.x][pos.y][pos.z][EPathfindingLayer::LAND];
|
||||||
|
|
||||||
|
for(const AIPathNode & node : chains)
|
||||||
|
{
|
||||||
|
if(node.action == CGPathNode::ENodeAction::UNKNOWN)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
AIPath path;
|
||||||
|
const AIPathNode * current = &node;
|
||||||
|
|
||||||
|
while(current != nullptr)
|
||||||
|
{
|
||||||
|
AIPathNodeInfo pathNode;
|
||||||
|
|
||||||
|
pathNode.movementPointsLeft = current->moveRemains;
|
||||||
|
pathNode.movementPointsUsed = (int)(current->turns * hero->maxMovePoints(true) + hero->movement) - (int)current->moveRemains;
|
||||||
|
pathNode.turns = current->turns;
|
||||||
|
pathNode.danger = current->danger;
|
||||||
|
pathNode.coord = current->coord;
|
||||||
|
|
||||||
|
path.nodes.push_back(pathNode);
|
||||||
|
current = getAINode(current->theNodeBefore);
|
||||||
|
}
|
||||||
|
paths.push_back(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
AIPath::AIPath()
|
||||||
|
: nodes({})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int3 AIPath::firstTileToGet() const
|
||||||
|
{
|
||||||
|
if(nodes.size())
|
||||||
|
{
|
||||||
|
return nodes.back().coord;
|
||||||
|
}
|
||||||
|
|
||||||
|
return int3(-1, -1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t AIPath::getPathDanger() const
|
||||||
|
{
|
||||||
|
if(nodes.size())
|
||||||
|
{
|
||||||
|
return nodes.front().danger;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t AIPath::movementCost() const
|
||||||
|
{
|
||||||
|
if(nodes.size())
|
||||||
|
{
|
||||||
|
return nodes.front().movementPointsUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: boost:optional?
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t AIPath::getTotalDanger(HeroPtr hero) const
|
||||||
|
{
|
||||||
|
uint64_t pathDanger = getPathDanger();
|
||||||
|
uint64_t objDanger = evaluateDanger(nodes.front().coord, hero.get()); // bank danger is not checked by pathfinder
|
||||||
|
uint64_t danger = pathDanger > objDanger ? pathDanger : objDanger;
|
||||||
|
|
||||||
|
return danger;
|
||||||
|
}
|
105
AI/VCAI/Pathfinding/AINodeStorage.h
Normal file
105
AI/VCAI/Pathfinding/AINodeStorage.h
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* AIhelper.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 "../../../lib/CPathfinder.h"
|
||||||
|
#include "../../../lib/mapObjects/CGHeroInstance.h"
|
||||||
|
#include "AIUtility.h"
|
||||||
|
|
||||||
|
class IVirtualObject
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void materialize();
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AIPathNode : public CGPathNode
|
||||||
|
{
|
||||||
|
uint32_t chainMask;
|
||||||
|
uint64_t danger;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AIPathNodeInfo
|
||||||
|
{
|
||||||
|
uint32_t movementPointsLeft;
|
||||||
|
uint32_t movementPointsUsed;
|
||||||
|
int turns;
|
||||||
|
int3 coord;
|
||||||
|
uint64_t danger;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AIPath
|
||||||
|
{
|
||||||
|
std::vector<AIPathNodeInfo> nodes;
|
||||||
|
|
||||||
|
AIPath();
|
||||||
|
|
||||||
|
/// Gets danger of path excluding danger of visiting the target object like creature bank
|
||||||
|
uint64_t getPathDanger() const;
|
||||||
|
|
||||||
|
/// Gets danger of path including danger of visiting the target object like creature bank
|
||||||
|
uint64_t getTotalDanger(HeroPtr hero) const;
|
||||||
|
|
||||||
|
int3 firstTileToGet() const;
|
||||||
|
|
||||||
|
uint32_t movementCost() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AINodeStorage : public INodeStorage
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
int3 sizes;
|
||||||
|
|
||||||
|
/// 1-3 - position on map, 4 - layer (air, water, land), 5 - chain (normal, battle, spellcast and combinations)
|
||||||
|
boost::multi_array<AIPathNode, 5> nodes;
|
||||||
|
const CGHeroInstance * hero;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// more than 1 chain layer allows us to have more than 1 path to each tile so we can chose more optimal one.
|
||||||
|
static const int NUM_CHAINS = 2;
|
||||||
|
static const int NORMAL_CHAIN = 0;
|
||||||
|
static const int BATTLE_CHAIN = 1;
|
||||||
|
|
||||||
|
AINodeStorage(const int3 & sizes);
|
||||||
|
~AINodeStorage();
|
||||||
|
|
||||||
|
virtual CGPathNode * getInitialNode() override;
|
||||||
|
virtual void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility) override;
|
||||||
|
|
||||||
|
virtual std::vector<CGPathNode *> calculateNeighbours(
|
||||||
|
const PathNodeInfo & source,
|
||||||
|
const PathfinderConfig * pathfinderConfig,
|
||||||
|
const CPathfinderHelper * pathfinderHelper) override;
|
||||||
|
|
||||||
|
virtual std::vector<CGPathNode *> calculateTeleportations(
|
||||||
|
const PathNodeInfo & source,
|
||||||
|
const PathfinderConfig * pathfinderConfig,
|
||||||
|
const CPathfinderHelper * pathfinderHelper) override;
|
||||||
|
|
||||||
|
virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override;
|
||||||
|
|
||||||
|
const AIPathNode * getAINode(const CGPathNode * node) const;
|
||||||
|
void updateAINode(CGPathNode * node, std::function<void (AIPathNode *)> updater);
|
||||||
|
|
||||||
|
bool isBattleNode(const CGPathNode * node) const;
|
||||||
|
bool hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const;
|
||||||
|
AIPathNode * getNode(const int3 & coord, const EPathfindingLayer layer, int chainNumber);
|
||||||
|
std::vector<AIPath> getChainInfo(int3 pos) const;
|
||||||
|
|
||||||
|
void setHero(HeroPtr heroPtr)
|
||||||
|
{
|
||||||
|
hero = heroPtr.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
const CGHeroInstance * getHero() const
|
||||||
|
{
|
||||||
|
return hero;
|
||||||
|
}
|
||||||
|
};
|
62
AI/VCAI/Pathfinding/AIPathfinder.cpp
Normal file
62
AI/VCAI/Pathfinding/AIPathfinder.cpp
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* AIhelper.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
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include "StdInc.h"
|
||||||
|
#include "AIPathfinder.h"
|
||||||
|
#include "AIPathfinderConfig.h"
|
||||||
|
#include "../../../CCallback.h"
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<AINodeStorage>> AIPathfinder::storagePool;
|
||||||
|
std::map<HeroPtr, std::shared_ptr<AINodeStorage>> AIPathfinder::storageMap;
|
||||||
|
boost::mutex AIPathfinder::storageMutex;
|
||||||
|
|
||||||
|
AIPathfinder::AIPathfinder(CPlayerSpecificInfoCallback * cb)
|
||||||
|
:cb(cb)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AIPathfinder::clear()
|
||||||
|
{
|
||||||
|
boost::unique_lock<boost::mutex> storageLock(storageMutex);
|
||||||
|
storageMap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<AIPath> AIPathfinder::getPathInfo(HeroPtr hero, int3 tile)
|
||||||
|
{
|
||||||
|
boost::unique_lock<boost::mutex> storageLock(storageMutex);
|
||||||
|
std::shared_ptr<AINodeStorage> nodeStorage;
|
||||||
|
|
||||||
|
if(!vstd::contains(storageMap, hero))
|
||||||
|
{
|
||||||
|
logAi->debug("Recalculate paths for %s", hero->name);
|
||||||
|
|
||||||
|
if(storageMap.size() < storagePool.size())
|
||||||
|
{
|
||||||
|
nodeStorage = storagePool.at(storageMap.size());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nodeStorage = std::make_shared<AINodeStorage>(cb->getMapSize());
|
||||||
|
storagePool.push_back(nodeStorage);
|
||||||
|
}
|
||||||
|
|
||||||
|
storageMap[hero] = nodeStorage;
|
||||||
|
|
||||||
|
auto config = std::make_shared<AIPathfinderConfig>(cb, nodeStorage);
|
||||||
|
|
||||||
|
nodeStorage->setHero(hero.get());
|
||||||
|
cb->calculatePaths(config, hero.get());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nodeStorage = storageMap.at(hero);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodeStorage->getChainInfo(tile);
|
||||||
|
}
|
28
AI/VCAI/Pathfinding/AIPathfinder.h
Normal file
28
AI/VCAI/Pathfinding/AIPathfinder.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* AIhelper.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 "AIUtility.h"
|
||||||
|
#include "AINodeStorage.h"
|
||||||
|
|
||||||
|
class AIPathfinder
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
static std::vector<std::shared_ptr<AINodeStorage>> storagePool;
|
||||||
|
static std::map<HeroPtr, std::shared_ptr<AINodeStorage>> storageMap;
|
||||||
|
static boost::mutex storageMutex;
|
||||||
|
CPlayerSpecificInfoCallback * cb;
|
||||||
|
|
||||||
|
public:
|
||||||
|
AIPathfinder(CPlayerSpecificInfoCallback * cb);
|
||||||
|
std::vector<AIPath> getPathInfo(HeroPtr hero, int3 tile);
|
||||||
|
void clear();
|
||||||
|
};
|
257
AI/VCAI/Pathfinding/AIPathfinderConfig.cpp
Normal file
257
AI/VCAI/Pathfinding/AIPathfinderConfig.cpp
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
/*
|
||||||
|
* AIhelper.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
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include "StdInc.h"
|
||||||
|
#include "AIPathfinderConfig.h"
|
||||||
|
#include "../../../CCallback.h"
|
||||||
|
|
||||||
|
class AIMovementAfterDestinationRule : public MovementAfterDestinationRule
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
CPlayerSpecificInfoCallback * cb;
|
||||||
|
std::shared_ptr<AINodeStorage> nodeStorage;
|
||||||
|
|
||||||
|
public:
|
||||||
|
AIMovementAfterDestinationRule(CPlayerSpecificInfoCallback * cb, std::shared_ptr<AINodeStorage> nodeStorage)
|
||||||
|
:cb(cb), nodeStorage(nodeStorage)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void process(
|
||||||
|
const PathNodeInfo & source,
|
||||||
|
CDestinationNodeInfo & destination,
|
||||||
|
const PathfinderConfig * pathfinderConfig,
|
||||||
|
CPathfinderHelper * pathfinderHelper) const override
|
||||||
|
{
|
||||||
|
if(nodeStorage->hasBetterChain(source, destination))
|
||||||
|
{
|
||||||
|
destination.blocked = true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper);
|
||||||
|
|
||||||
|
if(blocker == BlockingReason::NONE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto srcNode = nodeStorage->getAINode(source.node);
|
||||||
|
|
||||||
|
if(blocker == BlockingReason::DESTINATION_BLOCKVIS && destination.nodeObject)
|
||||||
|
{
|
||||||
|
auto objID = destination.nodeObject->ID;
|
||||||
|
if(objID == Obj::HERO && destination.objectRelations != PlayerRelations::ENEMIES
|
||||||
|
|| objID == Obj::SUBTERRANEAN_GATE || objID == Obj::MONOLITH_TWO_WAY
|
||||||
|
|| objID == Obj::MONOLITH_ONE_WAY_ENTRANCE || objID == Obj::MONOLITH_ONE_WAY_EXIT
|
||||||
|
|| objID == Obj::WHIRLPOOL)
|
||||||
|
{
|
||||||
|
destination.blocked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(blocker == BlockingReason::DESTINATION_VISIT)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(blocker == BlockingReason::DESTINATION_GUARDED)
|
||||||
|
{
|
||||||
|
auto srcGuardians = cb->getGuardingCreatures(source.coord);
|
||||||
|
auto destGuardians = cb->getGuardingCreatures(destination.coord);
|
||||||
|
|
||||||
|
if(destGuardians.empty())
|
||||||
|
{
|
||||||
|
destination.blocked = true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vstd::erase_if(destGuardians, [&](const CGObjectInstance * destGuard) -> bool
|
||||||
|
{
|
||||||
|
return vstd::contains(srcGuardians, destGuard);
|
||||||
|
});
|
||||||
|
|
||||||
|
auto guardsAlreadyBypassed = destGuardians.empty() && srcGuardians.size();
|
||||||
|
if(guardsAlreadyBypassed && nodeStorage->isBattleNode(source.node))
|
||||||
|
{
|
||||||
|
logAi->trace(
|
||||||
|
"Bypass guard at destination while moving %s -> %s",
|
||||||
|
source.coord.toString(),
|
||||||
|
destination.coord.toString());
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto destNode = nodeStorage->getAINode(destination.node);
|
||||||
|
auto battleNode = nodeStorage->getNode(destination.coord, destination.node->layer, destNode->chainMask | AINodeStorage::BATTLE_CHAIN);
|
||||||
|
|
||||||
|
if(battleNode->locked)
|
||||||
|
{
|
||||||
|
logAi->trace(
|
||||||
|
"Block bypass guard at destination while moving %s -> %s",
|
||||||
|
source.coord.toString(),
|
||||||
|
destination.coord.toString());
|
||||||
|
|
||||||
|
destination.blocked = true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto hero = nodeStorage->getHero();
|
||||||
|
auto danger = evaluateDanger(destination.coord, hero);
|
||||||
|
|
||||||
|
destination.node = battleNode;
|
||||||
|
nodeStorage->commit(destination, source);
|
||||||
|
|
||||||
|
if(battleNode->danger < danger)
|
||||||
|
{
|
||||||
|
battleNode->danger = danger;
|
||||||
|
}
|
||||||
|
|
||||||
|
logAi->trace(
|
||||||
|
"Begin bypass guard at destination with danger %s while moving %s -> %s",
|
||||||
|
std::to_string(danger),
|
||||||
|
source.coord.toString(),
|
||||||
|
destination.coord.toString());
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
destination.blocked = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class AIMovementToDestinationRule : public MovementToDestinationRule
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
CPlayerSpecificInfoCallback * cb;
|
||||||
|
std::shared_ptr<AINodeStorage> nodeStorage;
|
||||||
|
|
||||||
|
public:
|
||||||
|
AIMovementToDestinationRule(CPlayerSpecificInfoCallback * cb, std::shared_ptr<AINodeStorage> nodeStorage)
|
||||||
|
:cb(cb), nodeStorage(nodeStorage)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void process(
|
||||||
|
const PathNodeInfo & source,
|
||||||
|
CDestinationNodeInfo & destination,
|
||||||
|
const PathfinderConfig * pathfinderConfig,
|
||||||
|
CPathfinderHelper * pathfinderHelper) const override
|
||||||
|
{
|
||||||
|
auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper);
|
||||||
|
|
||||||
|
if(blocker == BlockingReason::NONE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(blocker == BlockingReason::SOURCE_GUARDED && nodeStorage->isBattleNode(source.node))
|
||||||
|
{
|
||||||
|
auto srcGuardians = cb->getGuardingCreatures(source.coord);
|
||||||
|
auto destGuardians = cb->getGuardingCreatures(destination.coord);
|
||||||
|
|
||||||
|
for(auto srcGuard : srcGuardians)
|
||||||
|
{
|
||||||
|
if(!vstd::contains(destGuardians, srcGuard))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto guardPos = srcGuard->visitablePos();
|
||||||
|
if(guardPos != source.coord && guardPos != destination.coord)
|
||||||
|
{
|
||||||
|
destination.blocked = true; // allow to pass monster only through guard tile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!destination.blocked)
|
||||||
|
{
|
||||||
|
logAi->trace(
|
||||||
|
"Bypass src guard while moving from %s to %s",
|
||||||
|
source.coord.toString(),
|
||||||
|
destination.coord.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
destination.blocked = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class AIPreviousNodeRule : public MovementToDestinationRule
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
CPlayerSpecificInfoCallback * cb;
|
||||||
|
std::shared_ptr<AINodeStorage> nodeStorage;
|
||||||
|
|
||||||
|
public:
|
||||||
|
AIPreviousNodeRule(CPlayerSpecificInfoCallback * cb, std::shared_ptr<AINodeStorage> nodeStorage)
|
||||||
|
:cb(cb), nodeStorage(nodeStorage)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void process(
|
||||||
|
const PathNodeInfo & source,
|
||||||
|
CDestinationNodeInfo & destination,
|
||||||
|
const PathfinderConfig * pathfinderConfig,
|
||||||
|
CPathfinderHelper * pathfinderHelper) const override
|
||||||
|
{
|
||||||
|
auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper);
|
||||||
|
|
||||||
|
if(source.guarded)
|
||||||
|
{
|
||||||
|
auto srcGuardian = cb->guardingCreaturePosition(source.node->coord);
|
||||||
|
|
||||||
|
if(srcGuardian == source.node->coord)
|
||||||
|
{
|
||||||
|
// guardian tile is used as chain junction
|
||||||
|
destination.node->theNodeBefore = source.node;
|
||||||
|
|
||||||
|
logAi->trace(
|
||||||
|
"Link src node %s to destination node %s while bypassing guard",
|
||||||
|
source.coord.toString(),
|
||||||
|
destination.coord.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(source.node->action == CGPathNode::ENodeAction::BLOCKING_VISIT || source.node->action == CGPathNode::ENodeAction::VISIT)
|
||||||
|
{
|
||||||
|
// we can not directly bypass objects, we need to interact with them first
|
||||||
|
destination.node->theNodeBefore = source.node;
|
||||||
|
|
||||||
|
logAi->trace(
|
||||||
|
"Link src node %s to destination node %s while bypassing visitable obj",
|
||||||
|
source.coord.toString(),
|
||||||
|
destination.coord.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<IPathfindingRule>> makeRuleset(
|
||||||
|
CPlayerSpecificInfoCallback * cb,
|
||||||
|
std::shared_ptr<AINodeStorage> nodeStorage)
|
||||||
|
{
|
||||||
|
std::vector<std::shared_ptr<IPathfindingRule>> rules = {
|
||||||
|
std::make_shared<LayerTransitionRule>(),
|
||||||
|
std::make_shared<DestinationActionRule>(),
|
||||||
|
std::make_shared<AIMovementToDestinationRule>(cb, nodeStorage),
|
||||||
|
std::make_shared<MovementCostRule>(),
|
||||||
|
std::make_shared<AIPreviousNodeRule>(cb, nodeStorage),
|
||||||
|
std::make_shared<AIMovementAfterDestinationRule>(cb, nodeStorage)
|
||||||
|
};
|
||||||
|
|
||||||
|
return rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
AIPathfinderConfig::AIPathfinderConfig(
|
||||||
|
CPlayerSpecificInfoCallback * cb,
|
||||||
|
std::shared_ptr<AINodeStorage> nodeStorage)
|
||||||
|
:PathfinderConfig(nodeStorage, makeRuleset(cb, nodeStorage))
|
||||||
|
{
|
||||||
|
}
|
19
AI/VCAI/Pathfinding/AIPathfinderConfig.h
Normal file
19
AI/VCAI/Pathfinding/AIPathfinderConfig.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* AIhelper.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 "AINodeStorage.h"
|
||||||
|
|
||||||
|
class AIPathfinderConfig : public PathfinderConfig
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AIPathfinderConfig(CPlayerSpecificInfoCallback * cb, std::shared_ptr<AINodeStorage> nodeStorage);
|
||||||
|
};
|
214
AI/VCAI/Pathfinding/PathfindingManager.cpp
Normal file
214
AI/VCAI/Pathfinding/PathfindingManager.cpp
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
/*
|
||||||
|
* AIhelper.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
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include "StdInc.h"
|
||||||
|
#include "PathfindingManager.h"
|
||||||
|
#include "AIPathfinder.h"
|
||||||
|
#include "AIPathfinderConfig.h"
|
||||||
|
#include "../../../lib/CGameInfoCallback.h"
|
||||||
|
#include "../../../lib/mapping/CMap.h"
|
||||||
|
|
||||||
|
PathfindingManager::PathfindingManager(CPlayerSpecificInfoCallback * CB, VCAI * AI)
|
||||||
|
: ai(AI), cb(CB)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void PathfindingManager::setCB(CPlayerSpecificInfoCallback * CB)
|
||||||
|
{
|
||||||
|
cb = CB;
|
||||||
|
pathfinder.reset(new AIPathfinder(cb));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PathfindingManager::setAI(VCAI * AI)
|
||||||
|
{
|
||||||
|
ai = AI;
|
||||||
|
}
|
||||||
|
|
||||||
|
Goals::TGoalVec PathfindingManager::howToVisitTile(int3 tile)
|
||||||
|
{
|
||||||
|
Goals::TGoalVec result;
|
||||||
|
|
||||||
|
auto heroes = cb->getHeroesInfo();
|
||||||
|
|
||||||
|
for(auto hero : heroes)
|
||||||
|
{
|
||||||
|
vstd::concatenate(result, howToVisitTile(hero, tile));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Goals::TGoalVec PathfindingManager::howToVisitObj(ObjectIdRef obj)
|
||||||
|
{
|
||||||
|
Goals::TGoalVec result;
|
||||||
|
|
||||||
|
auto heroes = cb->getHeroesInfo();
|
||||||
|
|
||||||
|
for(auto hero : heroes)
|
||||||
|
{
|
||||||
|
vstd::concatenate(result, howToVisitObj(hero, obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Goals::TGoalVec PathfindingManager::howToVisitTile(HeroPtr hero, int3 tile, bool allowGatherArmy)
|
||||||
|
{
|
||||||
|
return findPath(hero, tile, allowGatherArmy, [&](int3 firstTileToGet) -> Goals::TSubgoal
|
||||||
|
{
|
||||||
|
return sptr(Goals::VisitTile(firstTileToGet).sethero(hero).setisAbstract(true));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Goals::TGoalVec PathfindingManager::howToVisitObj(HeroPtr hero, ObjectIdRef obj, bool allowGatherArmy)
|
||||||
|
{
|
||||||
|
if(!obj)
|
||||||
|
{
|
||||||
|
return Goals::TGoalVec();
|
||||||
|
}
|
||||||
|
|
||||||
|
int3 dest = obj->visitablePos();
|
||||||
|
|
||||||
|
return findPath(hero, dest, allowGatherArmy, [&](int3 firstTileToGet) -> Goals::TSubgoal
|
||||||
|
{
|
||||||
|
return selectVisitingGoal(hero, obj);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<AIPath> PathfindingManager::getPathsToTile(HeroPtr hero, int3 tile)
|
||||||
|
{
|
||||||
|
return pathfinder->getPathInfo(hero, tile);
|
||||||
|
}
|
||||||
|
|
||||||
|
Goals::TGoalVec PathfindingManager::findPath(
|
||||||
|
HeroPtr hero,
|
||||||
|
crint3 dest,
|
||||||
|
bool allowGatherArmy,
|
||||||
|
const std::function<Goals::TSubgoal(int3)> doVisitTile)
|
||||||
|
{
|
||||||
|
Goals::TGoalVec result;
|
||||||
|
boost::optional<uint64_t> armyValueRequired;
|
||||||
|
uint64_t danger;
|
||||||
|
|
||||||
|
std::vector<AIPath> chainInfo = pathfinder->getPathInfo(hero, dest);
|
||||||
|
|
||||||
|
logAi->trace("Trying to find a way for %s to visit tile %s", hero->name, dest.toString());
|
||||||
|
|
||||||
|
for(auto path : chainInfo)
|
||||||
|
{
|
||||||
|
int3 firstTileToGet = path.firstTileToGet();
|
||||||
|
|
||||||
|
logAi->trace("Path found size=%i, first tile=%s", path.nodes.size(), firstTileToGet.toString());
|
||||||
|
|
||||||
|
if(firstTileToGet.valid() && ai->isTileNotReserved(hero.get(), firstTileToGet))
|
||||||
|
{
|
||||||
|
danger = path.getTotalDanger(hero);
|
||||||
|
|
||||||
|
if(isSafeToVisit(hero, danger))
|
||||||
|
{
|
||||||
|
logAi->trace("It's safe for %s to visit tile %s with danger %s", hero->name, dest.toString(), std::to_string(danger));
|
||||||
|
|
||||||
|
auto solution = dest == firstTileToGet
|
||||||
|
? doVisitTile(firstTileToGet)
|
||||||
|
: clearWayTo(hero, firstTileToGet);
|
||||||
|
result.push_back(solution);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!armyValueRequired || armyValueRequired > danger)
|
||||||
|
{
|
||||||
|
armyValueRequired = boost::make_optional(danger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
danger = armyValueRequired.get_value_or(0);
|
||||||
|
|
||||||
|
if(allowGatherArmy && danger > 0)
|
||||||
|
{
|
||||||
|
//we need to get army in order to conquer that place
|
||||||
|
logAi->trace("Gather army for %s, value=%s", hero->name, std::to_string(danger));
|
||||||
|
result.push_back(sptr(Goals::GatherArmy(danger * SAFE_ATTACK_CONSTANT).sethero(hero).setisAbstract(true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Goals::TSubgoal PathfindingManager::selectVisitingGoal(HeroPtr hero, ObjectIdRef obj) const
|
||||||
|
{
|
||||||
|
int3 dest = obj->visitablePos();
|
||||||
|
|
||||||
|
if(obj->ID.num == Obj::HERO) //enemy hero may move to other position
|
||||||
|
{
|
||||||
|
return sptr(Goals::VisitHero(obj->id.getNum()).sethero(hero).setisAbstract(true));
|
||||||
|
}
|
||||||
|
else //just visit that tile
|
||||||
|
{
|
||||||
|
//if target is town, fuzzy system will use additional "estimatedReward" variable to increase priority a bit
|
||||||
|
//TODO: change to getObj eventually and and move appropiate logic there
|
||||||
|
return obj->ID.num == Obj::TOWN
|
||||||
|
? sptr(Goals::VisitTile(dest).sethero(hero).setobjid(obj->ID.num).setisAbstract(true))
|
||||||
|
: sptr(Goals::VisitTile(dest).sethero(hero).setisAbstract(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sptr(Goals::VisitTile(dest).sethero(hero).setisAbstract(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
Goals::TSubgoal PathfindingManager::clearWayTo(HeroPtr hero, int3 firstTileToGet)
|
||||||
|
{
|
||||||
|
if(isBlockedBorderGate(firstTileToGet))
|
||||||
|
{
|
||||||
|
//FIXME: this way we'll not visit gate and activate quest :?
|
||||||
|
return sptr(Goals::FindObj(Obj::KEYMASTER, cb->getTile(firstTileToGet)->visitableObjects.back()->subID));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto topObj = cb->getTopObj(firstTileToGet);
|
||||||
|
if(topObj)
|
||||||
|
{
|
||||||
|
|
||||||
|
if(vstd::contains(ai->reservedObjs, topObj) && !vstd::contains(ai->reservedHeroesMap[hero], topObj))
|
||||||
|
{
|
||||||
|
return sptr(Goals::Invalid());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(topObj->ID == Obj::HERO && cb->getPlayerRelations(hero->tempOwner, topObj->tempOwner) != PlayerRelations::ENEMIES)
|
||||||
|
{
|
||||||
|
if(topObj != hero.get(true)) //the hero we want to free
|
||||||
|
{
|
||||||
|
logAi->error("%s stands in the way of %s", topObj->getObjectName(), hero->getObjectName());
|
||||||
|
|
||||||
|
return sptr(Goals::Invalid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(topObj->ID == Obj::QUEST_GUARD || topObj->ID == Obj::BORDERGUARD)
|
||||||
|
{
|
||||||
|
if(shouldVisit(hero, topObj))
|
||||||
|
{
|
||||||
|
//do NOT use VISIT_TILE, as tile with quets guard can't be visited
|
||||||
|
return sptr(Goals::VisitObj(topObj->id.getNum()).sethero(hero));
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: we should be able to return apriopriate quest here
|
||||||
|
//ret.push_back(ai->questToGoal());
|
||||||
|
//however, visiting obj for firts time will give us quest
|
||||||
|
//do not access quets guard if we can't complete the quest
|
||||||
|
return sptr(Goals::Invalid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sptr(Goals::VisitTile(firstTileToGet).sethero(hero).setisAbstract(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PathfindingManager::resetPaths()
|
||||||
|
{
|
||||||
|
logAi->debug("AIPathfinder has been reseted.");
|
||||||
|
pathfinder->clear();
|
||||||
|
}
|
64
AI/VCAI/Pathfinding/PathfindingManager.h
Normal file
64
AI/VCAI/Pathfinding/PathfindingManager.h
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* AIhelper.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 "VCAI.h"
|
||||||
|
#include "AINodeStorage.h"
|
||||||
|
|
||||||
|
class IPathfindingManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~IPathfindingManager() = default;
|
||||||
|
virtual void setCB(CPlayerSpecificInfoCallback * CB) = 0;
|
||||||
|
virtual void setAI(VCAI * AI) = 0;
|
||||||
|
|
||||||
|
virtual void resetPaths() = 0;
|
||||||
|
virtual Goals::TGoalVec howToVisitTile(HeroPtr hero, int3 tile, bool allowGatherArmy = true) = 0;
|
||||||
|
virtual Goals::TGoalVec howToVisitObj(HeroPtr hero, ObjectIdRef obj, bool allowGatherArmy = true) = 0;
|
||||||
|
virtual Goals::TGoalVec howToVisitTile(int3 tile) = 0;
|
||||||
|
virtual Goals::TGoalVec howToVisitObj(ObjectIdRef obj) = 0;
|
||||||
|
virtual std::vector<AIPath> getPathsToTile(HeroPtr hero, int3 tile) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PathfindingManager : public IPathfindingManager
|
||||||
|
{
|
||||||
|
friend class AIhelper;
|
||||||
|
|
||||||
|
private:
|
||||||
|
CPlayerSpecificInfoCallback * cb; //this is enough, but we downcast from CCallback
|
||||||
|
VCAI * ai;
|
||||||
|
std::unique_ptr<AIPathfinder> pathfinder;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PathfindingManager() = default;
|
||||||
|
PathfindingManager(CPlayerSpecificInfoCallback * CB, VCAI * AI = nullptr); //for tests only
|
||||||
|
|
||||||
|
Goals::TGoalVec howToVisitTile(HeroPtr hero, int3 tile, bool allowGatherArmy = true) override;
|
||||||
|
Goals::TGoalVec howToVisitObj(HeroPtr hero, ObjectIdRef obj, bool allowGatherArmy = true) override;
|
||||||
|
Goals::TGoalVec howToVisitTile(int3 tile) override;
|
||||||
|
Goals::TGoalVec howToVisitObj(ObjectIdRef obj) override;
|
||||||
|
std::vector<AIPath> getPathsToTile(HeroPtr hero, int3 tile) override;
|
||||||
|
void resetPaths() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setCB(CPlayerSpecificInfoCallback * CB) override;
|
||||||
|
void setAI(VCAI * AI) override;
|
||||||
|
|
||||||
|
Goals::TGoalVec findPath(
|
||||||
|
HeroPtr hero,
|
||||||
|
crint3 dest,
|
||||||
|
bool allowGatherArmy,
|
||||||
|
const std::function<Goals::TSubgoal(int3)> goalFactory);
|
||||||
|
|
||||||
|
Goals::TSubgoal clearWayTo(HeroPtr hero, int3 firstTileToGet);
|
||||||
|
|
||||||
|
Goals::TSubgoal selectVisitingGoal(HeroPtr hero, ObjectIdRef obj) const;
|
||||||
|
};
|
@ -172,6 +172,8 @@ Goals::TSubgoal ResourceManager::whatToDo() const //suggest any goal
|
|||||||
|
|
||||||
Goals::TSubgoal ResourceManager::whatToDo(TResources &res, Goals::TSubgoal goal)
|
Goals::TSubgoal ResourceManager::whatToDo(TResources &res, Goals::TSubgoal goal)
|
||||||
{
|
{
|
||||||
|
logAi->trace("ResourceManager: checking goal %s which requires resources %s", goal->name(), res.toString());
|
||||||
|
|
||||||
TResources accumulatedResources;
|
TResources accumulatedResources;
|
||||||
auto allResources = cb->getResourceAmount();
|
auto allResources = cb->getResourceAmount();
|
||||||
|
|
||||||
@ -181,19 +183,38 @@ Goals::TSubgoal ResourceManager::whatToDo(TResources &res, Goals::TSubgoal goal)
|
|||||||
for (auto it = queue.ordered_begin(); it != queue.ordered_end(); it++)
|
for (auto it = queue.ordered_begin(); it != queue.ordered_end(); it++)
|
||||||
{
|
{
|
||||||
accumulatedResources += it->resources;
|
accumulatedResources += it->resources;
|
||||||
if (!accumulatedResources.canBeAfforded(allResources)) //can't afford
|
|
||||||
return collectResourcesForOurGoal(ro);
|
logAi->trace(
|
||||||
|
"ResourceManager: checking goal %s, accumulatedResources=%s, available=%s",
|
||||||
|
it->goal->name(),
|
||||||
|
accumulatedResources.toString(),
|
||||||
|
allResources.toString());
|
||||||
|
|
||||||
|
if(!accumulatedResources.canBeAfforded(allResources))
|
||||||
|
{
|
||||||
|
//can't afford
|
||||||
|
break;
|
||||||
|
}
|
||||||
else //can afford all goals up to this point
|
else //can afford all goals up to this point
|
||||||
{
|
{
|
||||||
if (it->goal == goal)
|
if(it->goal == goal)
|
||||||
|
{
|
||||||
|
logAi->debug("ResourceManager: can afford goal %s", goal->name());
|
||||||
return goal; //can afford immediately
|
return goal; //can afford immediately
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return collectResourcesForOurGoal(ro); //fallback, ever needed?
|
|
||||||
|
logAi->debug("ResourceManager: can not afford goal %s", goal->name());
|
||||||
|
|
||||||
|
return collectResourcesForOurGoal(ro);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ResourceManager::containsObjective(Goals::TSubgoal goal) const
|
bool ResourceManager::containsObjective(Goals::TSubgoal goal) const
|
||||||
{
|
{
|
||||||
|
logAi->trace("Entering ResourceManager.containsObjective goal=%s", goal->name());
|
||||||
|
dumpToLog();
|
||||||
|
|
||||||
//TODO: unit tests for once
|
//TODO: unit tests for once
|
||||||
for (auto objective : queue)
|
for (auto objective : queue)
|
||||||
{
|
{
|
||||||
@ -205,6 +226,8 @@ bool ResourceManager::containsObjective(Goals::TSubgoal goal) const
|
|||||||
|
|
||||||
bool ResourceManager::notifyGoalCompleted(Goals::TSubgoal goal)
|
bool ResourceManager::notifyGoalCompleted(Goals::TSubgoal goal)
|
||||||
{
|
{
|
||||||
|
logAi->trace("Entering ResourceManager.notifyGoalCompleted goal=%s", goal->name());
|
||||||
|
|
||||||
if (goal->invalid())
|
if (goal->invalid())
|
||||||
logAi->warn("Attempt to complete Invalid goal");
|
logAi->warn("Attempt to complete Invalid goal");
|
||||||
|
|
||||||
@ -215,15 +238,18 @@ bool ResourceManager::notifyGoalCompleted(Goals::TSubgoal goal)
|
|||||||
{
|
{
|
||||||
return ro.goal == goal || ro.goal->fulfillsMe (goal);
|
return ro.goal == goal || ro.goal->fulfillsMe (goal);
|
||||||
});
|
});
|
||||||
if (it != queue.end()) //removed at least one
|
if(it != queue.end()) //removed at least one
|
||||||
{
|
{
|
||||||
logAi->debug("Removing goal %s from ResourceManager.", it->goal->name());
|
logAi->debug("Removing goal %s from ResourceManager.", it->goal->name());
|
||||||
queue.erase(queue.s_handle_from_iterator(it));
|
queue.erase(queue.s_handle_from_iterator(it));
|
||||||
removedGoal = true;
|
removedGoal = true;
|
||||||
}
|
}
|
||||||
else //found nothing more to remove
|
else //found nothing more to remove
|
||||||
return removedGoal;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dumpToLog();
|
||||||
|
|
||||||
return removedGoal;
|
return removedGoal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,10 +274,21 @@ bool ResourceManager::updateGoal(Goals::TSubgoal goal)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ResourceManager::dumpToLog() const
|
||||||
|
{
|
||||||
|
for(auto it = queue.ordered_begin(); it != queue.ordered_end(); it++)
|
||||||
|
{
|
||||||
|
logAi->trace("ResourceManager contains goal %s which requires resources %s", it->goal->name(), it->resources.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool ResourceManager::tryPush(const ResourceObjective & o)
|
bool ResourceManager::tryPush(const ResourceObjective & o)
|
||||||
{
|
{
|
||||||
auto goal = o.goal;
|
auto goal = o.goal;
|
||||||
|
|
||||||
|
logAi->trace("ResourceManager: Trying to add goal %s which requires resources %s", goal->name(), o.resources.toString());
|
||||||
|
dumpToLog();
|
||||||
|
|
||||||
auto it = boost::find_if(queue, [goal](const ResourceObjective & ro) -> bool
|
auto it = boost::find_if(queue, [goal](const ResourceObjective & ro) -> bool
|
||||||
{
|
{
|
||||||
return ro.goal == goal;
|
return ro.goal == goal;
|
||||||
|
@ -102,6 +102,8 @@ private:
|
|||||||
|
|
||||||
boost::heap::binomial_heap<ResourceObjective> queue;
|
boost::heap::binomial_heap<ResourceObjective> queue;
|
||||||
|
|
||||||
|
void dumpToLog() const;
|
||||||
|
|
||||||
//TODO: register?
|
//TODO: register?
|
||||||
template<typename Handler> void serializeInternal(Handler & h, const int version)
|
template<typename Handler> void serializeInternal(Handler & h, const int version)
|
||||||
{
|
{
|
||||||
|
177
AI/VCAI/VCAI.cpp
177
AI/VCAI/VCAI.cpp
@ -120,7 +120,7 @@ void VCAI::heroMoved(const TryMoveHero & details)
|
|||||||
|
|
||||||
validateObject(details.id); //enemy hero may have left visible area
|
validateObject(details.id); //enemy hero may have left visible area
|
||||||
auto hero = cb->getHero(details.id);
|
auto hero = cb->getHero(details.id);
|
||||||
cachedSectorMaps.clear();
|
ah->resetPaths();
|
||||||
|
|
||||||
const int3 from = CGHeroInstance::convertPosition(details.start, false);
|
const int3 from = CGHeroInstance::convertPosition(details.start, false);
|
||||||
const int3 to = CGHeroInstance::convertPosition(details.end, false);
|
const int3 to = CGHeroInstance::convertPosition(details.end, false);
|
||||||
@ -401,7 +401,7 @@ void VCAI::newObject(const CGObjectInstance * obj)
|
|||||||
if(obj->isVisitable())
|
if(obj->isVisitable())
|
||||||
addVisitableObj(obj);
|
addVisitableObj(obj);
|
||||||
|
|
||||||
cachedSectorMaps.clear();
|
ah->resetPaths();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VCAI::objectRemoved(const CGObjectInstance * obj)
|
void VCAI::objectRemoved(const CGObjectInstance * obj)
|
||||||
@ -460,7 +460,7 @@ void VCAI::objectRemoved(const CGObjectInstance * obj)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cachedSectorMaps.clear(); //invalidate all paths
|
ah->resetPaths();
|
||||||
|
|
||||||
//TODO
|
//TODO
|
||||||
//there are other places where CGObjectinstance ptrs are stored...
|
//there are other places where CGObjectinstance ptrs are stored...
|
||||||
@ -761,7 +761,7 @@ void VCAI::saveGame(BinarySerializer & h, const int version)
|
|||||||
void VCAI::loadGame(BinaryDeserializer & h, const int version)
|
void VCAI::loadGame(BinaryDeserializer & h, const int version)
|
||||||
{
|
{
|
||||||
LOG_TRACE_PARAMS(logAi, "version '%i'", version);
|
LOG_TRACE_PARAMS(logAi, "version '%i'", version);
|
||||||
NET_EVENT_HANDLER;
|
//NET_EVENT_HANDLER;
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
//disabled due to issue 2890
|
//disabled due to issue 2890
|
||||||
@ -792,9 +792,11 @@ void makePossibleUpgrades(const CArmedInstance * obj)
|
|||||||
|
|
||||||
void VCAI::makeTurn()
|
void VCAI::makeTurn()
|
||||||
{
|
{
|
||||||
logGlobal->info("Player %d (%s) starting turn", playerID, playerID.getStr());
|
|
||||||
|
|
||||||
MAKING_TURN;
|
MAKING_TURN;
|
||||||
|
|
||||||
|
auto day = cb->getDate(Date::EDateType::DAY);
|
||||||
|
logAi->info("Player %d (%s) starting turn, day %d", playerID, playerID.getStr(), day);
|
||||||
|
|
||||||
boost::shared_lock<boost::shared_mutex> gsLock(CGameState::mutex);
|
boost::shared_lock<boost::shared_mutex> gsLock(CGameState::mutex);
|
||||||
setThreadName("VCAI::makeTurn");
|
setThreadName("VCAI::makeTurn");
|
||||||
|
|
||||||
@ -882,8 +884,12 @@ void VCAI::mainLoop()
|
|||||||
elementarGoals.clear();
|
elementarGoals.clear();
|
||||||
ultimateGoalsFromBasic.clear();
|
ultimateGoalsFromBasic.clear();
|
||||||
|
|
||||||
|
logAi->debug("Main loop: decomposing %i basic goals", basicGoals.size());
|
||||||
|
|
||||||
for (auto basicGoal : basicGoals)
|
for (auto basicGoal : basicGoals)
|
||||||
{
|
{
|
||||||
|
logAi->debug("Main loop: decomposing basic goal %s", basicGoal->name());
|
||||||
|
|
||||||
auto goalToDecompose = basicGoal;
|
auto goalToDecompose = basicGoal;
|
||||||
Goals::TSubgoal elementarGoal = sptr(Goals::Invalid());
|
Goals::TSubgoal elementarGoal = sptr(Goals::Invalid());
|
||||||
int maxAbstractGoals = 10;
|
int maxAbstractGoals = 10;
|
||||||
@ -927,6 +933,8 @@ void VCAI::mainLoop()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logAi->trace("Main loop: selecting best elementar goal");
|
||||||
|
|
||||||
//now choose one elementar goal to realize
|
//now choose one elementar goal to realize
|
||||||
Goals::TGoalVec possibleGoals(elementarGoals.begin(), elementarGoals.end()); //copy to vector
|
Goals::TGoalVec possibleGoals(elementarGoals.begin(), elementarGoals.end()); //copy to vector
|
||||||
Goals::TSubgoal goalToRealize = sptr(Goals::Invalid());
|
Goals::TSubgoal goalToRealize = sptr(Goals::Invalid());
|
||||||
@ -975,6 +983,9 @@ void VCAI::mainLoop()
|
|||||||
completeGoal(e.goal);
|
completeGoal(e.goal);
|
||||||
//local goal was also completed?
|
//local goal was also completed?
|
||||||
completeGoal(goalToRealize);
|
completeGoal(goalToRealize);
|
||||||
|
|
||||||
|
// remove abstract visit tile if we completed the elementar one
|
||||||
|
vstd::erase_if_present(goalsToAdd, goalToRealize);
|
||||||
}
|
}
|
||||||
catch (std::exception & e)
|
catch (std::exception & e)
|
||||||
{
|
{
|
||||||
@ -1013,17 +1024,6 @@ void VCAI::mainLoop()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VCAI::goVisitObj(const CGObjectInstance * obj, HeroPtr h)
|
|
||||||
{
|
|
||||||
int3 dst = obj->visitablePos();
|
|
||||||
auto sm = getCachedSectorMap(h);
|
|
||||||
logAi->debug("%s will try to visit %s at (%s)", h->name, obj->getObjectName(), dst.toString());
|
|
||||||
int3 pos = sm->firstTileToGet(h, dst);
|
|
||||||
if(!pos.valid()) //rare case when we are already standing on one of potential objects
|
|
||||||
return false;
|
|
||||||
return moveHeroToTile(pos, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
|
void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
|
||||||
{
|
{
|
||||||
LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->name % obj->getObjectName() % obj->pos.toString());
|
LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->name % obj->getObjectName() % obj->pos.toString());
|
||||||
@ -1289,10 +1289,27 @@ void VCAI::recruitCreatures(const CGDwelling * d, const CArmedInstance * recruit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VCAI::isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, SectorMap & sm)
|
bool VCAI::isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, boost::optional<uint32_t> movementCostLimit)
|
||||||
|
{
|
||||||
|
int3 op = obj->visitablePos();
|
||||||
|
auto paths = ah->getPathsToTile(h, op);
|
||||||
|
|
||||||
|
for(auto path : paths)
|
||||||
|
{
|
||||||
|
if(movementCostLimit && movementCostLimit.get() < path.movementCost())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(ai->isGoodForVisit(obj, h, path))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VCAI::isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, const AIPath & path) const
|
||||||
{
|
{
|
||||||
const int3 pos = obj->visitablePos();
|
const int3 pos = obj->visitablePos();
|
||||||
const int3 targetPos = sm.firstTileToGet(h, pos);
|
const int3 targetPos = path.firstTileToGet();
|
||||||
if (!targetPos.valid())
|
if (!targetPos.valid())
|
||||||
return false;
|
return false;
|
||||||
if (!isTileNotReserved(h.get(), targetPos))
|
if (!isTileNotReserved(h.get(), targetPos))
|
||||||
@ -1309,8 +1326,11 @@ bool VCAI::isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, SectorMap & s
|
|||||||
return false;
|
return false;
|
||||||
if (vstd::contains(reservedObjs, obj))
|
if (vstd::contains(reservedObjs, obj))
|
||||||
return false;
|
return false;
|
||||||
if (!isAccessibleForHero(targetPos, h))
|
|
||||||
return false;
|
// TODO: looks extra if we already have AIPath
|
||||||
|
//if (!isAccessibleForHero(targetPos, h))
|
||||||
|
// return false;
|
||||||
|
|
||||||
const CGObjectInstance * topObj = cb->getVisitableObjs(obj->visitablePos()).back(); //it may be hero visiting this obj
|
const CGObjectInstance * topObj = cb->getVisitableObjs(obj->visitablePos()).back(); //it may be hero visiting this obj
|
||||||
//we don't try visiting object on which allied or owned hero stands
|
//we don't try visiting object on which allied or owned hero stands
|
||||||
// -> it will just trigger exchange windows and AI will be confused that obj behind doesn't get visited
|
// -> it will just trigger exchange windows and AI will be confused that obj behind doesn't get visited
|
||||||
@ -1320,12 +1340,14 @@ bool VCAI::isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, SectorMap & s
|
|||||||
return true; //all of the following is met
|
return true; //all of the following is met
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VCAI::isTileNotReserved(const CGHeroInstance * h, int3 t)
|
bool VCAI::isTileNotReserved(const CGHeroInstance * h, int3 t) const
|
||||||
{
|
{
|
||||||
if(t.valid())
|
if(t.valid())
|
||||||
{
|
{
|
||||||
auto obj = cb->getTopObj(t);
|
auto obj = cb->getTopObj(t);
|
||||||
if(obj && vstd::contains(ai->reservedObjs, obj) && !vstd::contains(reservedHeroesMap[h], obj))
|
if(obj && vstd::contains(ai->reservedObjs, obj)
|
||||||
|
&& vstd::contains(reservedHeroesMap, h)
|
||||||
|
&& !vstd::contains(reservedHeroesMap.at(h), obj))
|
||||||
return false; //do not capture object reserved by another hero
|
return false; //do not capture object reserved by another hero
|
||||||
else
|
else
|
||||||
return true;
|
return true;
|
||||||
@ -1380,47 +1402,38 @@ void VCAI::wander(HeroPtr h)
|
|||||||
{
|
{
|
||||||
validateVisitableObjs();
|
validateVisitableObjs();
|
||||||
std::vector<ObjectIdRef> dests;
|
std::vector<ObjectIdRef> dests;
|
||||||
|
|
||||||
auto sm = getCachedSectorMap(h);
|
|
||||||
|
|
||||||
//also visit our reserved objects - but they are not prioritized to avoid running back and forth
|
//also visit our reserved objects - but they are not prioritized to avoid running back and forth
|
||||||
vstd::copy_if(reservedHeroesMap[h], std::back_inserter(dests), [&](ObjectIdRef obj) -> bool
|
vstd::copy_if(reservedHeroesMap[h], std::back_inserter(dests), [&](ObjectIdRef obj) -> bool
|
||||||
{
|
{
|
||||||
int3 pos = sm->firstTileToGet(h, obj->visitablePos());
|
return ah->getPathsToTile(h, obj->visitablePos()).size();
|
||||||
if(pos.valid() && isAccessibleForHero(pos, h)) //even nearby objects could be blocked by other heroes :(
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
int pass = 0;
|
int pass = 0;
|
||||||
while(!dests.size() && pass < 3)
|
std::vector<boost::optional<ui32>> distanceLimits = {
|
||||||
|
h->movement,
|
||||||
|
h->movement + h->maxMovePoints(true),
|
||||||
|
boost::none
|
||||||
|
};
|
||||||
|
|
||||||
|
while(!dests.size() && pass < distanceLimits.size())
|
||||||
{
|
{
|
||||||
if(pass < 2) // optimization - first check objects in current sector; then in sectors around
|
boost::optional<ui32> distanceLimit = distanceLimits[pass];
|
||||||
|
|
||||||
|
logAi->debug("Looking for wander destination pass=%i, distance limit=%i", pass, distanceLimit.get_value_or(-1));
|
||||||
|
|
||||||
|
vstd::copy_if(visitableObjs, std::back_inserter(dests), [&](ObjectIdRef obj) -> bool
|
||||||
{
|
{
|
||||||
auto objs = sm->getNearbyObjs(h, pass);
|
return isGoodForVisit(obj, h, distanceLimit);
|
||||||
vstd::copy_if(objs, std::back_inserter(dests), [&](ObjectIdRef obj) -> bool
|
});
|
||||||
{
|
|
||||||
return isGoodForVisit(obj, h, *sm);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else // we only check full objects list if for some reason there are no objects in closest sectors
|
|
||||||
{
|
|
||||||
vstd::copy_if(visitableObjs, std::back_inserter(dests), [&](ObjectIdRef obj) -> bool
|
|
||||||
{
|
|
||||||
return isGoodForVisit(obj, h, *sm);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
pass++;
|
pass++;
|
||||||
}
|
}
|
||||||
|
|
||||||
vstd::erase_if(dests, [&](ObjectIdRef obj) -> bool
|
|
||||||
{
|
|
||||||
return !isSafeToVisit(h, sm->firstTileToGet(h, obj->visitablePos()));
|
|
||||||
});
|
|
||||||
|
|
||||||
if(!dests.size())
|
if(!dests.size())
|
||||||
{
|
{
|
||||||
|
logAi->debug("Looking for town destination");
|
||||||
|
|
||||||
if(cb->getVisitableObjs(h->visitablePos()).size() > 1)
|
if(cb->getVisitableObjs(h->visitablePos()).size() > 1)
|
||||||
moveHeroToTile(h->visitablePos(), h); //just in case we're standing on blocked subterranean gate
|
moveHeroToTile(h->visitablePos(), h); //just in case we're standing on blocked subterranean gate
|
||||||
|
|
||||||
@ -1497,10 +1510,10 @@ void VCAI::wander(HeroPtr h)
|
|||||||
Goals::TGoalVec targetObjectGoals;
|
Goals::TGoalVec targetObjectGoals;
|
||||||
for(auto destination : dests)
|
for(auto destination : dests)
|
||||||
{
|
{
|
||||||
targetObjectGoals.push_back(sptr(Goals::VisitObj(destination.id.getNum()).sethero(h).setisAbstract(true)));
|
vstd::concatenate(targetObjectGoals, ah->howToVisitObj(h, destination, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto bestObjectGoal = fh->chooseSolution(targetObjectGoals);
|
auto bestObjectGoal = fh->chooseSolution(targetObjectGoals);
|
||||||
decomposeGoal(bestObjectGoal)->accept(this);
|
|
||||||
|
|
||||||
//wander should not cause heroes to be reserved - they are always considered free
|
//wander should not cause heroes to be reserved - they are always considered free
|
||||||
if(bestObjectGoal->goalType == Goals::VISIT_OBJ)
|
if(bestObjectGoal->goalType == Goals::VISIT_OBJ)
|
||||||
@ -1510,7 +1523,19 @@ void VCAI::wander(HeroPtr h)
|
|||||||
logAi->debug("Of all %d destinations, object %s at pos=%s seems nice", dests.size(), chosenObject->getObjectName(), chosenObject->pos.toString());
|
logAi->debug("Of all %d destinations, object %s at pos=%s seems nice", dests.size(), chosenObject->getObjectName(), chosenObject->pos.toString());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
logAi->debug("Trying to realize goal of type %d as part of wandering.", bestObjectGoal->goalType);
|
logAi->debug("Trying to realize goal of type %s as part of wandering.", bestObjectGoal->name());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
decomposeGoal(bestObjectGoal)->accept(this);
|
||||||
|
}
|
||||||
|
catch(goalFulfilledException e)
|
||||||
|
{
|
||||||
|
if(e.goal->goalType == Goals::EGoals::VISIT_TILE || e.goal->goalType == Goals::EGoals::VISIT_OBJ)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
visitTownIfAny(h);
|
visitTownIfAny(h);
|
||||||
}
|
}
|
||||||
@ -1541,7 +1566,7 @@ void VCAI::completeGoal(Goals::TSubgoal goal)
|
|||||||
if (goal->goalType == Goals::WIN) //we can never complete this goal - unless we already won
|
if (goal->goalType == Goals::WIN) //we can never complete this goal - unless we already won
|
||||||
return;
|
return;
|
||||||
|
|
||||||
logAi->trace("Completing goal: %s", goal->name());
|
logAi->debug("Completing goal: %s", goal->name());
|
||||||
|
|
||||||
//notify Managers
|
//notify Managers
|
||||||
ah->notifyGoalCompleted(goal);
|
ah->notifyGoalCompleted(goal);
|
||||||
@ -1649,7 +1674,7 @@ bool VCAI::isAbleToExplore(HeroPtr h)
|
|||||||
void VCAI::clearPathsInfo()
|
void VCAI::clearPathsInfo()
|
||||||
{
|
{
|
||||||
heroesUnableToExplore.clear();
|
heroesUnableToExplore.clear();
|
||||||
cachedSectorMaps.clear();
|
ah->resetPaths();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VCAI::validateVisitableObjs()
|
void VCAI::validateVisitableObjs()
|
||||||
@ -1745,7 +1770,7 @@ const CGObjectInstance * VCAI::lookForArt(int aid) const
|
|||||||
//TODO what if more than one artifact is available? return them all or some slection criteria
|
//TODO what if more than one artifact is available? return them all or some slection criteria
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VCAI::isAccessible(const int3 & pos)
|
bool VCAI::isAccessible(const int3 & pos) const
|
||||||
{
|
{
|
||||||
//TODO precalculate for speed
|
//TODO precalculate for speed
|
||||||
|
|
||||||
@ -2182,7 +2207,7 @@ void VCAI::tryRealize(Goals::BuyArmy & g)
|
|||||||
});
|
});
|
||||||
|
|
||||||
vstd::amin(ci.count, res / ci.cre->cost); //max count we can afford
|
vstd::amin(ci.count, res / ci.cre->cost); //max count we can afford
|
||||||
if (ci.count > 0)
|
if (ci.count > 0 && t->getUpperArmy()->getSlotFor(ci.creID) != SlotID())
|
||||||
{
|
{
|
||||||
cb->recruitCreatures(t, t->getUpperArmy(), ci.creID, ci.count, ci.level);
|
cb->recruitCreatures(t, t->getUpperArmy(), ci.creID, ci.count, ci.level);
|
||||||
valueBought += ci.count * ci.cre->AIValue;
|
valueBought += ci.count * ci.cre->AIValue;
|
||||||
@ -2356,6 +2381,13 @@ void VCAI::striveToGoal(Goals::TSubgoal basicGoal)
|
|||||||
|
|
||||||
Goals::TSubgoal VCAI::decomposeGoal(Goals::TSubgoal ultimateGoal)
|
Goals::TSubgoal VCAI::decomposeGoal(Goals::TSubgoal ultimateGoal)
|
||||||
{
|
{
|
||||||
|
if(ultimateGoal->isElementar)
|
||||||
|
{
|
||||||
|
logAi->warn("Trying to decompose elementar goal %s", ultimateGoal->name());
|
||||||
|
|
||||||
|
return ultimateGoal;
|
||||||
|
}
|
||||||
|
|
||||||
const int searchDepth = 30;
|
const int searchDepth = 30;
|
||||||
const int searchDepth2 = searchDepth - 2;
|
const int searchDepth2 = searchDepth - 2;
|
||||||
Goals::TSubgoal abstractGoal = sptr(Goals::Invalid());
|
Goals::TSubgoal abstractGoal = sptr(Goals::Invalid());
|
||||||
@ -2529,7 +2561,7 @@ void VCAI::performTypicalActions()
|
|||||||
if(!h) //hero might be lost. getUnblockedHeroes() called once on start of turn
|
if(!h) //hero might be lost. getUnblockedHeroes() called once on start of turn
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
logAi->debug("Looking into %s, MP=%d", h->name.c_str(), h->movement);
|
logAi->debug("Hero %s started wandering, MP=%d", h->name.c_str(), h->movement);
|
||||||
makePossibleUpgrades(*h);
|
makePossibleUpgrades(*h);
|
||||||
pickBestArtifacts(*h);
|
pickBestArtifacts(*h);
|
||||||
try
|
try
|
||||||
@ -2644,7 +2676,6 @@ int3 VCAI::explorationNewPoint(HeroPtr h)
|
|||||||
|
|
||||||
int3 VCAI::explorationDesperate(HeroPtr h)
|
int3 VCAI::explorationDesperate(HeroPtr h)
|
||||||
{
|
{
|
||||||
auto sm = getCachedSectorMap(h);
|
|
||||||
int radius = h->getSightRadius();
|
int radius = h->getSightRadius();
|
||||||
|
|
||||||
std::vector<std::vector<int3>> tiles; //tiles[distance_to_fow]
|
std::vector<std::vector<int3>> tiles; //tiles[distance_to_fow]
|
||||||
@ -2673,15 +2704,20 @@ int3 VCAI::explorationDesperate(HeroPtr h)
|
|||||||
if(!howManyTilesWillBeDiscovered(tile, radius, cbp, h)) //avoid costly checks of tiles that don't reveal much
|
if(!howManyTilesWillBeDiscovered(tile, radius, cbp, h)) //avoid costly checks of tiles that don't reveal much
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
auto t = sm->firstTileToGet(h, tile);
|
auto paths = ah->getPathsToTile(h, tile);
|
||||||
if(t.valid())
|
for(auto path : paths)
|
||||||
{
|
{
|
||||||
|
auto t = path.firstTileToGet();
|
||||||
|
|
||||||
|
if(t == bestTile)
|
||||||
|
continue;
|
||||||
|
|
||||||
auto obj = cb->getTopObj(t);
|
auto obj = cb->getTopObj(t);
|
||||||
if (obj)
|
if (obj)
|
||||||
if (obj->blockVisit && !isObjectRemovable(obj)) //we can't stand on object or remove it
|
if (obj->blockVisit && !isObjectRemovable(obj)) //we can't stand on object or remove it
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
ui64 ourDanger = evaluateDanger(t, h.h);
|
ui64 ourDanger = path.getTotalDanger(h);
|
||||||
if(ourDanger < lowestDanger)
|
if(ourDanger < lowestDanger)
|
||||||
{
|
{
|
||||||
if(!ourDanger) //at least one safe place found
|
if(!ourDanger) //at least one safe place found
|
||||||
@ -2759,7 +2795,6 @@ void VCAI::lostHero(HeroPtr h)
|
|||||||
vstd::erase_if_present(reservedObjs, obj); //unreserve all objects for that hero
|
vstd::erase_if_present(reservedObjs, obj); //unreserve all objects for that hero
|
||||||
}
|
}
|
||||||
vstd::erase_if_present(reservedHeroesMap, h);
|
vstd::erase_if_present(reservedHeroesMap, h);
|
||||||
vstd::erase_if_present(cachedSectorMaps, h);
|
|
||||||
vstd::erase_if_present(visitedHeroes, h);
|
vstd::erase_if_present(visitedHeroes, h);
|
||||||
for (auto heroVec : visitedHeroes)
|
for (auto heroVec : visitedHeroes)
|
||||||
{
|
{
|
||||||
@ -2820,20 +2855,6 @@ void VCAI::validateObject(ObjectIdRef obj)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<SectorMap> VCAI::getCachedSectorMap(HeroPtr h)
|
|
||||||
{
|
|
||||||
auto it = cachedSectorMaps.find(h);
|
|
||||||
if(it != cachedSectorMaps.end())
|
|
||||||
{
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cachedSectorMaps[h] = std::make_shared<SectorMap>(h);
|
|
||||||
return cachedSectorMaps[h];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AIStatus::AIStatus()
|
AIStatus::AIStatus()
|
||||||
{
|
{
|
||||||
battle = NO_BATTLE;
|
battle = NO_BATTLE;
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include "../../lib/mapObjects/MiscObjects.h"
|
#include "../../lib/mapObjects/MiscObjects.h"
|
||||||
#include "../../lib/spells/CSpellHandler.h"
|
#include "../../lib/spells/CSpellHandler.h"
|
||||||
#include "../../lib/CondSh.h"
|
#include "../../lib/CondSh.h"
|
||||||
|
#include "Pathfinding/AIPathfinder.h"
|
||||||
|
|
||||||
struct QuestInfo;
|
struct QuestInfo;
|
||||||
|
|
||||||
@ -106,9 +107,6 @@ public:
|
|||||||
std::set<const CGObjectInstance *> reservedObjs; //to be visited by specific hero
|
std::set<const CGObjectInstance *> reservedObjs; //to be visited by specific hero
|
||||||
std::map<HeroPtr, std::set<HeroPtr>> visitedHeroes; //visited this turn //FIXME: this is just bug workaround
|
std::map<HeroPtr, std::set<HeroPtr>> visitedHeroes; //visited this turn //FIXME: this is just bug workaround
|
||||||
|
|
||||||
//TODO: move to separate PathHandler class?
|
|
||||||
std::map<HeroPtr, std::shared_ptr<SectorMap>> cachedSectorMaps; //TODO: serialize? not necessary
|
|
||||||
|
|
||||||
AIStatus status;
|
AIStatus status;
|
||||||
std::string battlename;
|
std::string battlename;
|
||||||
|
|
||||||
@ -137,7 +135,7 @@ public:
|
|||||||
int3 explorationBestNeighbour(int3 hpos, int radius, HeroPtr h);
|
int3 explorationBestNeighbour(int3 hpos, int radius, HeroPtr h);
|
||||||
int3 explorationNewPoint(HeroPtr h);
|
int3 explorationNewPoint(HeroPtr h);
|
||||||
int3 explorationDesperate(HeroPtr h);
|
int3 explorationDesperate(HeroPtr h);
|
||||||
bool isTileNotReserved(const CGHeroInstance * h, int3 t); //the tile is not occupied by allied hero and the object is not reserved
|
bool isTileNotReserved(const CGHeroInstance * h, int3 t) const; //the tile is not occupied by allied hero and the object is not reserved
|
||||||
|
|
||||||
std::string getBattleAIName() const override;
|
std::string getBattleAIName() const override;
|
||||||
|
|
||||||
@ -216,14 +214,14 @@ public:
|
|||||||
Goals::TSubgoal questToGoal(const QuestInfo & q);
|
Goals::TSubgoal questToGoal(const QuestInfo & q);
|
||||||
|
|
||||||
void recruitHero(const CGTownInstance * t, bool throwing = false);
|
void recruitHero(const CGTownInstance * t, bool throwing = false);
|
||||||
bool isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, SectorMap & sm);
|
bool isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, boost::optional<uint32_t> movementCostLimit = boost::none);
|
||||||
|
bool isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, const AIPath & path) const;
|
||||||
//void recruitCreatures(const CGTownInstance * t);
|
//void recruitCreatures(const CGTownInstance * t);
|
||||||
void recruitCreatures(const CGDwelling * d, const CArmedInstance * recruiter);
|
void recruitCreatures(const CGDwelling * d, const CArmedInstance * recruiter);
|
||||||
bool canGetArmy(const CGHeroInstance * h, const CGHeroInstance * source); //can we get any better stacks from other hero?
|
bool canGetArmy(const CGHeroInstance * h, const CGHeroInstance * source); //can we get any better stacks from other hero?
|
||||||
void pickBestCreatures(const CArmedInstance * army, const CArmedInstance * source); //called when we can't find a slot for new stack
|
void pickBestCreatures(const CArmedInstance * army, const CArmedInstance * source); //called when we can't find a slot for new stack
|
||||||
void pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance * other = nullptr);
|
void pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance * other = nullptr);
|
||||||
void moveCreaturesToHero(const CGTownInstance * t);
|
void moveCreaturesToHero(const CGTownInstance * t);
|
||||||
bool goVisitObj(const CGObjectInstance * obj, HeroPtr h);
|
|
||||||
void performObjectInteraction(const CGObjectInstance * obj, HeroPtr h);
|
void performObjectInteraction(const CGObjectInstance * obj, HeroPtr h);
|
||||||
|
|
||||||
bool moveHeroToTile(int3 dst, HeroPtr h);
|
bool moveHeroToTile(int3 dst, HeroPtr h);
|
||||||
@ -250,13 +248,12 @@ public:
|
|||||||
virtual std::vector<const CGObjectInstance *> getFlaggedObjects() const;
|
virtual std::vector<const CGObjectInstance *> getFlaggedObjects() const;
|
||||||
|
|
||||||
const CGObjectInstance * lookForArt(int aid) const;
|
const CGObjectInstance * lookForArt(int aid) const;
|
||||||
bool isAccessible(const int3 & pos);
|
bool isAccessible(const int3 & pos) const;
|
||||||
HeroPtr getHeroWithGrail() const;
|
HeroPtr getHeroWithGrail() const;
|
||||||
|
|
||||||
const CGObjectInstance * getUnvisitedObj(const std::function<bool(const CGObjectInstance *)> & predicate);
|
const CGObjectInstance * getUnvisitedObj(const std::function<bool(const CGObjectInstance *)> & predicate);
|
||||||
bool isAccessibleForHero(const int3 & pos, HeroPtr h, bool includeAllies = false) const;
|
bool isAccessibleForHero(const int3 & pos, HeroPtr h, bool includeAllies = false) const;
|
||||||
//optimization - use one SM for every hero call
|
//optimization - use one SM for every hero call
|
||||||
std::shared_ptr<SectorMap> getCachedSectorMap(HeroPtr h);
|
|
||||||
|
|
||||||
const CGTownInstance * findTownWithTavern() const;
|
const CGTownInstance * findTownWithTavern() const;
|
||||||
bool canRecruitAnyHero(const CGTownInstance * t = NULL) const;
|
bool canRecruitAnyHero(const CGTownInstance * t = NULL) const;
|
||||||
|
3
AUTHORS
3
AUTHORS
@ -72,3 +72,6 @@ Henning Koehler, <henning.koehler.nz@gmail.com>
|
|||||||
|
|
||||||
Andrzej Żak aka godric3
|
Andrzej Żak aka godric3
|
||||||
* minor bug fixes and modding features
|
* minor bug fixes and modding features
|
||||||
|
|
||||||
|
Andrii Danylchenko
|
||||||
|
* VCAI improvements
|
||||||
|
@ -25,6 +25,7 @@ class IShipyard;
|
|||||||
struct CGPathNode;
|
struct CGPathNode;
|
||||||
struct CGPath;
|
struct CGPath;
|
||||||
struct CPathsInfo;
|
struct CPathsInfo;
|
||||||
|
class PathfinderConfig;
|
||||||
struct CPack;
|
struct CPack;
|
||||||
class IBattleEventsReceiver;
|
class IBattleEventsReceiver;
|
||||||
class IGameEventsReceiver;
|
class IGameEventsReceiver;
|
||||||
|
@ -919,6 +919,11 @@ void CGameInfoCallback::getVisibleTilesInRange(std::unordered_set<int3, ShashInt
|
|||||||
gs->getTilesInRange(tiles, pos, radious, getLocalPlayer(), -1, distanceFormula);
|
gs->getTilesInRange(tiles, pos, radious, getLocalPlayer(), -1, distanceFormula);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CGameInfoCallback::calculatePaths(std::shared_ptr<PathfinderConfig> config, const CGHeroInstance * hero)
|
||||||
|
{
|
||||||
|
gs->calculatePaths(config, hero);
|
||||||
|
}
|
||||||
|
|
||||||
const CArtifactInstance * CGameInfoCallback::getArtInstance( ArtifactInstanceID aid ) const
|
const CArtifactInstance * CGameInfoCallback::getArtInstance( ArtifactInstanceID aid ) const
|
||||||
{
|
{
|
||||||
return gs->map->artInstances[aid.num];
|
return gs->map->artInstances[aid.num];
|
||||||
|
@ -31,6 +31,7 @@ struct TeamState;
|
|||||||
struct QuestInfo;
|
struct QuestInfo;
|
||||||
struct ShashInt3;
|
struct ShashInt3;
|
||||||
class CGameState;
|
class CGameState;
|
||||||
|
class PathfinderConfig;
|
||||||
|
|
||||||
|
|
||||||
class DLL_LINKAGE CGameInfoCallback : public virtual CCallbackBase
|
class DLL_LINKAGE CGameInfoCallback : public virtual CCallbackBase
|
||||||
@ -98,6 +99,7 @@ public:
|
|||||||
virtual std::shared_ptr<boost::multi_array<TerrainTile*, 3>> getAllVisibleTiles() const;
|
virtual std::shared_ptr<boost::multi_array<TerrainTile*, 3>> getAllVisibleTiles() const;
|
||||||
virtual bool isInTheMap(const int3 &pos) const;
|
virtual bool isInTheMap(const int3 &pos) const;
|
||||||
virtual void getVisibleTilesInRange(std::unordered_set<int3, ShashInt3> &tiles, int3 pos, int radious, int3::EDistanceFormula distanceFormula = int3::DIST_2D) const;
|
virtual void getVisibleTilesInRange(std::unordered_set<int3, ShashInt3> &tiles, int3 pos, int radious, int3::EDistanceFormula distanceFormula = int3::DIST_2D) const;
|
||||||
|
virtual void calculatePaths(std::shared_ptr<PathfinderConfig> config, const CGHeroInstance * hero);
|
||||||
|
|
||||||
//town
|
//town
|
||||||
virtual const CGTownInstance* getTown(ObjectInstanceID objid) const;
|
virtual const CGTownInstance* getTown(ObjectInstanceID objid) const;
|
||||||
|
@ -1979,6 +1979,12 @@ void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out)
|
|||||||
pathfinder.calculatePaths();
|
pathfinder.calculatePaths();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CGameState::calculatePaths(std::shared_ptr<PathfinderConfig> config, const CGHeroInstance * hero)
|
||||||
|
{
|
||||||
|
CPathfinder pathfinder(this, hero, config);
|
||||||
|
pathfinder.calculatePaths();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tells if the tile is guarded by a monster as well as the position
|
* Tells if the tile is guarded by a monster as well as the position
|
||||||
* of the monster that will attack on it.
|
* of the monster that will attack on it.
|
||||||
|
@ -178,6 +178,7 @@ public:
|
|||||||
PlayerRelations::PlayerRelations getPlayerRelations(PlayerColor color1, PlayerColor color2);
|
PlayerRelations::PlayerRelations getPlayerRelations(PlayerColor color1, PlayerColor color2);
|
||||||
bool checkForVisitableDir(const int3 & src, const int3 & dst) const; //check if src tile is visitable from dst tile
|
bool checkForVisitableDir(const int3 & src, const int3 & dst) const; //check if src tile is visitable from dst tile
|
||||||
void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out); //calculates possible paths for hero, by default uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists
|
void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out); //calculates possible paths for hero, by default uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists
|
||||||
|
void calculatePaths(std::shared_ptr<PathfinderConfig> config, const CGHeroInstance * hero);
|
||||||
int3 guardingCreaturePosition (int3 pos) const;
|
int3 guardingCreaturePosition (int3 pos) const;
|
||||||
std::vector<CGObjectInstance*> guardingCreatures (int3 pos) const;
|
std::vector<CGObjectInstance*> guardingCreatures (int3 pos) const;
|
||||||
void updateRumor();
|
void updateRumor();
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -22,6 +22,9 @@ struct TerrainTile;
|
|||||||
class CPathfinderHelper;
|
class CPathfinderHelper;
|
||||||
class CMap;
|
class CMap;
|
||||||
class CGWhirlpool;
|
class CGWhirlpool;
|
||||||
|
class CPathfinderHelper;
|
||||||
|
class CPathfinder;
|
||||||
|
class PathfinderConfig;
|
||||||
|
|
||||||
struct DLL_LINKAGE CGPathNode
|
struct DLL_LINKAGE CGPathNode
|
||||||
{
|
{
|
||||||
@ -96,72 +99,269 @@ struct DLL_LINKAGE CPathsInfo
|
|||||||
CGPathNode * getNode(const int3 & coord, const ELayer layer);
|
CGPathNode * getNode(const int3 & coord, const ELayer layer);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct DLL_LINKAGE PathNodeInfo
|
||||||
|
{
|
||||||
|
CGPathNode * node;
|
||||||
|
const CGObjectInstance * nodeObject;
|
||||||
|
const TerrainTile * tile;
|
||||||
|
int3 coord;
|
||||||
|
bool guarded;
|
||||||
|
PlayerRelations::PlayerRelations objectRelations;
|
||||||
|
|
||||||
|
PathNodeInfo();
|
||||||
|
|
||||||
|
virtual void setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject = false);
|
||||||
|
|
||||||
|
bool isNodeObjectVisitable() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DLL_LINKAGE CDestinationNodeInfo : public PathNodeInfo
|
||||||
|
{
|
||||||
|
CGPathNode::ENodeAction action;
|
||||||
|
int turn;
|
||||||
|
int movementLeft;
|
||||||
|
bool blocked;
|
||||||
|
bool isGuardianTile;
|
||||||
|
|
||||||
|
CDestinationNodeInfo();
|
||||||
|
|
||||||
|
virtual void setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject = false) override;
|
||||||
|
|
||||||
|
virtual bool isBetterWay() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IPathfindingRule
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void process(
|
||||||
|
const PathNodeInfo & source,
|
||||||
|
CDestinationNodeInfo & destination,
|
||||||
|
const PathfinderConfig * pathfinderConfig,
|
||||||
|
CPathfinderHelper * pathfinderHelper) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DLL_LINKAGE MovementCostRule : public IPathfindingRule
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void process(
|
||||||
|
const PathNodeInfo & source,
|
||||||
|
CDestinationNodeInfo & destination,
|
||||||
|
const PathfinderConfig * pathfinderConfig,
|
||||||
|
CPathfinderHelper * pathfinderHelper) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DLL_LINKAGE LayerTransitionRule : public IPathfindingRule
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void process(
|
||||||
|
const PathNodeInfo & source,
|
||||||
|
CDestinationNodeInfo & destination,
|
||||||
|
const PathfinderConfig * pathfinderConfig,
|
||||||
|
CPathfinderHelper * pathfinderHelper) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DLL_LINKAGE DestinationActionRule : public IPathfindingRule
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void process(
|
||||||
|
const PathNodeInfo & source,
|
||||||
|
CDestinationNodeInfo & destination,
|
||||||
|
const PathfinderConfig * pathfinderConfig,
|
||||||
|
CPathfinderHelper * pathfinderHelper) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DLL_LINKAGE PathfinderBlockingRule : public IPathfindingRule
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void process(
|
||||||
|
const PathNodeInfo & source,
|
||||||
|
CDestinationNodeInfo & destination,
|
||||||
|
const PathfinderConfig * pathfinderConfig,
|
||||||
|
CPathfinderHelper * pathfinderHelper) const override
|
||||||
|
{
|
||||||
|
auto blockingReason = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper);
|
||||||
|
|
||||||
|
destination.blocked = blockingReason != BlockingReason::NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
enum class BlockingReason
|
||||||
|
{
|
||||||
|
NONE = 0,
|
||||||
|
SOURCE_GUARDED = 1,
|
||||||
|
DESTINATION_GUARDED = 2,
|
||||||
|
SOURCE_BLOCKED = 3,
|
||||||
|
DESTINATION_BLOCKED = 4,
|
||||||
|
DESTINATION_BLOCKVIS = 5,
|
||||||
|
DESTINATION_VISIT = 6
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual BlockingReason getBlockingReason(
|
||||||
|
const PathNodeInfo & source,
|
||||||
|
const CDestinationNodeInfo & destination,
|
||||||
|
const PathfinderConfig * pathfinderConfig,
|
||||||
|
const CPathfinderHelper * pathfinderHelper) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DLL_LINKAGE MovementAfterDestinationRule : public PathfinderBlockingRule
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void process(
|
||||||
|
const PathNodeInfo & source,
|
||||||
|
CDestinationNodeInfo & destination,
|
||||||
|
const PathfinderConfig * pathfinderConfig,
|
||||||
|
CPathfinderHelper * pathfinderHelper) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual BlockingReason getBlockingReason(
|
||||||
|
const PathNodeInfo & source,
|
||||||
|
const CDestinationNodeInfo & destination,
|
||||||
|
const PathfinderConfig * pathfinderConfig,
|
||||||
|
const CPathfinderHelper * pathfinderHelper) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DLL_LINKAGE MovementToDestinationRule : public PathfinderBlockingRule
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
virtual BlockingReason getBlockingReason(
|
||||||
|
const PathNodeInfo & source,
|
||||||
|
const CDestinationNodeInfo & destination,
|
||||||
|
const PathfinderConfig * pathfinderConfig,
|
||||||
|
const CPathfinderHelper * pathfinderHelper) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class INodeStorage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual CGPathNode * getInitialNode() = 0;
|
||||||
|
virtual void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility) = 0;
|
||||||
|
|
||||||
|
virtual std::vector<CGPathNode *> calculateNeighbours(
|
||||||
|
const PathNodeInfo & source,
|
||||||
|
const PathfinderConfig * pathfinderConfig,
|
||||||
|
const CPathfinderHelper * pathfinderHelper) = 0;
|
||||||
|
|
||||||
|
virtual std::vector<CGPathNode *> calculateTeleportations(
|
||||||
|
const PathNodeInfo & source,
|
||||||
|
const PathfinderConfig * pathfinderConfig,
|
||||||
|
const CPathfinderHelper * pathfinderHelper) = 0;
|
||||||
|
|
||||||
|
virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DLL_LINKAGE NodeStorage : public INodeStorage
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
CPathsInfo & out;
|
||||||
|
|
||||||
|
public:
|
||||||
|
NodeStorage(CPathsInfo & pathsInfo, const CGHeroInstance * hero);
|
||||||
|
|
||||||
|
CGPathNode * getNode(const int3 & coord, const EPathfindingLayer layer);
|
||||||
|
virtual CGPathNode * getInitialNode() override;
|
||||||
|
|
||||||
|
virtual std::vector<CGPathNode *> calculateNeighbours(
|
||||||
|
const PathNodeInfo & source,
|
||||||
|
const PathfinderConfig * pathfinderConfig,
|
||||||
|
const CPathfinderHelper * pathfinderHelper) override;
|
||||||
|
|
||||||
|
virtual std::vector<CGPathNode *> calculateTeleportations(
|
||||||
|
const PathNodeInfo & source,
|
||||||
|
const PathfinderConfig * pathfinderConfig,
|
||||||
|
const CPathfinderHelper * pathfinderHelper) override;
|
||||||
|
|
||||||
|
virtual void resetTile(
|
||||||
|
const int3 & tile,
|
||||||
|
EPathfindingLayer layer,
|
||||||
|
CGPathNode::EAccessibility accessibility) override;
|
||||||
|
|
||||||
|
virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DLL_LINKAGE PathfinderOptions
|
||||||
|
{
|
||||||
|
bool useFlying;
|
||||||
|
bool useWaterWalking;
|
||||||
|
bool useEmbarkAndDisembark;
|
||||||
|
bool useTeleportTwoWay; // Two-way monoliths and Subterranean Gate
|
||||||
|
bool useTeleportOneWay; // One-way monoliths with one known exit only
|
||||||
|
bool useTeleportOneWayRandom; // One-way monoliths with more than one known exit
|
||||||
|
bool useTeleportWhirlpool; // Force enabled if hero protected or unaffected (have one stack of one creature)
|
||||||
|
|
||||||
|
/// TODO: Find out with client and server code, merge with normal teleporters.
|
||||||
|
/// Likely proper implementation would require some refactoring of CGTeleport.
|
||||||
|
/// So for now this is unfinished and disabled by default.
|
||||||
|
bool useCastleGate;
|
||||||
|
|
||||||
|
/// If true transition into air layer only possible from initial node.
|
||||||
|
/// This is drastically decrease path calculation complexity (and time).
|
||||||
|
/// Downside is less MP effective paths calculation.
|
||||||
|
///
|
||||||
|
/// TODO: If this option end up useful for slow devices it's can be improved:
|
||||||
|
/// - Allow transition into air layer not only from initial position, but also from teleporters.
|
||||||
|
/// Movement into air can be also allowed when hero disembarked.
|
||||||
|
/// - Other idea is to allow transition into air within certain radius of N tiles around hero.
|
||||||
|
/// Patrol support need similar functionality so it's won't be ton of useless code.
|
||||||
|
/// Such limitation could be useful as it's can be scaled depend on device performance.
|
||||||
|
bool lightweightFlyingMode;
|
||||||
|
|
||||||
|
/// This option enable one turn limitation for flying and water walking.
|
||||||
|
/// So if we're out of MP while cp is blocked or water tile we won't add dest tile to queue.
|
||||||
|
///
|
||||||
|
/// Following imitation is default H3 mechanics, but someone may want to disable it in mods.
|
||||||
|
/// After all this limit should benefit performance on maps with tons of water or blocked tiles.
|
||||||
|
///
|
||||||
|
/// TODO:
|
||||||
|
/// - Behavior when option is disabled not implemented and will lead to crashes.
|
||||||
|
bool oneTurnSpecialLayersLimit;
|
||||||
|
|
||||||
|
/// VCMI have different movement rules to solve flaws original engine has.
|
||||||
|
/// If this option enabled you'll able to do following things in fly:
|
||||||
|
/// - Move from blocked tiles to visitable one
|
||||||
|
/// - Move from guarded tiles to blockvis tiles without being attacked
|
||||||
|
/// - Move from guarded tiles to guarded visitable tiles with being attacked after
|
||||||
|
/// TODO:
|
||||||
|
/// - Option should also allow same tile land <-> air layer transitions.
|
||||||
|
/// Current implementation only allow go into (from) air layer only to neighbour tiles.
|
||||||
|
/// I find it's reasonable limitation, but it's will make some movements more expensive than in H3.
|
||||||
|
bool originalMovementRules;
|
||||||
|
|
||||||
|
PathfinderOptions();
|
||||||
|
};
|
||||||
|
|
||||||
|
class DLL_LINKAGE PathfinderConfig
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::shared_ptr<INodeStorage> nodeStorage;
|
||||||
|
std::vector<std::shared_ptr<IPathfindingRule>> rules;
|
||||||
|
PathfinderOptions options;
|
||||||
|
|
||||||
|
PathfinderConfig(
|
||||||
|
std::shared_ptr<INodeStorage> nodeStorage,
|
||||||
|
std::vector<std::shared_ptr<IPathfindingRule>> rules);
|
||||||
|
};
|
||||||
|
|
||||||
class CPathfinder : private CGameInfoCallback
|
class CPathfinder : private CGameInfoCallback
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
friend class CPathfinderHelper;
|
friend class CPathfinderHelper;
|
||||||
|
|
||||||
CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstance * _hero);
|
CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstance * _hero);
|
||||||
|
CPathfinder(
|
||||||
|
CGameState * _gs,
|
||||||
|
const CGHeroInstance * _hero,
|
||||||
|
std::shared_ptr<PathfinderConfig> config);
|
||||||
|
|
||||||
void calculatePaths(); //calculates possible paths for hero, uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists
|
void calculatePaths(); //calculates possible paths for hero, uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists
|
||||||
|
|
||||||
private:
|
private:
|
||||||
typedef EPathfindingLayer ELayer;
|
typedef EPathfindingLayer ELayer;
|
||||||
|
|
||||||
struct PathfinderOptions
|
|
||||||
{
|
|
||||||
bool useFlying;
|
|
||||||
bool useWaterWalking;
|
|
||||||
bool useEmbarkAndDisembark;
|
|
||||||
bool useTeleportTwoWay; // Two-way monoliths and Subterranean Gate
|
|
||||||
bool useTeleportOneWay; // One-way monoliths with one known exit only
|
|
||||||
bool useTeleportOneWayRandom; // One-way monoliths with more than one known exit
|
|
||||||
bool useTeleportWhirlpool; // Force enabled if hero protected or unaffected (have one stack of one creature)
|
|
||||||
|
|
||||||
/// TODO: Find out with client and server code, merge with normal teleporters.
|
|
||||||
/// Likely proper implementation would require some refactoring of CGTeleport.
|
|
||||||
/// So for now this is unfinished and disabled by default.
|
|
||||||
bool useCastleGate;
|
|
||||||
|
|
||||||
/// If true transition into air layer only possible from initial node.
|
|
||||||
/// This is drastically decrease path calculation complexity (and time).
|
|
||||||
/// Downside is less MP effective paths calculation.
|
|
||||||
///
|
|
||||||
/// TODO: If this option end up useful for slow devices it's can be improved:
|
|
||||||
/// - Allow transition into air layer not only from initial position, but also from teleporters.
|
|
||||||
/// Movement into air can be also allowed when hero disembarked.
|
|
||||||
/// - Other idea is to allow transition into air within certain radius of N tiles around hero.
|
|
||||||
/// Patrol support need similar functionality so it's won't be ton of useless code.
|
|
||||||
/// Such limitation could be useful as it's can be scaled depend on device performance.
|
|
||||||
bool lightweightFlyingMode;
|
|
||||||
|
|
||||||
/// This option enable one turn limitation for flying and water walking.
|
|
||||||
/// So if we're out of MP while cp is blocked or water tile we won't add dest tile to queue.
|
|
||||||
///
|
|
||||||
/// Following imitation is default H3 mechanics, but someone may want to disable it in mods.
|
|
||||||
/// After all this limit should benefit performance on maps with tons of water or blocked tiles.
|
|
||||||
///
|
|
||||||
/// TODO:
|
|
||||||
/// - Behavior when option is disabled not implemented and will lead to crashes.
|
|
||||||
bool oneTurnSpecialLayersLimit;
|
|
||||||
|
|
||||||
/// VCMI have different movement rules to solve flaws original engine has.
|
|
||||||
/// If this option enabled you'll able to do following things in fly:
|
|
||||||
/// - Move from blocked tiles to visitable one
|
|
||||||
/// - Move from guarded tiles to blockvis tiles without being attacked
|
|
||||||
/// - Move from guarded tiles to guarded visitable tiles with being attacked after
|
|
||||||
/// TODO:
|
|
||||||
/// - Option should also allow same tile land <-> air layer transitions.
|
|
||||||
/// Current implementation only allow go into (from) air layer only to neighbour tiles.
|
|
||||||
/// I find it's reasonable limitation, but it's will make some movements more expensive than in H3.
|
|
||||||
bool originalMovementRules;
|
|
||||||
|
|
||||||
PathfinderOptions();
|
|
||||||
} options;
|
|
||||||
|
|
||||||
CPathsInfo & out;
|
|
||||||
const CGHeroInstance * hero;
|
const CGHeroInstance * hero;
|
||||||
const std::vector<std::vector<std::vector<ui8> > > &FoW;
|
const std::vector<std::vector<std::vector<ui8> > > &FoW;
|
||||||
std::unique_ptr<CPathfinderHelper> hlp;
|
std::unique_ptr<CPathfinderHelper> hlp;
|
||||||
|
std::shared_ptr<PathfinderConfig> config;
|
||||||
|
|
||||||
enum EPatrolState {
|
enum EPatrolState {
|
||||||
PATROL_NONE = 0,
|
PATROL_NONE = 0,
|
||||||
@ -184,49 +384,24 @@ private:
|
|||||||
};
|
};
|
||||||
boost::heap::priority_queue<CGPathNode *, boost::heap::compare<NodeComparer> > pq;
|
boost::heap::priority_queue<CGPathNode *, boost::heap::compare<NodeComparer> > pq;
|
||||||
|
|
||||||
std::vector<int3> neighbourTiles;
|
PathNodeInfo source; //current (source) path node -> we took it from the queue
|
||||||
std::vector<int3> neighbours;
|
CDestinationNodeInfo destination; //destination node -> it's a neighbour of source that we consider
|
||||||
|
|
||||||
CGPathNode * cp; //current (source) path node -> we took it from the queue
|
|
||||||
CGPathNode * dp; //destination node -> it's a neighbour of cp that we consider
|
|
||||||
const TerrainTile * ct, * dt; //tile info for both nodes
|
|
||||||
const CGObjectInstance * ctObj, * dtObj;
|
|
||||||
CGPathNode::ENodeAction destAction;
|
|
||||||
|
|
||||||
void addNeighbours();
|
|
||||||
void addTeleportExits();
|
|
||||||
|
|
||||||
bool isHeroPatrolLocked() const;
|
bool isHeroPatrolLocked() const;
|
||||||
bool isPatrolMovementAllowed(const int3 & dst) const;
|
bool isPatrolMovementAllowed(const int3 & dst) const;
|
||||||
|
|
||||||
bool isLayerTransitionPossible(const ELayer dstLayer) const;
|
bool isLayerTransitionPossible(const ELayer dstLayer) const;
|
||||||
bool isLayerTransitionPossible() const;
|
|
||||||
bool isMovementToDestPossible() const;
|
|
||||||
bool isMovementAfterDestPossible() const;
|
|
||||||
CGPathNode::ENodeAction getDestAction() const;
|
|
||||||
CGPathNode::ENodeAction getTeleportDestAction() const;
|
CGPathNode::ENodeAction getTeleportDestAction() const;
|
||||||
|
|
||||||
bool isSourceInitialPosition() const;
|
bool isSourceInitialPosition() const;
|
||||||
bool isSourceVisitableObj() const;
|
|
||||||
bool isSourceGuarded() const;
|
bool isSourceGuarded() const;
|
||||||
bool isDestVisitableObj() const;
|
bool isDestinationGuarded() const;
|
||||||
bool isDestinationGuarded(const bool ignoreAccessibility = true) const;
|
|
||||||
bool isDestinationGuardian() const;
|
bool isDestinationGuardian() const;
|
||||||
|
|
||||||
void initializePatrol();
|
void initializePatrol();
|
||||||
void initializeGraph();
|
void initializeGraph();
|
||||||
|
|
||||||
CGPathNode::EAccessibility evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo, const ELayer layer) const;
|
CGPathNode::EAccessibility evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo, const ELayer layer) const;
|
||||||
bool isVisitableObj(const CGObjectInstance * obj, const ELayer layer) const;
|
|
||||||
bool canSeeObj(const CGObjectInstance * obj) const;
|
|
||||||
bool canMoveBetween(const int3 & a, const int3 & b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility)
|
|
||||||
|
|
||||||
bool isAllowedTeleportEntrance(const CGTeleport * obj) const;
|
|
||||||
bool addTeleportTwoWay(const CGTeleport * obj) const;
|
|
||||||
bool addTeleportOneWay(const CGTeleport * obj) const;
|
|
||||||
bool addTeleportOneWayRandom(const CGTeleport * obj) const;
|
|
||||||
bool addTeleportWhirlpool(const CGWhirlpool * obj) const;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DLL_LINKAGE TurnInfo
|
struct DLL_LINKAGE TurnInfo
|
||||||
@ -258,10 +433,15 @@ struct DLL_LINKAGE TurnInfo
|
|||||||
int getMaxMovePoints(const EPathfindingLayer layer) const;
|
int getMaxMovePoints(const EPathfindingLayer layer) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DLL_LINKAGE CPathfinderHelper
|
class DLL_LINKAGE CPathfinderHelper : private CGameInfoCallback
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CPathfinderHelper(const CGHeroInstance * Hero, const CPathfinder::PathfinderOptions & Options);
|
int turn;
|
||||||
|
const CGHeroInstance * hero;
|
||||||
|
std::vector<TurnInfo *> turnsInfo;
|
||||||
|
const PathfinderOptions & options;
|
||||||
|
|
||||||
|
CPathfinderHelper(CGameState * gs, const CGHeroInstance * Hero, const PathfinderOptions & Options);
|
||||||
~CPathfinderHelper();
|
~CPathfinderHelper();
|
||||||
void updateTurnInfo(const int turn = 0);
|
void updateTurnInfo(const int turn = 0);
|
||||||
bool isLayerAvailable(const EPathfindingLayer layer) const;
|
bool isLayerAvailable(const EPathfindingLayer layer) const;
|
||||||
@ -269,14 +449,50 @@ public:
|
|||||||
bool hasBonusOfType(const Bonus::BonusType type, const int subtype = -1) const;
|
bool hasBonusOfType(const Bonus::BonusType type, const int subtype = -1) const;
|
||||||
int getMaxMovePoints(const EPathfindingLayer layer) const;
|
int getMaxMovePoints(const EPathfindingLayer layer) const;
|
||||||
|
|
||||||
static void getNeighbours(const CMap * map, const TerrainTile & srct, const int3 & tile, std::vector<int3> & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing);
|
std::vector<int3> getCastleGates(const PathNodeInfo & source) const;
|
||||||
|
bool isAllowedTeleportEntrance(const CGTeleport * obj) const;
|
||||||
|
std::vector<int3> getAllowedTeleportChannelExits(TeleportChannelID channelID) const;
|
||||||
|
bool addTeleportTwoWay(const CGTeleport * obj) const;
|
||||||
|
bool addTeleportOneWay(const CGTeleport * obj) const;
|
||||||
|
bool addTeleportOneWayRandom(const CGTeleport * obj) const;
|
||||||
|
bool addTeleportWhirlpool(const CGWhirlpool * obj) const;
|
||||||
|
bool canMoveBetween(const int3 & a, const int3 & b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility)
|
||||||
|
|
||||||
static int getMovementCost(const CGHeroInstance * h, const int3 & src, const int3 & dst, const TerrainTile * ct, const TerrainTile * dt, const int remainingMovePoints =- 1, const TurnInfo * ti = nullptr, const bool checkLast = true);
|
std::vector<int3> getNeighbourTiles(const PathNodeInfo & source) const;
|
||||||
static int getMovementCost(const CGHeroInstance * h, const int3 & dst);
|
std::vector<int3> getTeleportExits(const PathNodeInfo & source) const;
|
||||||
|
|
||||||
private:
|
void getNeighbours(
|
||||||
int turn;
|
const TerrainTile & srct,
|
||||||
const CGHeroInstance * hero;
|
const int3 & tile,
|
||||||
std::vector<TurnInfo *> turnsInfo;
|
std::vector<int3> & vec,
|
||||||
const CPathfinder::PathfinderOptions & options;
|
const boost::logic::tribool & onLand,
|
||||||
|
const bool limitCoastSailing) const;
|
||||||
|
|
||||||
|
int getMovementCost(
|
||||||
|
const int3 & src,
|
||||||
|
const int3 & dst,
|
||||||
|
const TerrainTile * ct,
|
||||||
|
const TerrainTile * dt,
|
||||||
|
const int remainingMovePoints =- 1,
|
||||||
|
const bool checkLast = true) const;
|
||||||
|
|
||||||
|
int getMovementCost(
|
||||||
|
const PathNodeInfo & src,
|
||||||
|
const PathNodeInfo & dst,
|
||||||
|
const int remainingMovePoints = -1,
|
||||||
|
const bool checkLast = true) const
|
||||||
|
{
|
||||||
|
return getMovementCost(
|
||||||
|
src.coord,
|
||||||
|
dst.coord,
|
||||||
|
src.tile,
|
||||||
|
dst.tile,
|
||||||
|
remainingMovePoints,
|
||||||
|
checkLast
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getHeroMaxMovementPoints(EPathfindingLayer layer) const;
|
||||||
|
int movementPointsAfterEmbark(int movement, int cost, int action) const;
|
||||||
|
bool passOneTurnLimitCheck(const PathNodeInfo & source) const;
|
||||||
};
|
};
|
||||||
|
@ -2158,10 +2158,14 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
|
|||||||
tmh.movePoints = h->movement;
|
tmh.movePoints = h->movement;
|
||||||
|
|
||||||
//check if destination tile is available
|
//check if destination tile is available
|
||||||
auto ti = make_unique<TurnInfo>(h);
|
auto pathfinderHelper = make_unique<CPathfinderHelper>(gs, h, PathfinderOptions());
|
||||||
const bool canFly = ti->hasBonusOfType(Bonus::FLYING_MOVEMENT);
|
|
||||||
const bool canWalkOnSea = ti->hasBonusOfType(Bonus::WATER_WALKING);
|
pathfinderHelper->updateTurnInfo(0);
|
||||||
const int cost = CPathfinderHelper::getMovementCost(h, h->getPosition(), hmpos, nullptr, nullptr, h->movement, ti.get());
|
auto ti = pathfinderHelper->getTurnInfo();
|
||||||
|
|
||||||
|
const bool canFly = pathfinderHelper->hasBonusOfType(Bonus::FLYING_MOVEMENT);
|
||||||
|
const bool canWalkOnSea = pathfinderHelper->hasBonusOfType(Bonus::WATER_WALKING);
|
||||||
|
const int cost = pathfinderHelper->getMovementCost(h->getPosition(), hmpos, nullptr, nullptr, h->movement);
|
||||||
|
|
||||||
//it's a rock or blocked and not visitable tile
|
//it's a rock or blocked and not visitable tile
|
||||||
//OR hero is on land and dest is water and (there is not present only one object - boat)
|
//OR hero is on land and dest is water and (there is not present only one object - boat)
|
||||||
@ -2253,14 +2257,14 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
|
|||||||
|
|
||||||
if (!transit && embarking)
|
if (!transit && embarking)
|
||||||
{
|
{
|
||||||
tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, false, ti.get());
|
tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, false, ti);
|
||||||
return doMove(TryMoveHero::EMBARK, IGNORE_GUARDS, DONT_VISIT_DEST, LEAVING_TILE);
|
return doMove(TryMoveHero::EMBARK, IGNORE_GUARDS, DONT_VISIT_DEST, LEAVING_TILE);
|
||||||
// In H3 embark ignore guards
|
// In H3 embark ignore guards
|
||||||
}
|
}
|
||||||
|
|
||||||
if (disembarking)
|
if (disembarking)
|
||||||
{
|
{
|
||||||
tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, true, ti.get());
|
tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, true, ti);
|
||||||
return doMove(TryMoveHero::DISEMBARK, CHECK_FOR_GUARDS, VISIT_DEST, LEAVING_TILE);
|
return doMove(TryMoveHero::DISEMBARK, CHECK_FOR_GUARDS, VISIT_DEST, LEAVING_TILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user