1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

AI: replace SectorMap with new PathfinderManager

This commit is contained in:
Andrii Danylchenko 2018-08-12 14:31:31 +03:00
parent eb17313f7f
commit 49c872e4ec
28 changed files with 1449 additions and 418 deletions

@ -1 +1 @@
Subproject commit 42c30e157e999b45aee9b53bb849bb6ec3a2be22
Subproject commit 9751a751a17c0682ed5d02e583c6a0cda8bc88e5

View File

@ -36,6 +36,11 @@ ObjectIdRef::operator const CGObjectInstance *() const
return cb->getObj(id, false);
}
ObjectIdRef::operator bool() const
{
return cb->getObj(id, false);
}
ObjectIdRef::ObjectIdRef(ObjectInstanceID _id)
: id(_id)
{
@ -338,14 +343,18 @@ bool compareDanger(const CGObjectInstance * lhs, const CGObjectInstance * rhs)
}
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 dangerStrength = evaluateDanger(tile, *h);
if(dangerStrength)
{
if(heroStrength / SAFE_ATTACK_CONSTANT > dangerStrength)
{
logAi->trace("It's safe for %s to visit tile %s", h->name, tile.toString());
return true;
}
else
@ -398,11 +407,11 @@ int3 whereToExplore(HeroPtr h)
TimeCheck tc("where to explore");
int radius = h->getSightRadius();
int3 hpos = h->visitablePos();
auto sm = ai->getCachedSectorMap(h);
//look for nearby objs -> visit them if they're close enouh
const int DIST_LIMIT = 3;
const int MP_LIMIT = DIST_LIMIT * 150; // aproximate cost of diagonal movement
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
{
@ -410,13 +419,9 @@ int3 whereToExplore(HeroPtr h)
{
for(auto obj : cb->getVisitableObjs(int3(x, y, hpos.z), false))
{
int3 op = obj->visitablePos();
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, MP_LIMIT))
{
if(ai->isGoodForVisit(obj, h, *sm))
nearbyVisitableObjs.push_back(obj);
nearbyVisitableObjs.push_back(obj);
}
}
}

View File

@ -59,6 +59,10 @@ public:
const CGHeroInstance * operator->() const;
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
{
return !(*this == rhs);
}
const CGHeroInstance * get(bool doWeExpectNull = false) const;
bool validAndSet() const;
@ -88,6 +92,7 @@ struct ObjectIdRef
const CGObjectInstance * operator->() const;
operator const CGObjectInstance *() const;
operator bool() const;
ObjectIdRef(ObjectInstanceID _id);
ObjectIdRef(const CGObjectInstance * obj);
@ -166,8 +171,9 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj);
ui64 evaluateDanger(const CGObjectInstance * obj);
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 isSafeToVisit(HeroPtr h, uint64_t dangerStrength);
bool isSafeToVisit(HeroPtr h, crint3 tile);
bool compareMovement(HeroPtr lhs, HeroPtr rhs);
bool compareHeroStrength(HeroPtr h1, HeroPtr h2);

View File

@ -17,6 +17,7 @@ AIhelper::AIhelper()
{
resourceManager.reset(new ResourceManager());
buildingManager.reset(new BuildingManager());
pathfindingManager.reset(new CPathfindingManager());
//TODO: push to vector
}
@ -34,6 +35,7 @@ void AIhelper::setCB(CPlayerSpecificInfoCallback * CB)
//TODO: for
resourceManager->setCB(CB);
buildingManager->setCB(CB);
pathfindingManager->setCB(CB);
}
void AIhelper::setAI(VCAI * AI)
@ -41,6 +43,7 @@ void AIhelper::setAI(VCAI * AI)
//TODO: for loop
resourceManager->setAI(AI);
buildingManager->setAI(AI);
pathfindingManager->setAI(AI);
}
bool AIhelper::getBuildingOptions(const CGTownInstance * t)
@ -112,3 +115,33 @@ TResource AIhelper::allGold() const
{
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();
}

View File

@ -16,25 +16,26 @@
#include "ResourceManager.h"
#include "BuildingManager.h"
#include "Pathfinding/CPathfindingManager.h"
class ResourceManager;
class BuildingManager;
//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 struct SetGlobalState; //mess?
std::shared_ptr<ResourceManager> resourceManager;
std::shared_ptr<BuildingManager> buildingManager;
std::shared_ptr<CPathfindingManager> pathfindingManager;
//TODO: vector<IAbstractManager>
public:
AIhelper();
~AIhelper();
//from ResourceManager
bool canAfford(const TResources & cost) const;
TResources reservedResources() const override;
TResources freeResources() const override;
@ -43,21 +44,26 @@ public:
TResource allGold() const 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 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:
bool notifyGoalCompleted(Goals::TSubgoal goal);
void setCB(CPlayerSpecificInfoCallback * CB) 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;
};

View File

@ -8,6 +8,10 @@ include_directories(${Boost_INCLUDE_DIRS} ${CMAKE_HOME_DIRECTORY} ${CMAKE_HOME_D
set(VCAI_SRCS
StdInc.cpp
Pathfinding/AIPathfinderConfig.cpp
Pathfinding/AIPathfinder.cpp
Pathfinding/AINodeStorage.cpp
Pathfinding/CPathfindingManager.cpp
AIUtility.cpp
AIhelper.cpp
ResourceManager.cpp
@ -24,7 +28,11 @@ set(VCAI_SRCS
set(VCAI_HEADERS
StdInc.h
Pathfinding/AIPathfinderConfig.h
Pathfinding/AIPathfinder.h
Pathfinding/AINodeStorage.h
Pathfinding/CPathfindingManager.h
AIUtility.h
AIhelper.h
ResourceManager.h

View File

@ -79,7 +79,7 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army)
float HeroMovementGoalEngineBase::calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const
{
float turns = 0.0f;
float distance = CPathfinderHelper::getMovementCost(h, tile);
float distance = distanceToTile(h, tile);
if(distance)
{
if(distance < h->movement) //we can move there within one turn

View File

@ -22,8 +22,6 @@ Goals::TSubgoal FuzzyHelper::chooseSolution(Goals::TGoalVec vec)
if(vec.empty()) //no possibilities found
return sptr(Goals::Invalid());
ai->cachedSectorMaps.clear();
//a trick to switch between heroes less often - calculatePaths is costly
auto sortByHeroes = [](const Goals::TSubgoal & lhs, const Goals::TSubgoal & rhs) -> bool
{
@ -96,24 +94,7 @@ float FuzzyHelper::evaluate(Goals::ClearWayTo & g)
if (!g.hero.h)
return 0; //lowest priority
int3 t = ai->getCachedSectorMap(g.hero)->firstTileToGet(g.hero, g.tile);
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;
return g.whatToDoToAchieve()->accept(this);
}
float FuzzyHelper::evaluate(Goals::BuildThis & g)

View File

@ -698,63 +698,10 @@ TGoalVec ClearWayTo::getAllPossibleSubgoals()
//if our hero is trapped, make sure we request clearing the way from OUR perspective
auto sm = ai->getCachedSectorMap(h);
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)));
}
vstd::concatenate(ret, ai->ah->howToVisitTile(h, tile));
}
if(ai->canRecruitAnyHero())
if(ret.empty() && ai->canRecruitAnyHero())
ret.push_back(sptr(Goals::RecruitHero()));
if(ret.empty())
@ -860,13 +807,11 @@ TGoalVec Explore::getAllPossibleSubgoals()
auto primaryHero = ai->primaryHero().h;
for(auto h : heroes)
{
auto sm = ai->getCachedSectorMap(h);
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
if(ai->isTileNotReserved(h, t))
ret.push_back(sptr(Goals::ClearWayTo(obj->visitablePos(), h).setisAbstract(true)));
auto waysToVisitObj = ai->ah->howToVisitObj(h, obj);
vstd::concatenate(ret, waysToVisitObj);
}
int3 t = whereToExplore(h);
@ -883,7 +828,9 @@ TGoalVec Explore::getAllPossibleSubgoals()
t = ai->explorationDesperate(h); //check this only ONCE, high cost
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;
}
}
@ -1131,7 +1078,6 @@ TGoalVec Goals::CollectRes::getAllPossibleSubgoals()
}
for (auto h : cb->getHeroesInfo())
{
auto sm = ai->getCachedSectorMap(h);
std::vector<const CGObjectInstance *> ourObjs(objs); //copy common objects
for (auto obj : ai->reservedHeroesMap[h]) //add objects reserved by this hero
@ -1139,11 +1085,12 @@ TGoalVec Goals::CollectRes::getAllPossibleSubgoals()
if (givesResource(obj))
ourObjs.push_back(obj);
}
for (auto obj : ourObjs)
{
auto pos = obj->visitablePos();
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)));
auto waysToGo = ai->ah->howToVisitObj(h, ObjectIdRef(obj));
vstd::concatenate(ret, waysToGo);
}
}
return ret;
@ -1384,7 +1331,6 @@ TGoalVec Conquer::getAllPossibleSubgoals()
for(auto h : cb->getHeroesInfo())
{
auto sm = ai->getCachedSectorMap(h);
std::vector<const CGObjectInstance *> ourObjs(objs); //copy common objects
for(auto obj : ai->reservedHeroesMap[h]) //add objects reserved by this hero
@ -1394,32 +1340,9 @@ TGoalVec Conquer::getAllPossibleSubgoals()
}
for(auto obj : ourObjs)
{
int3 dest = obj->visitablePos();
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
{
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)));
}
}
auto waysToGo = ai->ah->howToVisitObj(h, ObjectIdRef(obj));
vstd::concatenate(ret, waysToGo);
}
}
if(!objs.empty() && ai->canRecruitAnyHero()) //probably no point to recruit hero if we see no objects to capture
@ -1500,6 +1423,11 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
//get all possible towns, heroes and dwellings we may use
TGoalVec ret;
if(!hero.validAndSet())
{
return ret;
}
//TODO: include evaluation of monsters gather in calculation
for(auto t : cb->getTownsInfo())
{
@ -1554,7 +1482,8 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
for(auto h : otherHeroes)
{
// 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)));
// Let the other hero come to us
if (!vstd::contains(ai->visitedHeroes[h], hero))
@ -1589,13 +1518,13 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
}
for(auto h : cb->getHeroesInfo())
{
auto sm = ai->getCachedSectorMap(h);
for(auto obj : objs)
{
//find safe dwelling
auto pos = obj->visitablePos();
if(ai->isGoodForVisit(obj, h, *sm))
ret.push_back(sptr(Goals::VisitTile(pos).sethero(h)));
if(ai->isGoodForVisit(obj, h))
{
vstd::concatenate(ret, ai->ah->howToVisitObj(h, obj));
}
}
}

View File

@ -0,0 +1,234 @@
/*
* 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()
{
}
AIPathNode * AINodeStorage::getAINode(CPathNodeInfo & nodeInfo) const
{
return static_cast<AIPathNode *>(nodeInfo.node);
}
AIPathNode * AINodeStorage::getAINode(CGPathNode * node) const
{
return static_cast<AIPathNode *>(node);
}
bool AINodeStorage::isBattleNode(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, CPathNodeInfo & source)
{
auto dstNode = getAINode(destination);
auto srcNode = getAINode(source);
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(
CPathNodeInfo & source,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper)
{
std::vector<CGPathNode *> neighbours;
auto srcNode = getAINode(source);
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(
CPathNodeInfo & source,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper)
{
std::vector<CGPathNode *> neighbours;
auto accessibleExits = pathfinderHelper->getTeleportExits(source);
auto srcNode = getAINode(source);
for(auto & neighbour : accessibleExits)
{
auto node = getNode(neighbour, source.node->layer, srcNode->chainMask);
neighbours.push_back(node);
}
return neighbours;
}
bool AINodeStorage::hasBetterChain(CPathNodeInfo & source, CDestinationNodeInfo & destination) const
{
auto pos = destination.coord;
auto chains = nodes[pos.x][pos.y][pos.z][EPathfindingLayer::LAND];
auto destinationNode = getAINode(destination);
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)
{
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;
}

View File

@ -0,0 +1,104 @@
/*
* 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(
CPathNodeInfo & source,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) override;
virtual std::vector<CGPathNode *> calculateTeleportations(
CPathNodeInfo & source,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) override;
virtual void commit(CDestinationNodeInfo & destination, CPathNodeInfo & source) override;
AIPathNode * getAINode(CPathNodeInfo & nodeInfo) const;
AIPathNode * getAINode(CGPathNode * node) const;
bool isBattleNode(CGPathNode * node) const;
bool hasBetterChain(CPathNodeInfo & source, CDestinationNodeInfo & destination) const;
AIPathNode * getNode(const int3 & coord, const EPathfindingLayer layer, int chainNumber);
std::vector<AIPath> getChainInfo(int3 pos);
void setHero(HeroPtr heroPtr)
{
hero = heroPtr.get();
}
const CGHeroInstance * getHero() const
{
return hero;
}
};

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

View 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();
};

View File

@ -0,0 +1,280 @@
/*
* 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 AILayerTransitionRule : public CLayerTransitionRule
{
public:
virtual void process(
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const override
{
CLayerTransitionRule::process(source, destination, pathfinderConfig, pathfinderHelper);
if(!destination.blocked)
{
return;
}
if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::SAIL)
{
logAi->debug("Check virtual boat!");
}
}
};
class AIMovementAfterDestinationRule : public CMovementAfterDestinationRule
{
private:
CPlayerSpecificInfoCallback * cb;
std::shared_ptr<AINodeStorage> nodeStorage;
public:
AIMovementAfterDestinationRule(CPlayerSpecificInfoCallback * cb, std::shared_ptr<AINodeStorage> nodeStorage)
:cb(cb), nodeStorage(nodeStorage)
{
}
virtual void process(
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * 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);
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);
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 CMovementToDestinationRule
{
private:
CPlayerSpecificInfoCallback * cb;
std::shared_ptr<AINodeStorage> nodeStorage;
public:
AIMovementToDestinationRule(CPlayerSpecificInfoCallback * cb, std::shared_ptr<AINodeStorage> nodeStorage)
:cb(cb), nodeStorage(nodeStorage)
{
}
virtual void process(
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * 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 CMovementToDestinationRule
{
private:
CPlayerSpecificInfoCallback * cb;
std::shared_ptr<AINodeStorage> nodeStorage;
public:
AIPreviousNodeRule(CPlayerSpecificInfoCallback * cb, std::shared_ptr<AINodeStorage> nodeStorage)
:cb(cb), nodeStorage(nodeStorage)
{
}
virtual void process(
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * 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<AILayerTransitionRule>(),
std::make_shared<CDestinationActionRule>(),
std::make_shared<AIMovementToDestinationRule>(cb, nodeStorage),
std::make_shared<CMovementCostRule>(),
std::make_shared<AIPreviousNodeRule>(cb, nodeStorage),
std::make_shared<AIMovementAfterDestinationRule>(cb, nodeStorage)
};
return rules;
}
AIPathfinderConfig::AIPathfinderConfig(
CPlayerSpecificInfoCallback * cb,
std::shared_ptr<AINodeStorage> nodeStorage)
:CPathfinderConfig(nodeStorage, makeRuleset(cb, nodeStorage))
{
}

View 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 CPathfinderConfig
{
public:
AIPathfinderConfig(CPlayerSpecificInfoCallback * cb, std::shared_ptr<AINodeStorage> nodeStorage);
};

View 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 "CPathfindingManager.h"
#include "AIPathfinder.h"
#include "AIPathfinderConfig.h"
#include "../../../lib/CGameInfoCallback.h"
#include "../../../lib/mapping/CMap.h"
CPathfindingManager::CPathfindingManager(CPlayerSpecificInfoCallback * CB, VCAI * AI)
: ai(AI), cb(CB)
{
}
void CPathfindingManager::setCB(CPlayerSpecificInfoCallback * CB)
{
cb = CB;
pathfinder.reset(new AIPathfinder(cb));
}
void CPathfindingManager::setAI(VCAI * AI)
{
ai = AI;
}
Goals::TGoalVec CPathfindingManager::howToVisitTile(int3 tile)
{
Goals::TGoalVec result;
auto heroes = cb->getHeroesInfo();
for(auto hero : heroes)
{
vstd::concatenate(result, howToVisitTile(hero, tile));
}
return result;
}
Goals::TGoalVec CPathfindingManager::howToVisitObj(ObjectIdRef obj)
{
Goals::TGoalVec result;
auto heroes = cb->getHeroesInfo();
for(auto hero : heroes)
{
vstd::concatenate(result, howToVisitObj(hero, obj));
}
return result;
}
Goals::TGoalVec CPathfindingManager::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 CPathfindingManager::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> CPathfindingManager::getPathsToTile(HeroPtr hero, int3 tile)
{
return pathfinder->getPathInfo(hero, tile);
}
Goals::TGoalVec CPathfindingManager::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 CPathfindingManager::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 CPathfindingManager::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 CPathfindingManager::resetPaths()
{
logAi->debug("AIPathfinder has been reseted.");
pathfinder->clear();
}

View 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 // : pulbic IAbstractManager
{
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 CPathfindingManager : public IPathfindingManager
{
friend class AIhelper;
private:
CPlayerSpecificInfoCallback * cb; //this is enough, but we downcast from CCallback
VCAI * ai;
std::unique_ptr<AIPathfinder> pathfinder;
public:
CPathfindingManager() = default;
CPathfindingManager(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;
};

View File

@ -120,7 +120,7 @@ void VCAI::heroMoved(const TryMoveHero & details)
validateObject(details.id); //enemy hero may have left visible area
auto hero = cb->getHero(details.id);
cachedSectorMaps.clear();
ah->resetPaths();
const int3 from = CGHeroInstance::convertPosition(details.start, false);
const int3 to = CGHeroInstance::convertPosition(details.end, false);
@ -401,7 +401,7 @@ void VCAI::newObject(const CGObjectInstance * obj)
if(obj->isVisitable())
addVisitableObj(obj);
cachedSectorMaps.clear();
ah->resetPaths();
}
void VCAI::objectRemoved(const CGObjectInstance * obj)
@ -460,7 +460,7 @@ void VCAI::objectRemoved(const CGObjectInstance * obj)
}
}
cachedSectorMaps.clear(); //invalidate all paths
ah->resetPaths();
//TODO
//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)
{
LOG_TRACE_PARAMS(logAi, "version '%i'", version);
NET_EVENT_HANDLER;
//NET_EVENT_HANDLER;
#if 0
//disabled due to issue 2890
@ -792,9 +792,11 @@ void makePossibleUpgrades(const CArmedInstance * obj)
void VCAI::makeTurn()
{
logGlobal->info("Player %d (%s) starting turn", playerID, playerID.getStr());
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);
setThreadName("VCAI::makeTurn");
@ -882,8 +884,12 @@ void VCAI::mainLoop()
elementarGoals.clear();
ultimateGoalsFromBasic.clear();
logAi->debug("Main loop: decomposing %i basic goals", basicGoals.size());
for (auto basicGoal : basicGoals)
{
logAi->debug("Main loop: decomposing basic goal %s", basicGoal->name());
auto goalToDecompose = basicGoal;
Goals::TSubgoal elementarGoal = sptr(Goals::Invalid());
int maxAbstractGoals = 10;
@ -975,6 +981,9 @@ void VCAI::mainLoop()
completeGoal(e.goal);
//local goal was also completed?
completeGoal(goalToRealize);
// remove abstract visit tile if we completed the elementar one
vstd::erase_if_present(goalsToAdd, goalToRealize);
}
catch (std::exception & e)
{
@ -1013,17 +1022,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)
{
LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->name % obj->getObjectName() % obj->pos.toString());
@ -1289,10 +1287,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 targetPos = sm.firstTileToGet(h, pos);
const int3 targetPos = path.firstTileToGet();
if (!targetPos.valid())
return false;
if (!isTileNotReserved(h.get(), targetPos))
@ -1309,8 +1324,11 @@ bool VCAI::isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, SectorMap & s
return false;
if (vstd::contains(reservedObjs, obj))
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
//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
@ -1320,12 +1338,14 @@ bool VCAI::isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, SectorMap & s
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())
{
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
else
return true;
@ -1380,47 +1400,38 @@ void VCAI::wander(HeroPtr h)
{
validateVisitableObjs();
std::vector<ObjectIdRef> dests;
auto sm = getCachedSectorMap(h);
//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
{
int3 pos = sm->firstTileToGet(h, obj->visitablePos());
if(pos.valid() && isAccessibleForHero(pos, h)) //even nearby objects could be blocked by other heroes :(
return true;
return false;
return ah->getPathsToTile(h, obj->visitablePos()).size();
});
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);
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);
});
}
return isGoodForVisit(obj, h, distanceLimit);
});
pass++;
}
vstd::erase_if(dests, [&](ObjectIdRef obj) -> bool
{
return !isSafeToVisit(h, sm->firstTileToGet(h, obj->visitablePos()));
});
if(!dests.size())
{
logAi->debug("Looking for town destination");
if(cb->getVisitableObjs(h->visitablePos()).size() > 1)
moveHeroToTile(h->visitablePos(), h); //just in case we're standing on blocked subterranean gate
@ -1497,10 +1508,10 @@ void VCAI::wander(HeroPtr h)
Goals::TGoalVec targetObjectGoals;
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);
decomposeGoal(bestObjectGoal)->accept(this);
//wander should not cause heroes to be reserved - they are always considered free
if(bestObjectGoal->goalType == Goals::VISIT_OBJ)
@ -1510,7 +1521,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());
}
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);
}
@ -1541,7 +1564,7 @@ void VCAI::completeGoal(Goals::TSubgoal goal)
if (goal->goalType == Goals::WIN) //we can never complete this goal - unless we already won
return;
logAi->trace("Completing goal: %s", goal->name());
logAi->debug("Completing goal: %s", goal->name());
//notify Managers
ah->notifyGoalCompleted(goal);
@ -1649,7 +1672,7 @@ bool VCAI::isAbleToExplore(HeroPtr h)
void VCAI::clearPathsInfo()
{
heroesUnableToExplore.clear();
cachedSectorMaps.clear();
ah->resetPaths();
}
void VCAI::validateVisitableObjs()
@ -1745,7 +1768,7 @@ const CGObjectInstance * VCAI::lookForArt(int aid) const
//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
@ -2529,7 +2552,7 @@ void VCAI::performTypicalActions()
if(!h) //hero might be lost. getUnblockedHeroes() called once on start of turn
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);
pickBestArtifacts(*h);
try
@ -2644,7 +2667,6 @@ int3 VCAI::explorationNewPoint(HeroPtr h)
int3 VCAI::explorationDesperate(HeroPtr h)
{
auto sm = getCachedSectorMap(h);
int radius = h->getSightRadius();
std::vector<std::vector<int3>> tiles; //tiles[distance_to_fow]
@ -2673,15 +2695,20 @@ int3 VCAI::explorationDesperate(HeroPtr h)
if(!howManyTilesWillBeDiscovered(tile, radius, cbp, h)) //avoid costly checks of tiles that don't reveal much
continue;
auto t = sm->firstTileToGet(h, tile);
if(t.valid())
auto paths = ah->getPathsToTile(h, tile);
for(auto path : paths)
{
auto t = path.firstTileToGet();
if(t == bestTile)
continue;
auto obj = cb->getTopObj(t);
if (obj)
if (obj->blockVisit && !isObjectRemovable(obj)) //we can't stand on object or remove it
continue;
ui64 ourDanger = evaluateDanger(t, h.h);
ui64 ourDanger = path.getTotalDanger(h);
if(ourDanger < lowestDanger)
{
if(!ourDanger) //at least one safe place found
@ -2759,7 +2786,6 @@ void VCAI::lostHero(HeroPtr h)
vstd::erase_if_present(reservedObjs, obj); //unreserve all objects for that hero
}
vstd::erase_if_present(reservedHeroesMap, h);
vstd::erase_if_present(cachedSectorMaps, h);
vstd::erase_if_present(visitedHeroes, h);
for (auto heroVec : visitedHeroes)
{
@ -2820,20 +2846,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()
{
battle = NO_BATTLE;

View File

@ -25,6 +25,7 @@
#include "../../lib/mapObjects/MiscObjects.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/CondSh.h"
#include "Pathfinding/AIPathfinder.h"
struct QuestInfo;
@ -106,9 +107,6 @@ public:
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
//TODO: move to separate PathHandler class?
std::map<HeroPtr, std::shared_ptr<SectorMap>> cachedSectorMaps; //TODO: serialize? not necessary
AIStatus status;
std::string battlename;
@ -137,7 +135,7 @@ public:
int3 explorationBestNeighbour(int3 hpos, int radius, HeroPtr h);
int3 explorationNewPoint(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;
@ -216,14 +214,14 @@ public:
Goals::TSubgoal questToGoal(const QuestInfo & q);
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 CGDwelling * d, const CArmedInstance * recruiter);
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 pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance * other = nullptr);
void moveCreaturesToHero(const CGTownInstance * t);
bool goVisitObj(const CGObjectInstance * obj, HeroPtr h);
void performObjectInteraction(const CGObjectInstance * obj, HeroPtr h);
bool moveHeroToTile(int3 dst, HeroPtr h);
@ -250,13 +248,12 @@ public:
virtual std::vector<const CGObjectInstance *> getFlaggedObjects() const;
const CGObjectInstance * lookForArt(int aid) const;
bool isAccessible(const int3 & pos);
bool isAccessible(const int3 & pos) const;
HeroPtr getHeroWithGrail() const;
const CGObjectInstance * getUnvisitedObj(const std::function<bool(const CGObjectInstance *)> & predicate);
bool isAccessibleForHero(const int3 & pos, HeroPtr h, bool includeAllies = false) const;
//optimization - use one SM for every hero call
std::shared_ptr<SectorMap> getCachedSectorMap(HeroPtr h);
const CGTownInstance * findTownWithTavern() const;
bool canRecruitAnyHero(const CGTownInstance * t = NULL) const;

View File

@ -72,3 +72,6 @@ Henning Koehler, <henning.koehler.nz@gmail.com>
Andrzej Żak aka godric3
* minor bug fixes and modding features
Andrii Danylchenko
* VCAI improvements

View File

@ -291,11 +291,6 @@ const CPathsInfo * CCallback::getPathsInfo(const CGHeroInstance *h)
return cl->getPathsInfo(h);
}
void CCallback::calculatePaths(std::shared_ptr<CPathfinderConfig> config, const CGHeroInstance * hero)
{
cl->calculatePaths(config, hero);
}
int3 CCallback::getGuardingCreaturePosition(int3 tile)
{
if (!gs->map->isInTheMap(tile))

View File

@ -107,7 +107,6 @@ public:
virtual bool canMoveBetween(const int3 &a, const int3 &b);
virtual int3 getGuardingCreaturePosition(int3 tile);
virtual const CPathsInfo * getPathsInfo(const CGHeroInstance *h);
virtual void calculatePaths(std::shared_ptr<CPathfinderConfig> config, const CGHeroInstance * hero);
virtual void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out);

View File

@ -628,13 +628,6 @@ const CPathsInfo * CClient::getPathsInfo(const CGHeroInstance * h)
return pathInfo.get();
}
void CClient::calculatePaths(std::shared_ptr<CPathfinderConfig> config, const CGHeroInstance * hero)
{
boost::unique_lock<boost::mutex> pathLock(config->nodeStorage->getMutex());
gs->calculatePaths(config, hero);
}
PlayerColor CClient::getLocalPlayer() const
{
if(LOCPLINT)

View File

@ -151,7 +151,6 @@ public:
void invalidatePaths();
const CPathsInfo * getPathsInfo(const CGHeroInstance * h);
void calculatePaths(std::shared_ptr<CPathfinderConfig> config, const CGHeroInstance * hero);
virtual PlayerColor getLocalPlayer() const override;
friend class CCallback; //handling players actions

View File

@ -919,6 +919,11 @@ void CGameInfoCallback::getVisibleTilesInRange(std::unordered_set<int3, ShashInt
gs->getTilesInRange(tiles, pos, radious, getLocalPlayer(), -1, distanceFormula);
}
void CGameInfoCallback::calculatePaths(std::shared_ptr<CPathfinderConfig> config, const CGHeroInstance * hero)
{
gs->calculatePaths(config, hero);
}
const CArtifactInstance * CGameInfoCallback::getArtInstance( ArtifactInstanceID aid ) const
{
return gs->map->artInstances[aid.num];

View File

@ -31,6 +31,7 @@ struct TeamState;
struct QuestInfo;
struct ShashInt3;
class CGameState;
class CPathfinderConfig;
class DLL_LINKAGE CGameInfoCallback : public virtual CCallbackBase
@ -98,6 +99,7 @@ public:
virtual std::shared_ptr<boost::multi_array<TerrainTile*, 3>> getAllVisibleTiles() 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 calculatePaths(std::shared_ptr<CPathfinderConfig> config, const CGHeroInstance * hero);
//town
virtual const CGTownInstance* getTown(ObjectInstanceID objid) const;

View File

@ -25,23 +25,19 @@ bool canSeeObj(const CGObjectInstance * obj)
return obj != nullptr && obj->ID != Obj::EVENT;
}
CNeighbourFinder::CNeighbourFinder()
{
}
std::vector<CGPathNode *> CNeighbourFinder::calculateNeighbours(
std::vector<CGPathNode *> CNodeStorage::calculateNeighbours(
CPathNodeInfo & source,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const
CPathfinderHelper * pathfinderHelper)
{
std::vector<CGPathNode *> neighbours;
auto accessibleNeighbourTiles = getNeighbourTiles(source, pathfinderConfig, pathfinderHelper);
auto accessibleNeighbourTiles = pathfinderHelper->getNeighbourTiles(source);
for(auto & neighbour : accessibleNeighbourTiles)
{
for(EPathfindingLayer i = EPathfindingLayer::LAND; i <= EPathfindingLayer::AIR; i.advance(1))
{
auto node = pathfinderConfig->nodeStorage->getNode(neighbour, i);
auto node = getNode(neighbour, i);
if(node->accessible == CGPathNode::NOT_SET)
continue;
@ -53,17 +49,17 @@ std::vector<CGPathNode *> CNeighbourFinder::calculateNeighbours(
return neighbours;
}
std::vector<CGPathNode *> CNeighbourFinder::calculateTeleportations(
std::vector<CGPathNode *> CNodeStorage::calculateTeleportations(
CPathNodeInfo & source,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const
CPathfinderHelper * pathfinderHelper)
{
std::vector<CGPathNode *> neighbours;
auto accessibleExits = getTeleportExits(source, pathfinderConfig, pathfinderHelper);
auto accessibleExits = pathfinderHelper->getTeleportExits(source);
for(auto & neighbour : accessibleExits)
{
auto node = pathfinderConfig->nodeStorage->getNode(neighbour, source.node->layer);
auto node = getNode(neighbour, source.node->layer);
neighbours.push_back(node);
}
@ -71,14 +67,11 @@ std::vector<CGPathNode *> CNeighbourFinder::calculateTeleportations(
return neighbours;
}
std::vector<int3> CNeighbourFinder::getNeighbourTiles(
CPathNodeInfo & source,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const
std::vector<int3> CPathfinderHelper::getNeighbourTiles(CPathNodeInfo & source) const
{
std::vector<int3> neighbourTiles;
pathfinderHelper->getNeighbours(
getNeighbours(
*source.tile,
source.node->coord,
neighbourTiles,
@ -89,46 +82,51 @@ std::vector<int3> CNeighbourFinder::getNeighbourTiles(
{
vstd::erase_if(neighbourTiles, [&](int3 tile) -> bool
{
return !pathfinderHelper->canMoveBetween(tile, source.nodeObject->visitablePos());
return !canMoveBetween(tile, source.nodeObject->visitablePos());
});
}
return neighbourTiles;
}
class CPathfinderNodeStorage : public CNodeStorage
CNodeStorage::CNodeStorage(CPathsInfo & pathsInfo, const CGHeroInstance * hero)
:out(pathsInfo)
{
private:
CPathsInfo & out;
out.hero = hero;
out.hpos = hero->getPosition(false);
}
public:
CPathfinderNodeStorage(CPathsInfo & pathsInfo, const CGHeroInstance * hero)
:out(pathsInfo)
{
out.hero = hero;
out.hpos = hero->getPosition(false);
}
CGPathNode * CNodeStorage::getNode(const int3 & coord, const EPathfindingLayer layer)
{
return out.getNode(coord, layer);
}
virtual boost::mutex & getMutex()
{
return out.pathMx;
}
void CNodeStorage::resetTile(
const int3 & tile,
EPathfindingLayer layer,
CGPathNode::EAccessibility accessibility)
{
getNode(tile, layer)->update(tile, layer, accessibility);
}
virtual CGPathNode * getNode(const int3 & coord, const EPathfindingLayer layer)
{
return out.getNode(coord, layer);
}
CGPathNode * CNodeStorage::getInitialNode()
{
auto initialNode = getNode(out.hpos, out.hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND);
virtual CGPathNode * getInitialNode()
{
auto initialNode = getNode(out.hpos, out.hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND);
initialNode->turns = 0;
initialNode->moveRemains = out.hero->movement;
initialNode->turns = 0;
initialNode->moveRemains = out.hero->movement;
return initialNode;
}
return initialNode;
}
};
void CNodeStorage::commit(CDestinationNodeInfo & destination, CPathNodeInfo & source)
{
assert(destination.node != source.node->theNodeBefore); //two tiles can't point to each other
destination.node->moveRemains = destination.movementLeft;
destination.node->turns = destination.turn;
destination.node->theNodeBefore = source.node;
destination.node->action = destination.action;
}
PathfinderOptions::PathfinderOptions()
{
@ -151,7 +149,7 @@ void CMovementCostRule::process(
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper)
CPathfinderHelper * pathfinderHelper) const
{
int turnAtNextTile = destination.turn, moveAtNextTile = destination.movementLeft;
int cost = pathfinderHelper->getMovementCost(source, destination, moveAtNextTile);
@ -177,11 +175,7 @@ void CMovementCostRule::process(
if(destination.isBetterWay() &&
((source.node->turns == turnAtNextTile && remains) || pathfinderHelper->passOneTurnLimitCheck(source)))
{
assert(destination.node != source.node->theNodeBefore); //two tiles can't point to each other
destination.node->moveRemains = remains;
destination.node->turns = turnAtNextTile;
destination.node->theNodeBefore = source.node;
destination.node->action = destination.action;
pathfinderConfig->nodeStorage->commit(destination, source);
return;
}
@ -190,10 +184,9 @@ void CMovementCostRule::process(
}
CPathfinderConfig::CPathfinderConfig(
std::shared_ptr<CNodeStorage> nodeStorage,
std::shared_ptr<CNeighbourFinder> neighbourFinder,
std::shared_ptr<INodeStorage> nodeStorage,
std::vector<std::shared_ptr<IPathfindingRule>> rules)
: nodeStorage(nodeStorage), neighbourFinder(neighbourFinder), rules(rules), options()
: nodeStorage(nodeStorage), rules(rules), options()
{
}
@ -205,9 +198,10 @@ CPathfinder::CPathfinder(
_gs,
_hero,
std::make_shared<CPathfinderConfig>(
std::make_shared<CPathfinderNodeStorage>(_out, _hero),
std::make_shared<CNeighbourFinder>(),
std::make_shared<CNodeStorage>(_out, _hero),
std::vector<std::shared_ptr<IPathfindingRule>>{
std::make_shared<CLayerTransitionRule>(),
std::make_shared<CDestinationActionRule>(),
std::make_shared<CMovementToDestinationRule>(),
std::make_shared<CMovementCostRule>(),
std::make_shared<CMovementAfterDestinationRule>()
@ -276,35 +270,31 @@ void CPathfinder::calculatePaths()
source.objectRelations = gs->getPlayerRelations(hero->tempOwner, source.nodeObject->tempOwner);
//add accessible neighbouring nodes to the queue
auto neighbourNodes = config->neighbourFinder->calculateNeighbours(source, config.get(), hlp.get());
auto neighbourNodes = config->nodeStorage->calculateNeighbours(source, config.get(), hlp.get());
for(CGPathNode * neighbour : neighbourNodes)
{
destination.setNode(gs, neighbour);
if(destination.node->locked)
if(neighbour->locked)
continue;
if(!isPatrolMovementAllowed(destination.node->coord))
if(!isPatrolMovementAllowed(neighbour->coord))
continue;
if(!hlp->isLayerAvailable(destination.node->layer))
if(!hlp->isLayerAvailable(neighbour->layer))
continue;
/// Check transition without tile accessability rules
if(source.node->layer != destination.node->layer && !isLayerTransitionPossible(destination.node->layer))
continue;
/// Check transition using tile accessability rules
if(source.node->layer != destination.node->layer && !isLayerTransitionPossible())
if(source.node->layer != neighbour->layer && !isLayerTransitionPossible(neighbour->layer))
continue;
destination.guarded = isDestinationGuarded();
if(destination.nodeObject)
destination.objectRelations = gs->getPlayerRelations(hero->tempOwner, destination.nodeObject->tempOwner);
destination.setNode(gs, neighbour);
destination.action = getDestAction();
destination.turn = turn;
destination.movementLeft = movement;
destination.guarded = isDestinationGuarded();
destination.isGuardianTile = destination.guarded && isDestinationGuardian();
if(destination.nodeObject)
destination.objectRelations = gs->getPlayerRelations(hero->tempOwner, destination.nodeObject->tempOwner);
for(auto rule : config->rules)
{
@ -326,7 +316,7 @@ void CPathfinder::calculatePaths()
if(!source.isNodeObjectVisitable() || patrolState == PATROL_RADIUS)
continue;
auto teleportationNodes = config->neighbourFinder->calculateTeleportations(source, config.get(), hlp.get());
auto teleportationNodes = config->nodeStorage->calculateTeleportations(source, config.get(), hlp.get());
for(CGPathNode * teleportNode : teleportationNodes)
{
if(teleportNode->locked)
@ -345,10 +335,9 @@ void CPathfinder::calculatePaths()
if(destination.isBetterWay())
{
destination.node->moveRemains = destination.movementLeft;
destination.node->turns = destination.turn;
destination.node->theNodeBefore = source.node;
destination.node->action = getTeleportDestAction();
destination.action = getTeleportDestAction();
config->nodeStorage->commit(destination, source);
if(destination.node->action == CGPathNode::TELEPORT_NORMAL)
pq.push(destination.node);
}
@ -396,28 +385,25 @@ std::vector<int3> CPathfinderHelper::getCastleGates(CPathNodeInfo & source) cons
return allowedExits;
}
std::vector<int3> CNeighbourFinder::getTeleportExits(
CPathNodeInfo & source,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const
std::vector<int3> CPathfinderHelper::getTeleportExits(CPathNodeInfo & source) const
{
std::vector<int3> teleportationExits;
const CGTeleport * objTeleport = dynamic_cast<const CGTeleport *>(source.nodeObject);
if(pathfinderHelper->isAllowedTeleportEntrance(objTeleport))
if(isAllowedTeleportEntrance(objTeleport))
{
for(auto exit : pathfinderHelper->getAllowedTeleportChannelExits(objTeleport->channel))
for(auto exit : getAllowedTeleportChannelExits(objTeleport->channel))
{
teleportationExits.push_back(exit);
}
}
else if(pathfinderHelper->options.useCastleGate
else if(options.useCastleGate
&& (source.nodeObject->ID == Obj::TOWN && source.nodeObject->subID == ETownType::INFERNO
&& source.objectRelations != PlayerRelations::ENEMIES))
{
/// TODO: Find way to reuse CPlayerSpecificInfoCallback::getTownsInfo
/// This may be handy if we allow to use teleportation to friendly towns
for(auto exit : pathfinderHelper->getCastleGates(source))
for(auto exit : getCastleGates(source))
{
teleportationExits.push_back(exit);
}
@ -488,68 +474,73 @@ bool CPathfinder::isLayerTransitionPossible(const ELayer destLayer) const
return false;
}
bool CPathfinder::isLayerTransitionPossible() const
void CLayerTransitionRule::process(
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const
{
if(source.node->layer == destination.node->layer)
return;
switch(source.node->layer)
{
case ELayer::LAND:
if(destination.node->layer == ELayer::SAIL)
case EPathfindingLayer::LAND:
if(destination.node->layer == EPathfindingLayer::SAIL)
{
/// Cannot enter empty water tile from land -> it has to be visitable
if(destination.node->accessible == CGPathNode::ACCESSIBLE)
return false;
destination.blocked = true;
}
break;
case ELayer::SAIL:
case EPathfindingLayer::SAIL:
//tile must be accessible -> exception: unblocked blockvis tiles -> clear but guarded by nearby monster coast
if((destination.node->accessible != CGPathNode::ACCESSIBLE && (destination.node->accessible != CGPathNode::BLOCKVIS || destination.tile->blocked))
|| destination.tile->visitable) //TODO: passableness problem -> town says it's passable (thus accessible) but we obviously can't disembark onto town gate
{
return false;
destination.blocked = true;
}
break;
case ELayer::AIR:
if(config->options.originalMovementRules)
case EPathfindingLayer::AIR:
if(pathfinderConfig->options.originalMovementRules)
{
if((source.node->accessible != CGPathNode::ACCESSIBLE &&
source.node->accessible != CGPathNode::VISITABLE) &&
(destination.node->accessible != CGPathNode::VISITABLE &&
destination.node->accessible != CGPathNode::ACCESSIBLE))
{
return false;
destination.blocked = true;
}
}
else if(source.node->accessible != CGPathNode::ACCESSIBLE && destination.node->accessible != CGPathNode::ACCESSIBLE)
{
/// Hero that fly can only land on accessible tiles
return false;
destination.blocked = true;
}
break;
case ELayer::WATER:
case EPathfindingLayer::WATER:
if(destination.node->accessible != CGPathNode::ACCESSIBLE && destination.node->accessible != CGPathNode::VISITABLE)
{
/// Hero that walking on water can transit to accessible and visitable tiles
/// Though hero can't interact with blocking visit objects while standing on water
return false;
destination.blocked = true;
}
break;
}
return true;
}
CPathfinderBlockingRule::BlockingReason CMovementToDestinationRule::getBlockingReason(
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper)
CPathfinderHelper * pathfinderHelper) const
{
if(destination.node->accessible == CGPathNode::BLOCKED)
@ -564,7 +555,7 @@ CPathfinderBlockingRule::BlockingReason CMovementToDestinationRule::getBlockingR
if(source.guarded)
{
if(!(pathfinderConfig->options.originalMovementRules && source.node->layer == EPathfindingLayer::AIR) &&
!destination.guarded) // Can step into tile of guard
!destination.isGuardianTile) // Can step into tile of guard
{
return BlockingReason::SOURCE_GUARDED;
}
@ -579,7 +570,7 @@ CPathfinderBlockingRule::BlockingReason CMovementToDestinationRule::getBlockingR
if(source.guarded)
{
// Hero embarked a boat standing on a guarded tile -> we must allow to move away from that tile
if(source.node->action != CGPathNode::EMBARK && !destination.guarded)
if(source.node->action != CGPathNode::EMBARK && !destination.isGuardianTile)
return BlockingReason::SOURCE_GUARDED;
}
@ -607,7 +598,7 @@ CPathfinderBlockingRule::BlockingReason CMovementToDestinationRule::getBlockingR
}
if(destination.guarded)
return BlockingReason::DESTINATION_GUARDED;
return BlockingReason::DESTINATION_BLOCKED;
break;
}
@ -620,7 +611,7 @@ void CMovementAfterDestinationRule::process(
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * config,
CPathfinderHelper * pathfinderHelper)
CPathfinderHelper * pathfinderHelper) const
{
auto blocker = getBlockingReason(source, destination, config, pathfinderHelper);
@ -636,7 +627,7 @@ CPathfinderBlockingRule::BlockingReason CMovementAfterDestinationRule::getBlocki
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * config,
CPathfinderHelper * pathfinderHelper)
CPathfinderHelper * pathfinderHelper) const
{
switch(destination.action)
{
@ -679,15 +670,15 @@ CPathfinderBlockingRule::BlockingReason CMovementAfterDestinationRule::getBlocki
return BlockingReason::DESTINATION_BLOCKED;
case CGPathNode::DISEMBARK:
if(pathfinderHelper->options.useEmbarkAndDisembark && !destination.guarded)
return BlockingReason::NONE;
if(pathfinderHelper->options.useEmbarkAndDisembark)
return destination.guarded ? BlockingReason::DESTINATION_GUARDED : BlockingReason::NONE;
return BlockingReason::DESTINATION_BLOCKED;
case CGPathNode::BATTLE:
/// Movement after BATTLE action only possible from guarded tile to guardian tile
if(destination.guarded)
return BlockingReason::NONE;
return BlockingReason::DESTINATION_GUARDED;
break;
}
@ -695,13 +686,24 @@ CPathfinderBlockingRule::BlockingReason CMovementAfterDestinationRule::getBlocki
return BlockingReason::DESTINATION_BLOCKED;
}
CGPathNode::ENodeAction CPathfinder::getDestAction() const
void CDestinationActionRule::process(
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const
{
if(destination.action != CGPathNode::ENodeAction::UNKNOWN)
{
return;
}
CGPathNode::ENodeAction action = CGPathNode::NORMAL;
auto hero = pathfinderHelper->hero;
switch(destination.node->layer)
{
case ELayer::LAND:
if(source.node->layer == ELayer::SAIL)
case EPathfindingLayer::LAND:
if(source.node->layer == EPathfindingLayer::SAIL)
{
// TODO: Handle dismebark into guarded areaa
action = CGPathNode::DISEMBARK;
@ -711,10 +713,10 @@ CGPathNode::ENodeAction CPathfinder::getDestAction() const
/// don't break - next case shared for both land and sail layers
FALLTHROUGH
case ELayer::SAIL:
case EPathfindingLayer::SAIL:
if(destination.isNodeObjectVisitable())
{
auto objRel = getPlayerRelations(destination.nodeObject->tempOwner, hero->tempOwner);
auto objRel = destination.objectRelations;
if(destination.nodeObject->ID == Obj::BOAT)
action = CGPathNode::EMBARK;
@ -736,7 +738,7 @@ CGPathNode::ENodeAction CPathfinder::getDestAction() const
{
if(destination.nodeObject->passableFor(hero->tempOwner))
{
if(isDestinationGuarded())
if(destination.guarded)
action = CGPathNode::BATTLE;
}
else if(objRel == PlayerRelations::ENEMIES)
@ -746,32 +748,32 @@ CGPathNode::ENodeAction CPathfinder::getDestAction() const
{
if(destination.nodeObject->passableFor(hero->tempOwner))
{
if(isDestinationGuarded())
if(destination.guarded)
action = CGPathNode::BATTLE;
}
else
action = CGPathNode::BLOCKING_VISIT;
}
else if(isDestinationGuardian())
else if(destination.isGuardianTile)
action = CGPathNode::BATTLE;
else if(destination.nodeObject->blockVisit && !(config->options.useCastleGate && destination.nodeObject->ID == Obj::TOWN))
else if(destination.nodeObject->blockVisit && !(pathfinderConfig->options.useCastleGate && destination.nodeObject->ID == Obj::TOWN))
action = CGPathNode::BLOCKING_VISIT;
if(action == CGPathNode::NORMAL)
{
if(config->options.originalMovementRules && isDestinationGuarded())
if(pathfinderConfig->options.originalMovementRules && destination.guarded)
action = CGPathNode::BATTLE;
else
action = CGPathNode::VISIT;
}
}
else if(isDestinationGuarded())
else if(destination.guarded)
action = CGPathNode::BATTLE;
break;
}
return action;
destination.action = action;
}
CGPathNode::ENodeAction CPathfinder::getTeleportDestAction() const
@ -842,9 +844,9 @@ void CPathfinder::initializeGraph()
{
auto updateNode = [&](int3 pos, ELayer layer, const TerrainTile * tinfo)
{
auto node = config->nodeStorage->getNode(pos, layer);
auto accessibility = evaluateAccessibility(pos, tinfo, layer);
node->update(pos, layer, accessibility);
config->nodeStorage->resetTile(pos, layer, accessibility);
};
int3 pos;
@ -996,17 +998,17 @@ bool CPathfinderHelper::addTeleportWhirlpool(const CGWhirlpool * obj) const
return options.useTeleportWhirlpool && hasBonusOfType(Bonus::WHIRLPOOL_PROTECTION) && obj;
}
int CPathfinderHelper::getHeroMaxMovementPoints(EPathfindingLayer layer)
int CPathfinderHelper::getHeroMaxMovementPoints(EPathfindingLayer layer) const
{
return hero->maxMovePoints(layer);
}
int CPathfinderHelper::movementPointsAfterEmbark(int movement, int turn, int action)
int CPathfinderHelper::movementPointsAfterEmbark(int movement, int turn, int action) const
{
return hero->movementPointsAfterEmbark(movement, turn, action, getTurnInfo());
}
bool CPathfinderHelper::passOneTurnLimitCheck(CPathNodeInfo & source)
bool CPathfinderHelper::passOneTurnLimitCheck(CPathNodeInfo & source) const
{
if(!options.oneTurnSpecialLayersLimit)
@ -1174,7 +1176,12 @@ int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer layer) const
return turnsInfo[turn]->getMaxMovePoints(layer);
}
void CPathfinderHelper::getNeighbours(const TerrainTile & srct, const int3 & tile, std::vector<int3> & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing)
void CPathfinderHelper::getNeighbours(
const TerrainTile & srct,
const int3 & tile,
std::vector<int3> & vec,
const boost::logic::tribool & onLand,
const bool limitCoastSailing) const
{
CMap * map = gs->map;
@ -1219,7 +1226,13 @@ void CPathfinderHelper::getNeighbours(const TerrainTile & srct, const int3 & til
}
}
int CPathfinderHelper::getMovementCost(const int3 & src, const int3 & dst, const TerrainTile * ct, const TerrainTile * dt, const int remainingMovePoints, const bool checkLast)
int CPathfinderHelper::getMovementCost(
const int3 & src,
const int3 & dst,
const TerrainTile * ct,
const TerrainTile * dt,
const int remainingMovePoints,
const bool checkLast) const
{
if(src == dst) //same tile
return 0;
@ -1440,7 +1453,7 @@ void CDestinationNodeInfo::setNode(CGameState * gs, CGPathNode * n, bool exclude
action = CGPathNode::ENodeAction::UNKNOWN;
}
bool CDestinationNodeInfo::isBetterWay()
bool CDestinationNodeInfo::isBetterWay() const
{
if(node->turns == 0xff) //we haven't been here before
return true;

View File

@ -22,6 +22,9 @@ struct TerrainTile;
class CPathfinderHelper;
class CMap;
class CGWhirlpool;
class CPathfinderHelper;
class CPathfinder;
class CPathfinderConfig;
struct DLL_LINKAGE CGPathNode
{
@ -118,26 +121,15 @@ struct DLL_LINKAGE CDestinationNodeInfo : public CPathNodeInfo
int turn;
int movementLeft;
bool blocked;
bool isGuardianTile;
CDestinationNodeInfo();
virtual void setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject = false) override;
virtual bool isBetterWay();
virtual bool isBetterWay() const;
};
class CNodeStorage
{
public:
virtual CGPathNode * getNode(const int3 & coord, const EPathfindingLayer layer) = 0;
virtual CGPathNode * getInitialNode() = 0;
virtual boost::mutex & getMutex() = 0;
};
class CPathfinderHelper;
class CPathfinder;
class CPathfinderConfig;
class IPathfindingRule
{
public:
@ -145,27 +137,47 @@ public:
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) = 0;
CPathfinderHelper * pathfinderHelper) const = 0;
};
class CMovementCostRule : public IPathfindingRule
class DLL_LINKAGE CMovementCostRule : public IPathfindingRule
{
public:
virtual void process(
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) override;
CPathfinderHelper * pathfinderHelper) const override;
};
class CPathfinderBlockingRule : public IPathfindingRule
class DLL_LINKAGE CLayerTransitionRule : public IPathfindingRule
{
public:
virtual void process(
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) override
CPathfinderHelper * pathfinderHelper) const override;
};
class DLL_LINKAGE CDestinationActionRule : public IPathfindingRule
{
public:
virtual void process(
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const override;
};
class DLL_LINKAGE CPathfinderBlockingRule : public IPathfindingRule
{
public:
virtual void process(
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const override
{
auto blockingReason = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper);
@ -188,59 +200,83 @@ protected:
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) = 0;
CPathfinderHelper * pathfinderHelper) const = 0;
};
class CMovementAfterDestinationRule : public CPathfinderBlockingRule
class DLL_LINKAGE CMovementAfterDestinationRule : public CPathfinderBlockingRule
{
public:
virtual void process(
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) override;
CPathfinderHelper * pathfinderHelper) const override;
protected:
virtual BlockingReason getBlockingReason(
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) override;
CPathfinderHelper * pathfinderHelper) const override;
};
class CMovementToDestinationRule : public CPathfinderBlockingRule
class DLL_LINKAGE CMovementToDestinationRule : public CPathfinderBlockingRule
{
protected:
virtual BlockingReason getBlockingReason(
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) override;
CPathfinderHelper * pathfinderHelper) const override;
};
class CNeighbourFinder
class INodeStorage
{
public:
CNeighbourFinder();
virtual CGPathNode * getInitialNode() = 0;
virtual void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility) = 0;
virtual std::vector<CGPathNode *> calculateNeighbours(
CPathNodeInfo & source,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const;
CPathfinderHelper * pathfinderHelper) = 0;
virtual std::vector<CGPathNode *> calculateTeleportations(
CPathNodeInfo & source,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const;
CPathfinderHelper * pathfinderHelper) = 0;
protected:
std::vector<int3> getNeighbourTiles(
CPathNodeInfo & source,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const;
virtual void commit(CDestinationNodeInfo & destination, CPathNodeInfo & source) = 0;
};
std::vector<int3> getTeleportExits(CPathNodeInfo & source,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const;
class DLL_LINKAGE CNodeStorage : public INodeStorage
{
private:
CPathsInfo & out;
public:
CNodeStorage(CPathsInfo & pathsInfo, const CGHeroInstance * hero);
CGPathNode * getNode(const int3 & coord, const EPathfindingLayer layer);
virtual CGPathNode * getInitialNode() override;
virtual std::vector<CGPathNode *> calculateNeighbours(
CPathNodeInfo & source,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) override;
virtual std::vector<CGPathNode *> calculateTeleportations(
CPathNodeInfo & source,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) override;
virtual void resetTile(
const int3 & tile,
EPathfindingLayer layer,
CGPathNode::EAccessibility accessibility) override;
virtual void commit(CDestinationNodeInfo & destination, CPathNodeInfo & source) override;
};
struct DLL_LINKAGE PathfinderOptions
@ -294,17 +330,15 @@ struct DLL_LINKAGE PathfinderOptions
PathfinderOptions();
};
class CPathfinderConfig
class DLL_LINKAGE CPathfinderConfig
{
public:
std::shared_ptr<CNodeStorage> nodeStorage;
std::shared_ptr<CNeighbourFinder> neighbourFinder;
std::shared_ptr<INodeStorage> nodeStorage;
std::vector<std::shared_ptr<IPathfindingRule>> rules;
PathfinderOptions options;
CPathfinderConfig(
std::shared_ptr<CNodeStorage> nodeStorage,
std::shared_ptr<CNeighbourFinder> neighbourFinder,
std::shared_ptr<INodeStorage> nodeStorage,
std::vector<std::shared_ptr<IPathfindingRule>> rules);
};
@ -357,8 +391,6 @@ private:
bool isPatrolMovementAllowed(const int3 & dst) const;
bool isLayerTransitionPossible(const ELayer dstLayer) const;
bool isLayerTransitionPossible() const;
CGPathNode::ENodeAction getDestAction() const;
CGPathNode::ENodeAction getTeleportDestAction() const;
bool isSourceInitialPosition() const;
@ -417,7 +449,7 @@ public:
bool hasBonusOfType(const Bonus::BonusType type, const int subtype = -1) const;
int getMaxMovePoints(const EPathfindingLayer layer) const;
std::vector<int3> CPathfinderHelper::getCastleGates(CPathNodeInfo & source) const;
std::vector<int3> getCastleGates(CPathNodeInfo & source) const;
bool isAllowedTeleportEntrance(const CGTeleport * obj) const;
std::vector<int3> getAllowedTeleportChannelExits(TeleportChannelID channelID) const;
bool addTeleportTwoWay(const CGTeleport * obj) const;
@ -426,7 +458,15 @@ public:
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)
void getNeighbours(const TerrainTile & srct, const int3 & tile, std::vector<int3> & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing);
std::vector<int3> getNeighbourTiles(CPathNodeInfo & source) const;
std::vector<int3> getTeleportExits(CPathNodeInfo & source) const;
void getNeighbours(
const TerrainTile & srct,
const int3 & tile,
std::vector<int3> & vec,
const boost::logic::tribool & onLand,
const bool limitCoastSailing) const;
int getMovementCost(
const int3 & src,
@ -434,13 +474,13 @@ public:
const TerrainTile * ct,
const TerrainTile * dt,
const int remainingMovePoints =- 1,
const bool checkLast = true);
const bool checkLast = true) const;
int getMovementCost(
const CPathNodeInfo & src,
const CPathNodeInfo & dst,
const int remainingMovePoints = -1,
const bool checkLast = true)
const bool checkLast = true) const
{
return getMovementCost(
src.coord,
@ -452,7 +492,7 @@ public:
);
}
int getHeroMaxMovementPoints(EPathfindingLayer layer);
int movementPointsAfterEmbark(int movement, int cost, int action);
bool passOneTurnLimitCheck(CPathNodeInfo & source);
int getHeroMaxMovementPoints(EPathfindingLayer layer) const;
int movementPointsAfterEmbark(int movement, int cost, int action) const;
bool passOneTurnLimitCheck(CPathNodeInfo & source) const;
};