1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-07-01 00:45:26 +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);
@ -3318,7 +3315,7 @@ void CPathfinder::calculatePaths()
if(!gs->map->isInTheMap(out.hpos)/* || !gs->map->isInTheMap(dest)*/) //check input
{
logGlobal->errorStream() << "CGameState::calculatePaths: Hero outside the gs->map? How dare you...";
logGlobal->errorStream() << "CGameState::calculatePaths: Hero outside the gs->map? How dare you...";
return;
}
@ -3349,68 +3346,73 @@ 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)
{
const int3 &n = neighbour; //current neighbor
dp = getNode(n);
dt = &gs->map->getTile(n);
destTopVisObjID = dt->topVisitableId();
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)
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;
@ -3434,9 +3435,13 @@ void CPathfinder::calculatePaths()
const bool guardedDst = gs->map->guardingCreaturePositions[dp->coord.x][dp->coord.y][dp->coord.z].valid()
&& 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)
|| 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;
}