1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-07-15 01:24:45 +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()
{
assert(hero);
assert(hero == getHero(hero->id));
bool flying = hero->hasBonusOfType(Bonus::FLYING_MOVEMENT);
int maxMovePointsLand = hero->maxMovePoints(true);
int maxMovePointsWater = hero->maxMovePoints(false);
@ -3349,29 +3346,37 @@ void CPathfinder::calculatePaths()
turn++;
}
//add accessible neighbouring nodes to the queue
neighbours.clear();
//handling subterranean gate => it's exit is the only neighbour
bool subterraneanEntry = (ct->topVisitableId() == Obj::SUBTERRANEAN_GATE && useSubterraneanGates);
if(subterraneanEntry)
auto sObj = ct->topVisitableObj(cp->coord == CGHeroInstance::convertPosition(hero->pos, false));
auto cObj = dynamic_cast<const CGTeleport *>(sObj);
if(gs->isTeleportEntrancePassable(cObj, hero->tempOwner)
&& (addTeleportWhirlpool(dynamic_cast<const CGWhirlpool *>(cObj))
|| addTeleportTwoWay(cObj)
|| addTeleportOneWay(cObj)
|| addTeleportOneWayRandom(cObj)))
{
//try finding the exit gate
if(const CGObjectInstance *outGate = getObj(CGTeleport::getMatchingGate(ct->visitableObjects.back()->id), false))
for(auto objId : gs->getTeleportChannelExits(cObj->channel, ObjectInstanceID(), hero->tempOwner))
{
const int3 outPos = outGate->visitablePos();
//gs->getNeighbours(*getTile(outPos), outPos, neighbours, boost::logic::indeterminate, !cp->land);
neighbours.push_back(outPos);
}
else
{
//gate with no exit (blocked) -> do nothing with this node
continue;
auto obj = getObj(objId);
if(CGTeleport::isExitPassable(gs, hero, obj))
neighbours.push_back(obj->visitablePos());
}
}
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)
{
@ -3381,36 +3386,33 @@ void CPathfinder::calculatePaths()
destTopVisObjID = dt->topVisitableId();
useEmbarkCost = 0; //0 - usual movement; 1 - embark; 2 - disembark
int turnAtNextTile = turn;
const bool destIsGuardian = sourceGuardPosition == n;
if(!goodForLandSeaTransition())
continue;
if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED )
auto dObj = dynamic_cast<const CGTeleport*>(dt->topVisitableObj());
if(!goodForLandSeaTransition()
|| (!canMoveBetween(cp->coord, dp->coord) && !CGTeleport::isConnected(cObj, dObj))
|| dp->accessible == CGPathNode::BLOCKED)
{
continue;
}
//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)
guardedSource = false;
int cost = gs->getMovementCost(hero, cp->coord, dp->coord, flying, movement);
//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;
int remains = movement - cost;
if(useEmbarkCost)
{
remains = hero->movementPointsAfterEmbark(movement, cost, useEmbarkCost - 1);
cost = movement - remains;
}
int turnAtNextTile = turn;
if(remains < 0)
{
//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
&& (!guardedSource || destIsGuardian)) // Can step into tile of guard
{
assert(dp != cp->theNodeBefore); //two tiles can't point to each other
dp->moveRemains = remains;
dp->turns = turnAtNextTile;
@ -3435,8 +3436,12 @@ void CPathfinder::calculatePaths()
&& dp->accessible == CGPathNode::BLOCKVIS;
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)
|| 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.
{
mq.push_back(dp);
@ -3453,7 +3458,7 @@ CGPathNode *CPathfinder::getNode(const int3 &coord)
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
@ -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)
{
useSubterraneanGates = true;
assert(hero);
assert(hero == getHero(hero->id));
allowEmbarkAndDisembark = true;
allowTeleportTwoWay = true;
allowTeleportOneWay = true;
allowTeleportOneWayRandom = false;
allowTeleportWhirlpool = false;
if (CGWhirlpool::isProtected(hero))
allowTeleportWhirlpool = true;
}
CRandomGenerator & CGameState::getRandomGenerator()
@ -3541,3 +3554,48 @@ CRandomGenerator & CGameState::getRandomGenerator()
//logGlobal->traceStream() << "Fetching CGameState::rand with seed " << rand.nextInt();
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
{
private:
bool useSubterraneanGates;
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;
const CGHeroInstance *hero;
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
const TerrainTile *ct, *dt; //tile info for both nodes
ui8 useEmbarkCost; //0 - usual movement; 1 - embark; 2 - disembark
int destTopVisObjID;
Obj destTopVisObjID;
CGPathNode *getNode(const int3 &coord);
@ -303,6 +306,11 @@ private:
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 addTeleportTwoWay(const CGTeleport * obj) const;
bool addTeleportOneWay(const CGTeleport * obj) const;
bool addTeleportOneWayRandom(const CGTeleport * obj) const;
bool addTeleportWhirlpool(const CGWhirlpool * obj) const;
public:
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