1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-07-17 01:32:21 +02:00

CPathfinder: add all new code for pathfinding via teleporters

Each kind of teleporter have own function that determine if it's should or shouldn't be used.
For now Monolith with bidirectional channels and Subterranean Gate are united.
This commit is contained in:
ArseniyShestakov
2015-03-08 17:13:26 +03:00
parent 3773859e9d
commit 530b63f7c4
2 changed files with 105 additions and 39 deletions

View File

@ -3300,9 +3300,6 @@ void CPathfinder::initializeGraph()
void CPathfinder::calculatePaths() void CPathfinder::calculatePaths()
{ {
assert(hero);
assert(hero == getHero(hero->id));
bool flying = hero->hasBonusOfType(Bonus::FLYING_MOVEMENT); bool flying = hero->hasBonusOfType(Bonus::FLYING_MOVEMENT);
int maxMovePointsLand = hero->maxMovePoints(true); int maxMovePointsLand = hero->maxMovePoints(true);
int maxMovePointsWater = hero->maxMovePoints(false); int maxMovePointsWater = hero->maxMovePoints(false);
@ -3349,29 +3346,37 @@ void CPathfinder::calculatePaths()
turn++; turn++;
} }
//add accessible neighbouring nodes to the queue //add accessible neighbouring nodes to the queue
neighbours.clear(); neighbours.clear();
//handling subterranean gate => it's exit is the only neighbour auto sObj = ct->topVisitableObj(cp->coord == CGHeroInstance::convertPosition(hero->pos, false));
bool subterraneanEntry = (ct->topVisitableId() == Obj::SUBTERRANEAN_GATE && useSubterraneanGates); auto cObj = dynamic_cast<const CGTeleport *>(sObj);
if(subterraneanEntry) if(gs->isTeleportEntrancePassable(cObj, hero->tempOwner)
&& (addTeleportWhirlpool(dynamic_cast<const CGWhirlpool *>(cObj))
|| addTeleportTwoWay(cObj)
|| addTeleportOneWay(cObj)
|| addTeleportOneWayRandom(cObj)))
{ {
//try finding the exit gate for(auto objId : gs->getTeleportChannelExits(cObj->channel, ObjectInstanceID(), hero->tempOwner))
if(const CGObjectInstance *outGate = getObj(CGTeleport::getMatchingGate(ct->visitableObjects.back()->id), false))
{ {
const int3 outPos = outGate->visitablePos(); auto obj = getObj(objId);
//gs->getNeighbours(*getTile(outPos), outPos, neighbours, boost::logic::indeterminate, !cp->land); if(CGTeleport::isExitPassable(gs, hero, obj))
neighbours.push_back(outPos); neighbours.push_back(obj->visitablePos());
}
else
{
//gate with no exit (blocked) -> do nothing with this node
continue;
} }
} }
gs->getNeighbours(*ct, cp->coord, neighbours, boost::logic::indeterminate, !cp->land); std::vector<int3> neighbour_tiles;
gs->getNeighbours(*ct, cp->coord, neighbour_tiles, boost::logic::indeterminate, !cp->land);
if(sObj)
{
for(int3 neighbour_tile: neighbour_tiles)
{
if(canMoveBetween(neighbour_tile, sObj->visitablePos()))
neighbours.push_back(neighbour_tile);
}
}
else
vstd::concatenate(neighbours, neighbour_tiles);
for(auto & neighbour : neighbours) for(auto & neighbour : neighbours)
{ {
@ -3381,36 +3386,33 @@ void CPathfinder::calculatePaths()
destTopVisObjID = dt->topVisitableId(); destTopVisObjID = dt->topVisitableId();
useEmbarkCost = 0; //0 - usual movement; 1 - embark; 2 - disembark useEmbarkCost = 0; //0 - usual movement; 1 - embark; 2 - disembark
int turnAtNextTile = turn;
const bool destIsGuardian = sourceGuardPosition == n; const bool destIsGuardian = sourceGuardPosition == n;
if(!goodForLandSeaTransition()) auto dObj = dynamic_cast<const CGTeleport*>(dt->topVisitableObj());
continue; if(!goodForLandSeaTransition()
|| (!canMoveBetween(cp->coord, dp->coord) && !CGTeleport::isConnected(cObj, dObj))
if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED ) || dp->accessible == CGPathNode::BLOCKED)
{
continue; continue;
}
//special case -> hero embarked a boat standing on a guarded tile -> we must allow to move away from that tile //special case -> hero embarked a boat standing on a guarded tile -> we must allow to move away from that tile
if(cp->accessible == CGPathNode::VISITABLE && guardedSource && cp->theNodeBefore->land && ct->topVisitableId() == Obj::BOAT) if(cp->accessible == CGPathNode::VISITABLE && guardedSource && cp->theNodeBefore->land && ct->topVisitableId() == Obj::BOAT)
guardedSource = false; guardedSource = false;
int cost = gs->getMovementCost(hero, cp->coord, dp->coord, flying, movement); int cost = gs->getMovementCost(hero, cp->coord, dp->coord, flying, movement);
//special case -> moving from src Subterranean gate to dest gate -> it's free //special case -> moving from src Subterranean gate to dest gate -> it's free
if(subterraneanEntry && destTopVisObjID == Obj::SUBTERRANEAN_GATE && cp->coord.z != dp->coord.z) if(CGTeleport::isConnected(cObj, dObj))
cost = 0; cost = 0;
int remains = movement - cost; int remains = movement - cost;
if(useEmbarkCost) if(useEmbarkCost)
{ {
remains = hero->movementPointsAfterEmbark(movement, cost, useEmbarkCost - 1); remains = hero->movementPointsAfterEmbark(movement, cost, useEmbarkCost - 1);
cost = movement - remains; cost = movement - remains;
} }
int turnAtNextTile = turn;
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
@ -3425,7 +3427,6 @@ void CPathfinder::calculatePaths()
|| (dp->turns >= turnAtNextTile && dp->moveRemains < remains)) //this route is faster || (dp->turns >= turnAtNextTile && dp->moveRemains < remains)) //this route is faster
&& (!guardedSource || destIsGuardian)) // Can step into tile of guard && (!guardedSource || destIsGuardian)) // Can step into tile of guard
{ {
assert(dp != cp->theNodeBefore); //two tiles can't point to each other assert(dp != cp->theNodeBefore); //two tiles can't point to each other
dp->moveRemains = remains; dp->moveRemains = remains;
dp->turns = turnAtNextTile; dp->turns = turnAtNextTile;
@ -3435,8 +3436,12 @@ void CPathfinder::calculatePaths()
&& dp->accessible == CGPathNode::BLOCKVIS; && dp->accessible == CGPathNode::BLOCKVIS;
if(dp->accessible == CGPathNode::ACCESSIBLE if(dp->accessible == CGPathNode::ACCESSIBLE
|| dp->coord == CGHeroInstance::convertPosition(hero->pos, false) // This one is tricky, we can ignore fact that tile is not ACCESSIBLE in case if it's our hero block it. Though this need investigation.
|| (dp->accessible == CGPathNode::VISITABLE
&& CGTeleport::isTeleport(dt->topVisitableObj())) // For now we'll walways allos transit for teleports
|| (useEmbarkCost && allowEmbarkAndDisembark) || (useEmbarkCost && allowEmbarkAndDisembark)
|| destTopVisObjID == Obj::SUBTERRANEAN_GATE || gs->isTeleportEntrancePassable(dObj, hero->tempOwner) // Always add entry teleport with non-dummy channel
|| CGTeleport::isConnected(cObj, dObj) // Always add exit points of teleport
|| (guardedDst && !guardedSource)) // Can step into a hostile tile once. || (guardedDst && !guardedSource)) // Can step into a hostile tile once.
{ {
mq.push_back(dp); mq.push_back(dp);
@ -3453,7 +3458,7 @@ CGPathNode *CPathfinder::getNode(const int3 &coord)
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) && gs->checkForVisitableDir(b, a); return gs->checkForVisitableDir(a, b);
} }
CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const TerrainTile *tinfo) const CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const TerrainTile *tinfo) const
@ -3532,8 +3537,16 @@ bool CPathfinder::goodForLandSeaTransition()
CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero) : CGameInfoCallback(_gs, boost::optional<PlayerColor>()), out(_out), hero(_hero), FoW(getPlayerTeam(hero->tempOwner)->fogOfWarMap) CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero) : CGameInfoCallback(_gs, boost::optional<PlayerColor>()), out(_out), hero(_hero), FoW(getPlayerTeam(hero->tempOwner)->fogOfWarMap)
{ {
useSubterraneanGates = true; assert(hero);
assert(hero == getHero(hero->id));
allowEmbarkAndDisembark = true; allowEmbarkAndDisembark = true;
allowTeleportTwoWay = true;
allowTeleportOneWay = true;
allowTeleportOneWayRandom = false;
allowTeleportWhirlpool = false;
if (CGWhirlpool::isProtected(hero))
allowTeleportWhirlpool = true;
} }
CRandomGenerator & CGameState::getRandomGenerator() CRandomGenerator & CGameState::getRandomGenerator()
@ -3541,3 +3554,48 @@ CRandomGenerator & CGameState::getRandomGenerator()
//logGlobal->traceStream() << "Fetching CGameState::rand with seed " << rand.nextInt(); //logGlobal->traceStream() << "Fetching CGameState::rand with seed " << rand.nextInt();
return rand; return rand;
} }
bool CPathfinder::addTeleportTwoWay(const CGTeleport * obj) const
{
if(allowTeleportTwoWay)
{
if(ETeleportChannelType::BIDIRECTIONAL == gs->getTeleportChannelType(obj->channel, hero->tempOwner))
return true;
}
return false;
}
bool CPathfinder::addTeleportOneWay(const CGTeleport * obj) const
{
if(allowTeleportOneWay)
{
auto passableExits = CGTeleport::getPassableExits(gs, hero, gs->getTeleportChannelExits(obj->channel, ObjectInstanceID(), hero->tempOwner));
if(passableExits.size() == 1)
return true;
}
return false;
}
bool CPathfinder::addTeleportOneWayRandom(const CGTeleport * obj) const
{
if(allowTeleportOneWayRandom)
{
if(ETeleportChannelType::UNIDIRECTIONAL == gs->getTeleportChannelType(obj->channel, hero->tempOwner))
{
auto passableExits = CGTeleport::getPassableExits(gs, hero, gs->getTeleportChannelExits(obj->channel, ObjectInstanceID(), hero->tempOwner));
if(passableExits.size() > 1)
return true;
}
}
return false;
}
bool CPathfinder::addTeleportWhirlpool(const CGWhirlpool * obj) const
{
if(allowTeleportWhirlpool && obj)
{
if(ETeleportChannelType::IMPASSABLE != gs->getTeleportChannelType(obj->channel, hero->tempOwner))
return true;
}
return false;
}

View File

@ -279,8 +279,11 @@ struct DLL_EXPORT DuelParameters
class CPathfinder : private CGameInfoCallback class CPathfinder : private CGameInfoCallback
{ {
private: private:
bool useSubterraneanGates;
bool allowEmbarkAndDisembark; bool allowEmbarkAndDisembark;
bool allowTeleportTwoWay; // Two-way monoliths and Subterranean Gate
bool allowTeleportOneWay; // One-way monoliths with one known exit only
bool allowTeleportOneWayRandom; // One-way monoliths with more than one known exit
bool allowTeleportWhirlpool; // Force enabled if hero protected or unaffected (have one stack of one creature)
CPathsInfo &out; 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;
@ -293,7 +296,7 @@ private:
CGPathNode *dp; //destination node -> it's a neighbour of cp that we consider CGPathNode *dp; //destination node -> it's a neighbour of cp that we consider
const TerrainTile *ct, *dt; //tile info for both nodes const TerrainTile *ct, *dt; //tile info for both nodes
ui8 useEmbarkCost; //0 - usual movement; 1 - embark; 2 - disembark ui8 useEmbarkCost; //0 - usual movement; 1 - embark; 2 - disembark
int destTopVisObjID; Obj destTopVisObjID;
CGPathNode *getNode(const int3 &coord); CGPathNode *getNode(const int3 &coord);
@ -303,6 +306,11 @@ private:
CGPathNode::EAccessibility evaluateAccessibility(const TerrainTile *tinfo) const; CGPathNode::EAccessibility evaluateAccessibility(const TerrainTile *tinfo) 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 addTeleportTwoWay(const CGTeleport * obj) const;
bool addTeleportOneWay(const CGTeleport * obj) const;
bool addTeleportOneWayRandom(const CGTeleport * obj) const;
bool addTeleportWhirlpool(const CGWhirlpool * obj) const;
public: public:
CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero); CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero);
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