1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-15 20:03:15 +02:00

AI: try to add some pathfinder extensibility. Extracted neighbour nodes finder

This commit is contained in:
Andrii Danylchenko
2018-07-29 14:50:22 +03:00
parent 57b687a094
commit 9af0032f84
2 changed files with 378 additions and 211 deletions

View File

@@ -19,6 +19,86 @@
#include "CConfigHandler.h" #include "CConfigHandler.h"
#include "../lib/CPlayerState.h" #include "../lib/CPlayerState.h"
bool canSeeObj(const CGObjectInstance * obj)
{
/// Pathfinder should ignore placed events
return obj != nullptr && obj->ID != Obj::EVENT;
}
CNeighbourFinder::CNeighbourFinder(CPathfinder * pathfinder)
:pathfinder(pathfinder), neighbours(), neighbourTiles(), accessibleNeighbourTiles()
{
}
std::vector<CGPathNode *> & CNeighbourFinder::calculateNeighbours()
{
addNeighbourTiles();
for(auto & neighbour : accessibleNeighbourTiles)
{
for(EPathfindingLayer i = EPathfindingLayer::LAND; i <= EPathfindingLayer::AIR; i.advance(1))
{
auto node = pathfinder->nodeHelper->getNode(neighbour, i);
if(node->accessible == CGPathNode::NOT_SET)
continue;
neighbours.push_back(node);
}
}
return neighbours;
}
void CNeighbourFinder::addNeighbourTiles()
{
neighbourTiles.clear();
accessibleNeighbourTiles.clear();
neighbours.clear();
pathfinder->populateNeighbourTiles(neighbourTiles);
if(pathfinder->source.isNodeObjectVisitable())
{
for(int3 tile : neighbourTiles)
{
if(pathfinder->canMoveBetween(tile, pathfinder->source.nodeObject->visitablePos()))
accessibleNeighbourTiles.push_back(tile);
}
}
else
vstd::concatenate(accessibleNeighbourTiles, neighbourTiles);
}
class CPathfinderNodeHelper : public CNodeHelper
{
private:
CPathsInfo & out;
public:
CPathfinderNodeHelper(CPathsInfo & pathsInfo, const CGHeroInstance * hero)
:out(pathsInfo)
{
out.hero = hero;
out.hpos = hero->getPosition(false);
}
virtual CGPathNode * getNode(const int3 & coord, const EPathfindingLayer layer)
{
return out.getNode(coord, layer);
}
virtual CGPathNode * getInitialNode()
{
auto initialNode = getNode(out.hpos, out.hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND);
initialNode->turns = 0;
initialNode->moveRemains = out.hero->movement;
return initialNode;
}
};
CPathfinder::PathfinderOptions::PathfinderOptions() CPathfinder::PathfinderOptions::PathfinderOptions()
{ {
useFlying = settings["pathfinder"]["layers"]["flying"].Bool(); useFlying = settings["pathfinder"]["layers"]["flying"].Bool();
@@ -36,25 +116,37 @@ CPathfinder::PathfinderOptions::PathfinderOptions()
originalMovementRules = settings["pathfinder"]["originalMovementRules"].Bool(); originalMovementRules = settings["pathfinder"]["originalMovementRules"].Bool();
} }
CPathfinder::CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstance * _hero)
: CGameInfoCallback(_gs, boost::optional<PlayerColor>()), out(_out), hero(_hero), FoW(getPlayerTeam(hero->tempOwner)->fogOfWarMap), patrolTiles({}) CPathfinder::CPathfinder(
CPathsInfo & _out,
CGameState * _gs,
const CGHeroInstance * _hero)
:CPathfinder(
_gs,
_hero,
std::make_shared<CPathfinderNodeHelper>(_out, _hero),
std::make_shared<CNeighbourFinder>(this))
{
}
CPathfinder::CPathfinder(
CGameState * _gs,
const CGHeroInstance * _hero,
std::shared_ptr<CNodeHelper> nodeHelper,
std::shared_ptr<CNeighbourFinder> neighbourFinder)
: CGameInfoCallback(_gs, boost::optional<PlayerColor>())
, hero(_hero)
, FoW(getPlayerTeam(hero->tempOwner)->fogOfWarMap), patrolTiles({})
, nodeHelper(nodeHelper)
, neighbourFinder(neighbourFinder)
, source()
, destination()
{ {
assert(hero); assert(hero);
assert(hero == getHero(hero->id)); assert(hero == getHero(hero->id));
cp = dp = nullptr;
ct = dt = nullptr;
ctObj = dtObj = nullptr;
destAction = CGPathNode::UNKNOWN; destAction = CGPathNode::UNKNOWN;
out.hero = hero;
out.hpos = hero->getPosition(false);
if(!isInTheMap(out.hpos)/* || !gs->map->isInTheMap(dest)*/) //check input
{
logGlobal->error("CGameState::calculatePaths: Hero outside the gs->map? How dare you...");
throw std::runtime_error("Wrong checksum");
}
hlp = make_unique<CPathfinderHelper>(hero, options); hlp = make_unique<CPathfinderHelper>(hero, options);
initializePatrol(); initializePatrol();
@@ -70,11 +162,11 @@ void CPathfinder::calculatePaths()
if(!options.oneTurnSpecialLayersLimit) if(!options.oneTurnSpecialLayersLimit)
return true; return true;
if(cp->layer == ELayer::WATER) if(source.node->layer == ELayer::WATER)
return false; return false;
if(cp->layer == ELayer::AIR) if(source.node->layer == ELayer::AIR)
{ {
if(options.originalMovementRules && cp->accessible == CGPathNode::ACCESSIBLE) if(options.originalMovementRules && source.node->accessible == CGPathNode::ACCESSIBLE)
return true; return true;
else else
return false; return false;
@@ -85,11 +177,11 @@ void CPathfinder::calculatePaths()
auto isBetterWay = [&](int remains, int turn) -> bool auto isBetterWay = [&](int remains, int turn) -> bool
{ {
if(dp->turns == 0xff) //we haven't been here before if(destination.node->turns == 0xff) //we haven't been here before
return true; return true;
else if(dp->turns > turn) else if(destination.node->turns > turn)
return true; return true;
else if(dp->turns >= turn && dp->moveRemains < remains) //this route is faster else if(destination.node->turns >= turn && destination.node->moveRemains < remains) //this route is faster
return true; return true;
return false; return false;
@@ -98,58 +190,58 @@ void CPathfinder::calculatePaths()
//logGlobal->info("Calculating paths for hero %s (adress %d) of player %d", hero->name, hero , hero->tempOwner); //logGlobal->info("Calculating paths for hero %s (adress %d) of player %d", hero->name, hero , hero->tempOwner);
//initial tile - set cost on 0 and add to the queue //initial tile - set cost on 0 and add to the queue
CGPathNode * initialNode = out.getNode(out.hpos, hero->boat ? ELayer::SAIL : ELayer::LAND); CGPathNode * initialNode = nodeHelper->getInitialNode();
initialNode->turns = 0;
initialNode->moveRemains = hero->movement; if(!isInTheMap(initialNode->coord)/* || !gs->map->isInTheMap(dest)*/) //check input
{
logGlobal->error("CGameState::calculatePaths: Hero outside the gs->map? How dare you...");
throw std::runtime_error("Wrong checksum");
}
if(isHeroPatrolLocked()) if(isHeroPatrolLocked())
return; return;
pq.push(initialNode); pq.push(initialNode);
while(!pq.empty()) while(!pq.empty())
{ {
cp = pq.top(); auto node = pq.top();
pq.pop(); auto excludeOurHero = node->coord == initialNode->coord;
cp->locked = true;
int movement = cp->moveRemains, turn = cp->turns; source.setNode(gs, node, excludeOurHero);
pq.pop();
source.node->locked = true;
int movement = source.node->moveRemains, turn = source.node->turns;
hlp->updateTurnInfo(turn); hlp->updateTurnInfo(turn);
if(!movement) if(!movement)
{ {
hlp->updateTurnInfo(++turn); hlp->updateTurnInfo(++turn);
movement = hlp->getMaxMovePoints(cp->layer); movement = hlp->getMaxMovePoints(source.node->layer);
if(!passOneTurnLimitCheck()) if(!passOneTurnLimitCheck())
continue; continue;
} }
ct = &gs->map->getTile(cp->coord);
ctObj = ct->topVisitableObj(isSourceInitialPosition());
//add accessible neighbouring nodes to the queue //add accessible neighbouring nodes to the queue
addNeighbours(); auto neighbourNodes = neighbourFinder->calculateNeighbours();
for(auto & neighbour : neighbours) for(auto & neighbour : neighbourNodes)
{ {
if(!isPatrolMovementAllowed(neighbour)) destination.setNode(gs, neighbour);
if(destination.node->locked)
continue; continue;
dt = &gs->map->getTile(neighbour); if(!isPatrolMovementAllowed(destination.node->coord))
dtObj = dt->topVisitableObj(); continue;
for(ELayer i = ELayer::LAND; i <= ELayer::AIR; i.advance(1))
{ if(!hlp->isLayerAvailable(destination.node->layer))
if(!hlp->isLayerAvailable(i))
continue; continue;
/// Check transition without tile accessability rules /// Check transition without tile accessability rules
if(cp->layer != i && !isLayerTransitionPossible(i)) if(source.node->layer != destination.node->layer && !isLayerTransitionPossible(destination.node->layer))
continue;
dp = out.getNode(neighbour, i);
if(dp->locked)
continue;
if(dp->accessible == CGPathNode::NOT_SET)
continue; continue;
/// Check transition using tile accessability rules /// Check transition using tile accessability rules
if(cp->layer != i && !isLayerTransitionPossible()) if(source.node->layer != destination.node->layer && !isLayerTransitionPossible())
continue; continue;
if(!isMovementToDestPossible()) if(!isMovementToDestPossible())
@@ -157,14 +249,14 @@ void CPathfinder::calculatePaths()
destAction = getDestAction(); destAction = getDestAction();
int turnAtNextTile = turn, moveAtNextTile = movement; int turnAtNextTile = turn, moveAtNextTile = movement;
int cost = CPathfinderHelper::getMovementCost(hero, cp->coord, dp->coord, ct, dt, moveAtNextTile, hlp->getTurnInfo()); int cost = CPathfinderHelper::getMovementCost(hero, source, destination, moveAtNextTile, hlp->getTurnInfo());
int remains = moveAtNextTile - cost; int remains = moveAtNextTile - cost;
if(remains < 0) if(remains < 0)
{ {
//occurs rarely, when hero with low movepoints tries to leave the road //occurs rarely, when hero with low movepoints tries to leave the road
hlp->updateTurnInfo(++turnAtNextTile); hlp->updateTurnInfo(++turnAtNextTile);
moveAtNextTile = hlp->getMaxMovePoints(i); moveAtNextTile = hlp->getMaxMovePoints(destination.node->layer);
cost = CPathfinderHelper::getMovementCost(hero, cp->coord, dp->coord, ct, dt, moveAtNextTile, hlp->getTurnInfo()); //cost must be updated, movement points changed :( cost = CPathfinderHelper::getMovementCost(hero, source, destination, moveAtNextTile, hlp->getTurnInfo()); //cost must be updated, movement points changed :(
remains = moveAtNextTile - cost; remains = moveAtNextTile - cost;
} }
if(destAction == CGPathNode::EMBARK || destAction == CGPathNode::DISEMBARK) if(destAction == CGPathNode::EMBARK || destAction == CGPathNode::DISEMBARK)
@@ -176,17 +268,18 @@ void CPathfinder::calculatePaths()
} }
if(isBetterWay(remains, turnAtNextTile) && if(isBetterWay(remains, turnAtNextTile) &&
((cp->turns == turnAtNextTile && remains) || passOneTurnLimitCheck())) ((source.node->turns == turnAtNextTile && remains) || passOneTurnLimitCheck()))
{ {
assert(dp != cp->theNodeBefore); //two tiles can't point to each other assert(dp != source.node->theNodeBefore); //two tiles can't point to each other
dp->moveRemains = remains; destination.node->moveRemains = remains;
dp->turns = turnAtNextTile; destination.node->turns = turnAtNextTile;
dp->theNodeBefore = cp; destination.node->theNodeBefore = source.node;
dp->action = destAction; destination.node->action = destAction;
if(isMovementAfterDestPossible()) // TODO: move this method to a separete module kind of IPathfinderRule.process(). And all above.
pq.push(dp); checkMovementAfterDestPossible();
} if(!destination.furtherProcessingImpossible)
pq.push(destination.node);
} }
} //neighbours loop } //neighbours loop
@@ -194,47 +287,41 @@ void CPathfinder::calculatePaths()
addTeleportExits(); addTeleportExits();
for(auto & neighbour : neighbours) for(auto & neighbour : neighbours)
{ {
dp = out.getNode(neighbour, cp->layer); auto teleportNode = nodeHelper->getNode(neighbour, source.node->layer);
if(dp->locked) if(teleportNode->locked)
continue; continue;
/// TODO: We may consider use invisible exits on FoW border in future /// TODO: We may consider use invisible exits on FoW border in future
/// Useful for AI when at least one tile around exit is visible and passable /// Useful for AI when at least one tile around exit is visible and passable
/// Objects are usually visible on FoW border anyway so it's not cheating. /// Objects are usually visible on FoW border anyway so it's not cheating.
/// ///
/// For now it's disabled as it's will cause crashes in movement code. /// For now it's disabled as it's will cause crashes in movement code.
if(dp->accessible == CGPathNode::BLOCKED) if(teleportNode->accessible == CGPathNode::BLOCKED)
continue; continue;
destination.setNode(gs, teleportNode);
if(isBetterWay(movement, turn)) if(isBetterWay(movement, turn))
{ {
dtObj = gs->map->getTile(neighbour).topVisitableObj(); destination.node->moveRemains = movement;
destination.node->turns = turn;
dp->moveRemains = movement; destination.node->theNodeBefore = source.node;
dp->turns = turn; destination.node->action = getTeleportDestAction();
dp->theNodeBefore = cp; if(destination.node->action == CGPathNode::TELEPORT_NORMAL)
dp->action = getTeleportDestAction(); pq.push(destination.node);
if(dp->action == CGPathNode::TELEPORT_NORMAL)
pq.push(dp);
} }
} }
} //queue loop } //queue loop
} }
void CPathfinder::addNeighbours() void CPathfinder::populateNeighbourTiles(std::vector<int3> & neighbourTiles)
{ {
neighbours.clear(); CPathfinderHelper::getNeighbours(
neighbourTiles.clear(); gs->map,
CPathfinderHelper::getNeighbours(gs->map, *ct, cp->coord, neighbourTiles, boost::logic::indeterminate, cp->layer == ELayer::SAIL); *source.tile,
if(isSourceVisitableObj()) source.node->coord,
{ neighbourTiles,
for(int3 tile: neighbourTiles) boost::logic::indeterminate,
{ source.node->layer == EPathfindingLayer::SAIL);
if(canMoveBetween(tile, ctObj->visitablePos()))
neighbours.push_back(tile);
}
}
else
vstd::concatenate(neighbours, neighbourTiles);
} }
void CPathfinder::addTeleportExits() void CPathfinder::addTeleportExits()
@@ -242,10 +329,10 @@ void CPathfinder::addTeleportExits()
neighbours.clear(); neighbours.clear();
/// For now we disable teleports usage for patrol movement /// For now we disable teleports usage for patrol movement
/// VCAI not aware about patrol and may stuck while attempt to use teleport /// VCAI not aware about patrol and may stuck while attempt to use teleport
if(!isSourceVisitableObj() || patrolState == PATROL_RADIUS) if(!source.isNodeObjectVisitable() || patrolState == PATROL_RADIUS)
return; return;
const CGTeleport * objTeleport = dynamic_cast<const CGTeleport *>(ctObj); const CGTeleport * objTeleport = dynamic_cast<const CGTeleport *>(source.nodeObject);
if(isAllowedTeleportEntrance(objTeleport)) if(isAllowedTeleportEntrance(objTeleport))
{ {
for(auto objId : getTeleportChannelExits(objTeleport->channel, hero->tempOwner)) for(auto objId : getTeleportChannelExits(objTeleport->channel, hero->tempOwner))
@@ -266,15 +353,15 @@ void CPathfinder::addTeleportExits()
} }
if(options.useCastleGate if(options.useCastleGate
&& (ctObj->ID == Obj::TOWN && ctObj->subID == ETownType::INFERNO && (source.nodeObject->ID == Obj::TOWN && source.nodeObject->subID == ETownType::INFERNO
&& getPlayerRelations(hero->tempOwner, ctObj->tempOwner) != PlayerRelations::ENEMIES)) && getPlayerRelations(hero->tempOwner, source.nodeObject->tempOwner) != PlayerRelations::ENEMIES))
{ {
/// TODO: Find way to reuse CPlayerSpecificInfoCallback::getTownsInfo /// TODO: Find way to reuse CPlayerSpecificInfoCallback::getTownsInfo
/// This may be handy if we allow to use teleportation to friendly towns /// This may be handy if we allow to use teleportation to friendly towns
auto towns = getPlayer(hero->tempOwner)->towns; auto towns = getPlayer(hero->tempOwner)->towns;
for(const auto & town : towns) for(const auto & town : towns)
{ {
if(town->id != ctObj->id && town->visitingHero == nullptr if(town->id != source.nodeObject->id && town->visitingHero == nullptr
&& town->hasBuilt(BuildingID::CASTLE_GATE, ETownType::INFERNO)) && town->hasBuilt(BuildingID::CASTLE_GATE, ETownType::INFERNO))
{ {
neighbours.push_back(town->visitablePos()); neighbours.push_back(town->visitablePos());
@@ -302,10 +389,10 @@ bool CPathfinder::isPatrolMovementAllowed(const int3 & dst) const
bool CPathfinder::isLayerTransitionPossible(const ELayer destLayer) const bool CPathfinder::isLayerTransitionPossible(const ELayer destLayer) const
{ {
/// No layer transition allowed when previous node action is BATTLE /// No layer transition allowed when previous node action is BATTLE
if(cp->action == CGPathNode::BATTLE) if(source.node->action == CGPathNode::BATTLE)
return false; return false;
switch(cp->layer) switch(source.node->layer)
{ {
case ELayer::LAND: case ELayer::LAND:
if(destLayer == ELayer::AIR) if(destLayer == ELayer::AIR)
@@ -315,7 +402,7 @@ bool CPathfinder::isLayerTransitionPossible(const ELayer destLayer) const
} }
else if(destLayer == ELayer::SAIL) else if(destLayer == ELayer::SAIL)
{ {
if(dt->isWater()) if(destination.tile->isWater())
return true; return true;
} }
else else
@@ -324,7 +411,7 @@ bool CPathfinder::isLayerTransitionPossible(const ELayer destLayer) const
break; break;
case ELayer::SAIL: case ELayer::SAIL:
if(destLayer == ELayer::LAND && !dt->isWater()) if(destLayer == ELayer::LAND && !destination.tile->isWater())
return true; return true;
break; break;
@@ -347,13 +434,13 @@ bool CPathfinder::isLayerTransitionPossible(const ELayer destLayer) const
bool CPathfinder::isLayerTransitionPossible() const bool CPathfinder::isLayerTransitionPossible() const
{ {
switch(cp->layer) switch(source.node->layer)
{ {
case ELayer::LAND: case ELayer::LAND:
if(dp->layer == ELayer::SAIL) if(destination.node->layer == ELayer::SAIL)
{ {
/// Cannot enter empty water tile from land -> it has to be visitable /// Cannot enter empty water tile from land -> it has to be visitable
if(dp->accessible == CGPathNode::ACCESSIBLE) if(destination.node->accessible == CGPathNode::ACCESSIBLE)
return false; return false;
} }
@@ -361,8 +448,8 @@ bool CPathfinder::isLayerTransitionPossible() const
case ELayer::SAIL: case ELayer::SAIL:
//tile must be accessible -> exception: unblocked blockvis tiles -> clear but guarded by nearby monster coast //tile must be accessible -> exception: unblocked blockvis tiles -> clear but guarded by nearby monster coast
if((dp->accessible != CGPathNode::ACCESSIBLE && (dp->accessible != CGPathNode::BLOCKVIS || dt->blocked)) if((destination.node->accessible != CGPathNode::ACCESSIBLE && (destination.node->accessible != CGPathNode::BLOCKVIS || destination.tile->blocked))
|| dt->visitable) //TODO: passableness problem -> town says it's passable (thus accessible) but we obviously can't disembark onto town gate || destination.tile->visitable) //TODO: passableness problem -> town says it's passable (thus accessible) but we obviously can't disembark onto town gate
{ {
return false; return false;
} }
@@ -372,15 +459,15 @@ bool CPathfinder::isLayerTransitionPossible() const
case ELayer::AIR: case ELayer::AIR:
if(options.originalMovementRules) if(options.originalMovementRules)
{ {
if((cp->accessible != CGPathNode::ACCESSIBLE && if((source.node->accessible != CGPathNode::ACCESSIBLE &&
cp->accessible != CGPathNode::VISITABLE) && source.node->accessible != CGPathNode::VISITABLE) &&
(dp->accessible != CGPathNode::VISITABLE && (destination.node->accessible != CGPathNode::VISITABLE &&
dp->accessible != CGPathNode::ACCESSIBLE)) destination.node->accessible != CGPathNode::ACCESSIBLE))
{ {
return false; return false;
} }
} }
else if(cp->accessible != CGPathNode::ACCESSIBLE && dp->accessible != CGPathNode::ACCESSIBLE) else if(source.node->accessible != CGPathNode::ACCESSIBLE && destination.node->accessible != CGPathNode::ACCESSIBLE)
{ {
/// Hero that fly can only land on accessible tiles /// Hero that fly can only land on accessible tiles
return false; return false;
@@ -389,7 +476,7 @@ bool CPathfinder::isLayerTransitionPossible() const
break; break;
case ELayer::WATER: case ELayer::WATER:
if(dp->accessible != CGPathNode::ACCESSIBLE && dp->accessible != CGPathNode::VISITABLE) if(destination.node->accessible != CGPathNode::ACCESSIBLE && destination.node->accessible != CGPathNode::VISITABLE)
{ {
/// Hero that walking on water can transit to accessible and visitable tiles /// 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 /// Though hero can't interact with blocking visit objects while standing on water
@@ -404,17 +491,17 @@ bool CPathfinder::isLayerTransitionPossible() const
bool CPathfinder::isMovementToDestPossible() const bool CPathfinder::isMovementToDestPossible() const
{ {
if(dp->accessible == CGPathNode::BLOCKED) if(destination.node->accessible == CGPathNode::BLOCKED)
return false; return false;
switch(dp->layer) switch(destination.node->layer)
{ {
case ELayer::LAND: case ELayer::LAND:
if(!canMoveBetween(cp->coord, dp->coord)) if(!canMoveBetween(source.node->coord, destination.node->coord))
return false; return false;
if(isSourceGuarded()) if(isSourceGuarded())
{ {
if(!(options.originalMovementRules && cp->layer == ELayer::AIR) && if(!(options.originalMovementRules && source.node->layer == ELayer::AIR) &&
!isDestinationGuardian()) // Can step into tile of guard !isDestinationGuardian()) // Can step into tile of guard
{ {
return false; return false;
@@ -424,24 +511,24 @@ bool CPathfinder::isMovementToDestPossible() const
break; break;
case ELayer::SAIL: case ELayer::SAIL:
if(!canMoveBetween(cp->coord, dp->coord)) if(!canMoveBetween(source.node->coord, destination.node->coord))
return false; return false;
if(isSourceGuarded()) if(isSourceGuarded())
{ {
// Hero embarked a boat standing on a guarded tile -> we must allow to move away from that tile // Hero embarked a boat standing on a guarded tile -> we must allow to move away from that tile
if(cp->action != CGPathNode::EMBARK && !isDestinationGuardian()) if(source.node->action != CGPathNode::EMBARK && !isDestinationGuardian())
return false; return false;
} }
if(cp->layer == ELayer::LAND) if(source.node->layer == ELayer::LAND)
{ {
if(!isDestVisitableObj()) if(!destination.isNodeObjectVisitable())
return false; return false;
if(dtObj->ID != Obj::BOAT && dtObj->ID != Obj::HERO) if(destination.nodeObject->ID != Obj::BOAT && destination.nodeObject->ID != Obj::HERO)
return false; return false;
} }
else if(isDestVisitableObj() && dtObj->ID == Obj::BOAT) else if(destination.isNodeObjectVisitable() && destination.nodeObject->ID == Obj::BOAT)
{ {
/// Hero in boat can't visit empty boats /// Hero in boat can't visit empty boats
return false; return false;
@@ -450,7 +537,7 @@ bool CPathfinder::isMovementToDestPossible() const
break; break;
case ELayer::WATER: case ELayer::WATER:
if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible != CGPathNode::ACCESSIBLE) if(!canMoveBetween(source.node->coord, destination.node->coord) || destination.node->accessible != CGPathNode::ACCESSIBLE)
return false; return false;
if(isDestinationGuarded()) if(isDestinationGuarded())
return false; return false;
@@ -461,7 +548,7 @@ bool CPathfinder::isMovementToDestPossible() const
return true; return true;
} }
bool CPathfinder::isMovementAfterDestPossible() const void CPathfinder::checkMovementAfterDestPossible()
{ {
switch(destAction) switch(destAction)
{ {
@@ -471,55 +558,57 @@ bool CPathfinder::isMovementAfterDestPossible() const
{ {
/// For now we only add visitable tile into queue when it's teleporter that allow transit /// For now we only add visitable tile into queue when it's teleporter that allow transit
/// Movement from visitable tile when hero is standing on it is possible into any layer /// Movement from visitable tile when hero is standing on it is possible into any layer
const CGTeleport * objTeleport = dynamic_cast<const CGTeleport *>(dtObj); const CGTeleport * objTeleport = dynamic_cast<const CGTeleport *>(destination.nodeObject);
if(isAllowedTeleportEntrance(objTeleport)) if(isAllowedTeleportEntrance(objTeleport))
{ {
/// For now we'll always allow transit over teleporters /// For now we'll always allow transit over teleporters
/// Transit over whirlpools only allowed when hero protected /// Transit over whirlpools only allowed when hero protected
return true; return;
} }
else if(dtObj->ID == Obj::GARRISON || dtObj->ID == Obj::GARRISON2 || dtObj->ID == Obj::BORDER_GATE) else if(destination.nodeObject->ID == Obj::GARRISON
|| destination.nodeObject->ID == Obj::GARRISON2
|| destination.nodeObject->ID == Obj::BORDER_GATE)
{ {
/// Transit via unguarded garrisons is always possible /// Transit via unguarded garrisons is always possible
return true; return;
} }
break; break;
} }
case CGPathNode::NORMAL: case CGPathNode::NORMAL:
return true; return;
case CGPathNode::EMBARK: case CGPathNode::EMBARK:
if(options.useEmbarkAndDisembark) if(options.useEmbarkAndDisembark)
return true; return;
break; break;
case CGPathNode::DISEMBARK: case CGPathNode::DISEMBARK:
if(options.useEmbarkAndDisembark && !isDestinationGuarded()) if(options.useEmbarkAndDisembark && !isDestinationGuarded())
return true; return;
break; break;
case CGPathNode::BATTLE: case CGPathNode::BATTLE:
/// Movement after BATTLE action only possible from guarded tile to guardian tile /// Movement after BATTLE action only possible from guarded tile to guardian tile
if(isDestinationGuarded()) if(isDestinationGuarded())
return true; return;
break; break;
} }
return false; destination.furtherProcessingImpossible = true;
} }
CGPathNode::ENodeAction CPathfinder::getDestAction() const CGPathNode::ENodeAction CPathfinder::getDestAction() const
{ {
CGPathNode::ENodeAction action = CGPathNode::NORMAL; CGPathNode::ENodeAction action = CGPathNode::NORMAL;
switch(dp->layer) switch(destination.node->layer)
{ {
case ELayer::LAND: case ELayer::LAND:
if(cp->layer == ELayer::SAIL) if(source.node->layer == ELayer::SAIL)
{ {
// TODO: Handle dismebark into guarded areaa // TODO: Handle dismebark into guarded areaa
action = CGPathNode::DISEMBARK; action = CGPathNode::DISEMBARK;
@@ -530,29 +619,29 @@ CGPathNode::ENodeAction CPathfinder::getDestAction() const
FALLTHROUGH FALLTHROUGH
case ELayer::SAIL: case ELayer::SAIL:
if(isDestVisitableObj()) if(destination.isNodeObjectVisitable())
{ {
auto objRel = getPlayerRelations(dtObj->tempOwner, hero->tempOwner); auto objRel = getPlayerRelations(destination.nodeObject->tempOwner, hero->tempOwner);
if(dtObj->ID == Obj::BOAT) if(destination.nodeObject->ID == Obj::BOAT)
action = CGPathNode::EMBARK; action = CGPathNode::EMBARK;
else if(dtObj->ID == Obj::HERO) else if(destination.nodeObject->ID == Obj::HERO)
{ {
if(objRel == PlayerRelations::ENEMIES) if(objRel == PlayerRelations::ENEMIES)
action = CGPathNode::BATTLE; action = CGPathNode::BATTLE;
else else
action = CGPathNode::BLOCKING_VISIT; action = CGPathNode::BLOCKING_VISIT;
} }
else if(dtObj->ID == Obj::TOWN) else if(destination.nodeObject->ID == Obj::TOWN)
{ {
if(dtObj->passableFor(hero->tempOwner)) if(destination.nodeObject->passableFor(hero->tempOwner))
action = CGPathNode::VISIT; action = CGPathNode::VISIT;
else if(objRel == PlayerRelations::ENEMIES) else if(objRel == PlayerRelations::ENEMIES)
action = CGPathNode::BATTLE; action = CGPathNode::BATTLE;
} }
else if(dtObj->ID == Obj::GARRISON || dtObj->ID == Obj::GARRISON2) else if(destination.nodeObject->ID == Obj::GARRISON || destination.nodeObject->ID == Obj::GARRISON2)
{ {
if(dtObj->passableFor(hero->tempOwner)) if(destination.nodeObject->passableFor(hero->tempOwner))
{ {
if(isDestinationGuarded(true)) if(isDestinationGuarded(true))
action = CGPathNode::BATTLE; action = CGPathNode::BATTLE;
@@ -560,9 +649,9 @@ CGPathNode::ENodeAction CPathfinder::getDestAction() const
else if(objRel == PlayerRelations::ENEMIES) else if(objRel == PlayerRelations::ENEMIES)
action = CGPathNode::BATTLE; action = CGPathNode::BATTLE;
} }
else if(dtObj->ID == Obj::BORDER_GATE) else if(destination.nodeObject->ID == Obj::BORDER_GATE)
{ {
if(dtObj->passableFor(hero->tempOwner)) if(destination.nodeObject->passableFor(hero->tempOwner))
{ {
if(isDestinationGuarded(true)) if(isDestinationGuarded(true))
action = CGPathNode::BATTLE; action = CGPathNode::BATTLE;
@@ -572,7 +661,7 @@ CGPathNode::ENodeAction CPathfinder::getDestAction() const
} }
else if(isDestinationGuardian()) else if(isDestinationGuardian())
action = CGPathNode::BATTLE; action = CGPathNode::BATTLE;
else if(dtObj->blockVisit && !(options.useCastleGate && dtObj->ID == Obj::TOWN)) else if(destination.nodeObject->blockVisit && !(options.useCastleGate && destination.nodeObject->ID == Obj::TOWN))
action = CGPathNode::BLOCKING_VISIT; action = CGPathNode::BLOCKING_VISIT;
if(action == CGPathNode::NORMAL) if(action == CGPathNode::NORMAL)
@@ -595,9 +684,9 @@ CGPathNode::ENodeAction CPathfinder::getDestAction() const
CGPathNode::ENodeAction CPathfinder::getTeleportDestAction() const CGPathNode::ENodeAction CPathfinder::getTeleportDestAction() const
{ {
CGPathNode::ENodeAction action = CGPathNode::TELEPORT_NORMAL; CGPathNode::ENodeAction action = CGPathNode::TELEPORT_NORMAL;
if(isDestVisitableObj() && dtObj->ID == Obj::HERO) if(destination.isNodeObjectVisitable() && destination.nodeObject->ID == Obj::HERO)
{ {
auto objRel = getPlayerRelations(dtObj->tempOwner, hero->tempOwner); auto objRel = getPlayerRelations(destination.nodeObject->tempOwner, hero->tempOwner);
if(objRel == PlayerRelations::ENEMIES) if(objRel == PlayerRelations::ENEMIES)
action = CGPathNode::TELEPORT_BATTLE; action = CGPathNode::TELEPORT_BATTLE;
else else
@@ -609,12 +698,7 @@ CGPathNode::ENodeAction CPathfinder::getTeleportDestAction() const
bool CPathfinder::isSourceInitialPosition() const bool CPathfinder::isSourceInitialPosition() const
{ {
return cp->coord == out.hpos; return source.node->coord == nodeHelper->getInitialNode()->coord;
}
bool CPathfinder::isSourceVisitableObj() const
{
return isVisitableObj(ctObj, cp->layer);
} }
bool CPathfinder::isSourceGuarded() const bool CPathfinder::isSourceGuarded() const
@@ -624,7 +708,7 @@ bool CPathfinder::isSourceGuarded() const
/// - Map start with hero on guarded tile /// - Map start with hero on guarded tile
/// - Dimention door used /// - Dimention door used
/// TODO: check what happen when there is several guards /// TODO: check what happen when there is several guards
if(gs->guardingCreaturePosition(cp->coord).valid() && !isSourceInitialPosition()) if(gs->guardingCreaturePosition(source.node->coord).valid() && !isSourceInitialPosition())
{ {
return true; return true;
} }
@@ -632,17 +716,12 @@ bool CPathfinder::isSourceGuarded() const
return false; return false;
} }
bool CPathfinder::isDestVisitableObj() const
{
return isVisitableObj(dtObj, dp->layer);
}
bool CPathfinder::isDestinationGuarded(const bool ignoreAccessibility) const bool CPathfinder::isDestinationGuarded(const bool ignoreAccessibility) const
{ {
/// isDestinationGuarded is exception needed for garrisons. /// isDestinationGuarded is exception needed for garrisons.
/// When monster standing behind garrison it's visitable and guarded at the same time. /// When monster standing behind garrison it's visitable and guarded at the same time.
if(gs->guardingCreaturePosition(dp->coord).valid() if(gs->guardingCreaturePosition(destination.node->coord).valid()
&& (ignoreAccessibility || dp->accessible == CGPathNode::BLOCKVIS)) && (ignoreAccessibility || destination.node->accessible == CGPathNode::BLOCKVIS))
{ {
return true; return true;
} }
@@ -652,7 +731,7 @@ bool CPathfinder::isDestinationGuarded(const bool ignoreAccessibility) const
bool CPathfinder::isDestinationGuardian() const bool CPathfinder::isDestinationGuardian() const
{ {
return gs->guardingCreaturePosition(cp->coord) == dp->coord; return gs->guardingCreaturePosition(source.node->coord) == destination.node->coord;
} }
void CPathfinder::initializePatrol() void CPathfinder::initializePatrol()
@@ -676,17 +755,18 @@ void CPathfinder::initializeGraph()
{ {
auto updateNode = [&](int3 pos, ELayer layer, const TerrainTile * tinfo) auto updateNode = [&](int3 pos, ELayer layer, const TerrainTile * tinfo)
{ {
auto node = out.getNode(pos, layer); auto node = nodeHelper->getNode(pos, layer);
auto accessibility = evaluateAccessibility(pos, tinfo, layer); auto accessibility = evaluateAccessibility(pos, tinfo, layer);
node->update(pos, layer, accessibility); node->update(pos, layer, accessibility);
}; };
int3 pos; int3 pos;
for(pos.x=0; pos.x < out.sizes.x; ++pos.x) int3 sizes = gs->getMapSize();
for(pos.x=0; pos.x < sizes.x; ++pos.x)
{ {
for(pos.y=0; pos.y < out.sizes.y; ++pos.y) for(pos.y=0; pos.y < sizes.y; ++pos.y)
{ {
for(pos.z=0; pos.z < out.sizes.z; ++pos.z) for(pos.z=0; pos.z < sizes.z; ++pos.z)
{ {
const TerrainTile * tinfo = &gs->map->getTile(pos); const TerrainTile * tinfo = &gs->map->getTile(pos);
switch(tinfo->terType) switch(tinfo->terType)
@@ -775,18 +855,6 @@ CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const int3 & pos,
return CGPathNode::ACCESSIBLE; return CGPathNode::ACCESSIBLE;
} }
bool CPathfinder::isVisitableObj(const CGObjectInstance * obj, const ELayer layer) const
{
/// Hero can't visit objects while walking on water or flying
return canSeeObj(obj) && (layer == ELayer::LAND || layer == ELayer::SAIL);
}
bool CPathfinder::canSeeObj(const CGObjectInstance * obj) const
{
/// Pathfinder should ignore placed events
return obj != nullptr && obj->ID != Obj::EVENT;
}
bool CPathfinder::canMoveBetween(const int3 & a, const int3 & b) const bool CPathfinder::canMoveBetween(const int3 & a, const int3 & b) const
{ {
return gs->checkForVisitableDir(a, b); return gs->checkForVisitableDir(a, b);
@@ -1238,3 +1306,31 @@ CGPathNode * CPathsInfo::getNode(const int3 & coord, const ELayer layer)
{ {
return &nodes[coord.x][coord.y][coord.z][layer]; return &nodes[coord.x][coord.y][coord.z][layer];
} }
CPathNodeInfo::CPathNodeInfo()
: node(nullptr), nodeObject(nullptr), tile(nullptr), coord(-1, -1, -1), blocked(false), furtherProcessingImpossible(false)
{
}
void CPathNodeInfo::setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject)
{
node = n;
if(coord != node->coord)
{
assert(node->coord.valid());
coord = node->coord;
tile = gs->getTile(coord);
nodeObject = tile->topVisitableObj(excludeTopObject);
}
blocked = false;
furtherProcessingImpossible = false;
}
bool CPathNodeInfo::isNodeObjectVisitable() const
{
/// Hero can't visit objects while walking on water or flying
return canSeeObj(nodeObject) && (node->layer == EPathfindingLayer::LAND || node->layer == EPathfindingLayer::SAIL);
}

View File

@@ -96,15 +96,62 @@ struct DLL_LINKAGE CPathsInfo
CGPathNode * getNode(const int3 & coord, const ELayer layer); CGPathNode * getNode(const int3 & coord, const ELayer layer);
}; };
struct DLL_LINKAGE CPathNodeInfo
{
CGPathNode * node;
const CGObjectInstance * nodeObject;
const TerrainTile * tile;
int3 coord;
bool blocked;
bool furtherProcessingImpossible;
CPathNodeInfo();
void setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject = false);
bool isNodeObjectVisitable() const;
};
class CNodeHelper
{
public:
virtual CGPathNode * getNode(const int3 & coord, const EPathfindingLayer layer) = 0;
virtual CGPathNode * getInitialNode() = 0;
};
class CPathfinder;
class CNeighbourFinder
{
protected:
CPathfinder * pathfinder;
std::vector<int3> neighbourTiles;
std::vector<int3> accessibleNeighbourTiles;
std::vector<CGPathNode *> neighbours;
public:
CNeighbourFinder(CPathfinder * pathfinder);
virtual std::vector<CGPathNode *> & calculateNeighbours();
protected:
void addNeighbourTiles();
};
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<CNodeHelper> nodeHelper,
std::shared_ptr<CNeighbourFinder> neighbourFinder);
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 struct PathfinderOptions
@@ -158,10 +205,11 @@ private:
PathfinderOptions(); PathfinderOptions();
} options; } 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<CNodeHelper> nodeHelper;
std::shared_ptr<CNeighbourFinder> neighbourFinder;
enum EPatrolState { enum EPatrolState {
PATROL_NONE = 0, PATROL_NONE = 0,
@@ -187,13 +235,11 @@ private:
std::vector<int3> neighbourTiles; std::vector<int3> neighbourTiles;
std::vector<int3> neighbours; std::vector<int3> neighbours;
CGPathNode * cp; //current (source) path node -> we took it from the queue CPathNodeInfo source; //current (source) path node -> we took it from the queue
CGPathNode * dp; //destination node -> it's a neighbour of cp that we consider CPathNodeInfo destination; //destination node -> it's a neighbour of source that we consider
const TerrainTile * ct, * dt; //tile info for both nodes
const CGObjectInstance * ctObj, * dtObj;
CGPathNode::ENodeAction destAction; CGPathNode::ENodeAction destAction;
void addNeighbours(); void populateNeighbourTiles(std::vector<int3> & neighbourTiles);
void addTeleportExits(); void addTeleportExits();
bool isHeroPatrolLocked() const; bool isHeroPatrolLocked() const;
@@ -202,14 +248,12 @@ private:
bool isLayerTransitionPossible(const ELayer dstLayer) const; bool isLayerTransitionPossible(const ELayer dstLayer) const;
bool isLayerTransitionPossible() const; bool isLayerTransitionPossible() const;
bool isMovementToDestPossible() const; bool isMovementToDestPossible() const;
bool isMovementAfterDestPossible() const; void checkMovementAfterDestPossible();
CGPathNode::ENodeAction getDestAction() 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 ignoreAccessibility = true) const; bool isDestinationGuarded(const bool ignoreAccessibility = true) const;
bool isDestinationGuardian() const; bool isDestinationGuardian() const;
@@ -217,8 +261,6 @@ private:
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 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 isAllowedTeleportEntrance(const CGTeleport * obj) const;
@@ -271,7 +313,36 @@ public:
static void getNeighbours(const CMap * map, const TerrainTile & srct, const int3 & tile, std::vector<int3> & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing); static void getNeighbours(const CMap * map, const TerrainTile & srct, const int3 & tile, std::vector<int3> & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing);
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); 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);
static int getMovementCost(
const CGHeroInstance * h,
const CPathNodeInfo & src,
const CPathNodeInfo & dst,
const int remainingMovePoints = -1,
const TurnInfo * ti = nullptr,
const bool checkLast = true)
{
return getMovementCost(
h,
src.coord,
dst.coord,
src.tile,
dst.tile,
remainingMovePoints,
ti,
checkLast
);
}
static int getMovementCost(const CGHeroInstance * h, const int3 & dst); static int getMovementCost(const CGHeroInstance * h, const int3 & dst);
private: private: