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:
@ -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;
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user