1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-07-15 01:24:45 +02:00

Work on pathfinder: torn the code out from CGameState into a separate class. It can use Subterranean Gates and Boats. Removed code for handling Fly spell effect. It didn't work as supposed anyway.

This commit is contained in:
Michał W. Urbańczyk
2011-09-19 20:50:25 +00:00
parent 74fafbedbf
commit 6ef44bde5a
15 changed files with 440 additions and 429 deletions

View File

@ -1762,10 +1762,9 @@ void CGameState::loadTownDInfos()
void CGameState::getNeighbours(const TerrainTile &srct, int3 tile, std::vector<int3> &vec, const boost::logic::tribool &onLand, bool limitCoastSailing)
{
static int3 dirs[] = { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0),
static const int3 dirs[] = { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0),
int3(1,1,0),int3(-1,1,0),int3(1,-1,0),int3(-1,-1,0) };
vec.clear();
for (size_t i = 0; i < ARRAY_COUNT(dirs); i++)
{
const int3 hlp = tile + dirs[i];
@ -1774,11 +1773,11 @@ void CGameState::getNeighbours(const TerrainTile &srct, int3 tile, std::vector<i
const TerrainTile &hlpt = map->getTile(hlp);
//we cannot visit things from blocked tiles
if(srct.blocked && !srct.visitable && hlpt.visitable && srct.blockingObjects.front()->ID != HEROI_TYPE)
{
continue;
}
// //we cannot visit things from blocked tiles
// if(srct.blocked && !srct.visitable && hlpt.visitable && srct.blockingObjects.front()->ID != HEROI_TYPE)
// {
// continue;
// }
if(srct.tertype == TerrainTile::water && limitCoastSailing && hlpt.tertype == TerrainTile::water && dirs[i].x && dirs[i].y) //diagonal move through water
{
@ -1821,7 +1820,7 @@ int CGameState::getMovementCost(const CGHeroInstance *h, const int3 &src, const
}
else if (d.tertype == TerrainTile::water)
{
if(h->boat && s.siodmyTajemniczyBajt & 128 && d.siodmyTajemniczyBajt & 128) //Favourable Winds
if(h->boat && s.hasFavourableWinds() && d.hasFavourableWinds()) //Favourable Winds
ret *= 0.666f;
else if (!h->boat && h->getBonusesCount(Selector::typeSubtype(Bonus::WATER_WALKING, 1)) > 0)
ret *= 1.4f; //40% penalty for water walking
@ -1865,352 +1864,15 @@ void CGameState::apply(CPack *pack)
bool CGameState::getPath(int3 src, int3 dest, const CGHeroInstance * hero, CPath &ret)
{
if(!map->isInTheMap(src) || !map->isInTheMap(dest)) //check input
return false;
int3 hpos = hero->getPosition(false);
bool flying = false; //hero is under flying effect TODO
bool waterWalking = false; //hero is on land and can walk on water TODO
bool onLand = map->getTile(hpos).tertype != TerrainTile::water;
// tribool blockLandSea; //true - blocks sea, false - blocks land, indeterminate - allows all
//
// if (!hero->canWalkOnSea())
// blockLandSea = (map->getTile(hpos).tertype != TerrainTile::water); //block land if hero is on water and vice versa
// else
// blockLandSea = boost::logic::indeterminate;
const std::vector<std::vector<std::vector<ui8> > > &FoW = getPlayerTeam(hero->tempOwner)->fogOfWarMap;
//graph initialization
std::vector< std::vector<CPathNode> > graph;
graph.resize(map->width);
for(size_t i=0; i<graph.size(); ++i)
{
graph[i].resize(map->height);
for(size_t j=0; j<graph[i].size(); ++j)
{
const TerrainTile *tinfo = &map->terrain[i][j][src.z];
CPathNode &node = graph[i][j];
node.accessible = !tinfo->blocked;
node.dist = -1;
node.theNodeBefore = NULL;
node.visited = false;
node.coord.x = i;
node.coord.y = j;
node.coord.z = dest.z;
if(!tinfo->entrableTerrain(onLand, flying || waterWalking)
|| !FoW[i][j][src.z] //tile is covered by the FoW
)
{
node.accessible = false;
}
}
}
//Special rules for the destination tile
{
const TerrainTile *t = &map->terrain[dest.x][dest.y][dest.z];
CPathNode &d = graph[dest.x][dest.y];
//tile may be blocked by blockvis / normal vis obj but it still must be accessible
if(t->visitable)
{
d.accessible = true; //for allowing visiting objects
}
if(onLand && t->tertype == TerrainTile::water) //hero can walk only on land and dst lays on the water
{
size_t i = 0;
for(; i < t->visitableObjects.size(); i++)
if(t->visitableObjects[i]->ID == 8 || t->visitableObjects[i]->ID == HEROI_TYPE) //it's a Boat
break;
d.accessible = (i < t->visitableObjects.size()); //dest is accessible only if there is boat/hero
}
else if(!onLand && t->tertype != TerrainTile::water) //hero is moving by water
{
d.accessible = (t->siodmyTajemniczyBajt & 64) && !t->blocked; //tile is accessible if it's coastal and not blocked
}
}
//graph initialized
//initial tile - set cost on 0 and add to the queue
graph[src.x][src.y].dist = 0;
std::queue<CPathNode> mq;
mq.push(graph[src.x][src.y]);
ui32 curDist = 0xffffffff; //total cost of path - init with max possible val
std::vector<int3> neighbours;
neighbours.reserve(8);
while(!mq.empty())
{
CPathNode &cp = graph[mq.front().coord.x][mq.front().coord.y];
mq.pop();
if (cp.coord == dest) //it's destination tile
{
if (cp.dist < curDist) //that path is better than previous one
curDist = cp.dist;
continue;
}
else
{
if (cp.dist > curDist) //it's not dest and current length is greater than cost of already found path
continue;
}
//add accessible neighbouring nodes to the queue
getNeighbours(map->getTile(cp.coord), cp.coord, neighbours, boost::logic::indeterminate, true);
for(unsigned int i=0; i < neighbours.size(); i++)
{
CPathNode & dp = graph[neighbours[i].x][neighbours[i].y];
if(dp.accessible)
{
int cost = getMovementCost(hero,cp.coord,dp.coord,hero->movement - cp.dist);
if((dp.dist==-1 || (dp.dist > cp.dist + cost)) && dp.accessible && checkForVisitableDir(cp.coord, dp.coord) && checkForVisitableDir(dp.coord, cp.coord))
{
dp.dist = cp.dist + cost;
dp.theNodeBefore = &cp;
mq.push(dp);
}
}
}
}
CPathNode *curNode = &graph[dest.x][dest.y];
if(!curNode->theNodeBefore) //destination is not accessible
return false;
//fill ret with found path
ret.nodes.clear();
while(curNode->coord != graph[src.x][src.y].coord)
{
ret.nodes.push_back(*curNode);
curNode = curNode->theNodeBefore;
}
ret.nodes.push_back(graph[src.x][src.y]);
return true;
//the old pathfinder is not supported anymore!
assert(0);
return false;
}
void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out, int3 src, int movement)
{
assert(hero);
assert(hero == getHero(hero->id));
if(src.x < 0)
src = hero->getPosition(false);
if(movement < 0)
movement = hero->movement;
out.hero = hero;
out.hpos = src;
if(!map->isInTheMap(src)/* || !map->isInTheMap(dest)*/) //check input
{
tlog1 << "CGameState::calculatePaths: Hero outside the map? How dare you...\n";
return;
}
tribool onLand; //true - blocks sea, false - blocks land, indeterminate - allows all
if (!hero->canWalkOnSea())
onLand = (map->getTile(src).tertype != TerrainTile::water); //block land if hero is on water and vice versa
else
onLand = boost::logic::indeterminate;
const std::vector<std::vector<std::vector<ui8> > > &FoW = getPlayerTeam(hero->tempOwner)->fogOfWarMap;
bool flying = hero->hasBonusOfType(Bonus::FLYING_MOVEMENT);
bool waterWalk = hero->hasBonusOfType(Bonus::WATER_WALKING);
//graph initialization
CGPathNode ***graph = out.nodes;
for(size_t i=0; i < out.sizes.x; ++i)
{
for(size_t j=0; j < out.sizes.y; ++j)
{
for(size_t k=0; k < out.sizes.z; ++k)
{
const TerrainTile *tinfo = &map->terrain[i][j][k];
CGPathNode &node = graph[i][j][k];
node.accessible = (tinfo->blocked ? CGPathNode::FLYABLE : CGPathNode::ACCESSIBLE);
if(!flying && node.accessible == CGPathNode::FLYABLE)
{
node.accessible = CGPathNode::BLOCKED;
}
node.turns = 0xff;
node.moveRemains = 0;
node.coord.x = i;
node.coord.y = j;
node.coord.z = k;
node.land = tinfo->tertype != TerrainTile::water;
node.theNodeBefore = NULL;
bool leaveAsBlocked = false;
if((onLand || indeterminate(onLand)) && !node.land)//it's sea and we cannot walk on sea
{
if (waterWalk || flying)
{
node.accessible = CGPathNode::FLYABLE;
}
else
{
node.accessible = CGPathNode::BLOCKED;
leaveAsBlocked = true;
}
}
if ( tinfo->tertype == TerrainTile::rock//it's rock
|| (!onLand && node.land) //it's land and we cannot walk on land (complementary condition is handled above)
|| !FoW[i][j][k] //tile is covered by the FoW
|| leaveAsBlocked
)
{
node.accessible = CGPathNode::BLOCKED;
}
else if(tinfo->visitable)
{
//hero is protected in Sanctuary
if(tinfo->visitableObjects.front()->ID == 80 && tinfo->visitableObjects.back()->ID == HEROI_TYPE && tinfo->visitableObjects.back()->tempOwner != hero->tempOwner)
node.accessible = CGPathNode::BLOCKED;
else
{
for(size_t ii = 0; ii < tinfo->visitableObjects.size(); ii++)
{
const CGObjectInstance * const obj = tinfo->visitableObjects[ii];
if(obj->getPassableness() & 1<<hero->tempOwner) //special object instance specific passableness flag - overwrites other accessibility flags
{
node.accessible = CGPathNode::ACCESSIBLE;
}
else if(obj->blockVisit)
{
node.accessible = CGPathNode::BLOCKVIS;
break;
}
else if(obj->ID != EVENTI_TYPE) //pathfinder should ignore placed events
{
node.accessible = CGPathNode::VISITABLE;
}
}
}
}
else if (map->isInTheMap(guardingCreaturePosition(int3(i, j, k)))
&& tinfo->blockingObjects.size() == 0)
{
// Monster close by; blocked visit for battle.
node.accessible = CGPathNode::BLOCKVIS;
}
if(onLand && !node.land) //hero can walk only on land and tile lays on the water
{
size_t i = 0;
for(; i < tinfo->visitableObjects.size(); i++)
if(tinfo->visitableObjects[i]->ID == 8 || tinfo->visitableObjects[i]->ID == HEROI_TYPE) //it's a Boat
break;
if(i < tinfo->visitableObjects.size())
node.accessible = CGPathNode::BLOCKVIS; //dest is accessible only if there is boat/hero
}
else if(!onLand && tinfo->tertype != TerrainTile::water) //hero is moving by water
{
if((tinfo->siodmyTajemniczyBajt & 64) && !tinfo->blocked)
node.accessible = CGPathNode::VISITABLE; //tile is accessible if it's coastal and not blocked
}
}
}
}
//graph initialized
//initial tile - set cost on 0 and add to the queue
graph[src.x][src.y][src.z].turns = 0;
graph[src.x][src.y][src.z].moveRemains = movement;
std::queue<CGPathNode*> mq;
mq.push(&graph[src.x][src.y][src.z]);
//ui32 curDist = 0xffffffff; //total cost of path - init with max possible val
std::vector<int3> neighbours;
neighbours.reserve(8);
while(!mq.empty())
{
CGPathNode *cp = mq.front();
mq.pop();
const int3 guardPosition = guardingCreaturePosition(cp->coord);
const bool guardedPosition = (guardPosition != int3(-1, -1, -1) && cp->coord != src);
const TerrainTile &ct = map->getTile(cp->coord);
int movement = cp->moveRemains, turn = cp->turns;
if(!movement)
{
movement = hero->maxMovePoints(ct.tertype != TerrainTile::water);
turn++;
}
//add accessible neighbouring nodes to the queue
getNeighbours(ct, cp->coord, neighbours, boost::logic::indeterminate, !onLand);
for(unsigned int i=0; i < neighbours.size(); i++)
{
int moveAtNextTile = movement;
int turnAtNextTile = turn;
const int3 &n = neighbours[i]; //current neighbor
CGPathNode & dp = graph[n.x][n.y][n.z];
if( !checkForVisitableDir(cp->coord, dp.coord)
|| !checkForVisitableDir(dp.coord, cp->coord)
|| dp.accessible == CGPathNode::BLOCKED )
{
continue;
}
int cost = getMovementCost(hero, cp->coord, dp.coord, movement);
int remains = movement - cost;
if(remains < 0)
{
//occurs rarely, when hero with low movepoints tries to go leave the road
turnAtNextTile++;
moveAtNextTile = hero->maxMovePoints(ct.tertype != TerrainTile::water);
cost = getMovementCost(hero, cp->coord, dp.coord, moveAtNextTile); //cost must be updated, movement points changed :(
remains = moveAtNextTile - cost;
}
const bool neighborIsGuard = guardingCreaturePosition(cp->coord) == dp.coord;
if((dp.turns==0xff //we haven't been here before
|| dp.turns > turnAtNextTile
|| (dp.turns >= turnAtNextTile && dp.moveRemains < remains)) //this route is faster
&& (!guardedPosition || neighborIsGuard)) // Can step into tile of guard
{
assert(&dp != cp->theNodeBefore); //two tiles can't point to each other
dp.moveRemains = remains;
dp.turns = turnAtNextTile;
dp.theNodeBefore = cp;
const bool guardedNeighbor = guardingCreaturePosition(dp.coord) != int3(-1, -1, -1);
//const bool positionIsGuard = guardingCreaturePosition(cp->coord) == cp->coord; //can this be true? hero never passes from monster tile...
if (dp.accessible == CGPathNode::ACCESSIBLE || dp.accessible == CGPathNode::FLYABLE
|| (guardedNeighbor && !guardedPosition)) // Can step into a hostile tile once.
{
mq.push(&dp);
}
}
} //neighbours loop
} //queue loop
out.isValid = true;
CPathfinder pathfinder(out, this, hero);
pathfinder.calculatePaths(src, movement);
}
/**
@ -2830,32 +2492,14 @@ bool CPathsInfo::getPath( const int3 &dst, CGPath &out )
out.nodes.clear();
const CGPathNode *curnode = &nodes[dst.x][dst.y][dst.z];
if(!curnode->theNodeBefore || curnode->accessible == CGPathNode::FLYABLE)
if(!curnode->theNodeBefore)
return false;
//we'll transform number of turns to conform the rule that hero cannot stop on blocked tile
bool transition01 = false;
while(curnode)
{
CGPathNode cpn = *curnode;
if(transition01)
{
if (curnode->accessible == CGPathNode::ACCESSIBLE)
{
transition01 = false;
}
else if (curnode->accessible == CGPathNode::FLYABLE)
{
cpn.turns = 1;
}
}
if(curnode->turns == 1 && curnode->theNodeBefore->turns == 0)
{
transition01 = true;
}
curnode = curnode->theNodeBefore;
out.nodes.push_back(cpn);
}
return true;
@ -3054,3 +2698,269 @@ TeamState::TeamState()
{
setNodeType(TEAM);
}
void CPathfinder::initializeGraph()
{
CGPathNode ***graph = out.nodes;
for(size_t i=0; i < out.sizes.x; ++i)
{
for(size_t j=0; j < out.sizes.y; ++j)
{
for(size_t k=0; k < out.sizes.z; ++k)
{
curPos = int3(i,j,k);
const TerrainTile *tinfo = &gs->map->terrain[i][j][k];
CGPathNode &node = graph[i][j][k];
node.accessible = evaluateAccessibility(tinfo);
node.turns = 0xff;
node.moveRemains = 0;
node.coord.x = i;
node.coord.y = j;
node.coord.z = k;
node.land = tinfo->tertype != TerrainTile::water;
node.theNodeBefore = NULL;
}
}
}
}
void CPathfinder::calculatePaths(int3 src /*= int3(-1,-1,-1)*/, int movement /*= -1*/)
{
assert(hero);
assert(hero == getHero(hero->id));
if(src.x < 0)
src = hero->getPosition(false);
if(movement < 0)
movement = hero->movement;
out.hero = hero;
out.hpos = src;
if(!gs->map->isInTheMap(src)/* || !gs->map->isInTheMap(dest)*/) //check input
{
tlog1 << "CGameState::calculatePaths: Hero outside the gs->map? How dare you...\n";
return;
}
initializeGraph();
//initial tile - set cost on 0 and add to the queue
CGPathNode &initialNode = *getNode(src);
initialNode.turns = 0;
initialNode.moveRemains = movement;
mq.push_back(&initialNode);
std::vector<int3> neighbours;
neighbours.reserve(16);
while(!mq.empty())
{
cp = mq.front();
mq.pop_front();
const int3 sourceGuardPosition = guardingCreaturePosition(cp->coord);
bool guardedSource = (sourceGuardPosition != int3(-1, -1, -1) && cp->coord != src);
ct = &gs->map->getTile(cp->coord);
int movement = cp->moveRemains, turn = cp->turns;
if(!movement)
{
movement = hero->maxMovePoints(cp->land);
turn++;
}
//add accessible neighbouring nodes to the queue
neighbours.clear();
//handling subterranean gate => it's exit is the only neighbour
bool subterraneanEntry = (ct->topVisitableID() == SUBTERRANEAN_GATE_TYPE && useSubterraneanGates);
if(subterraneanEntry)
{
//try finding the exit gate
if(const CGObjectInstance *outGate = getObj(CGTeleport::getMatchingGate(ct->visitableObjects.back()->id), false))
{
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;
}
}
gs->getNeighbours(*ct, cp->coord, neighbours, boost::logic::indeterminate, !cp->land);
for(unsigned int i=0; i < neighbours.size(); i++)
{
const int3 &n = neighbours[i]; //current neighbor
dp = getNode(n);
dt = &gs->map->getTile(n);
destTopVisObjID = dt->topVisitableID();
useEmbarkCost = 0; //0 - usual movement; 1 - embark; 2 - disembark
int moveAtNextTile = movement;
int turnAtNextTile = turn;
const bool destIsGuardian = sourceGuardPosition == n;
if(!goodForLandSeaTransition())
continue;
if(!canMoveBetween(cp->coord, dp->coord) || 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() == BOATI_TYPE)
guardedSource = false;
int cost = gs->getMovementCost(hero, cp->coord, dp->coord, movement);
//special case -> moving from src Subterranean gate to dest gate -> it's free
if(subterraneanEntry && destTopVisObjID == SUBTERRANEAN_GATE_TYPE && cp->coord.z != dp->coord.z)
cost = 0;
int remains = movement - cost;
if(useEmbarkCost)
{
remains = hero->movementPointsAfterEmbark(movement, cost, useEmbarkCost - 1);
cost = movement - remains;
}
if(remains < 0)
{
//occurs rarely, when hero with low movepoints tries to leave the road
turnAtNextTile++;
moveAtNextTile = hero->maxMovePoints(cp->land);
cost = gs->getMovementCost(hero, cp->coord, dp->coord, moveAtNextTile); //cost must be updated, movement points changed :(
remains = moveAtNextTile - cost;
}
if((dp->turns==0xff //we haven't been here before
|| dp->turns > turnAtNextTile
|| (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;
dp->theNodeBefore = cp;
const bool guardedDst = guardingCreaturePosition(dp->coord) != int3(-1, -1, -1)
&& dp->accessible == CGPathNode::BLOCKVIS;
if (dp->accessible == CGPathNode::ACCESSIBLE
|| useEmbarkCost && allowEmbarkAndDisembark
|| destTopVisObjID == SUBTERRANEAN_GATE_TYPE
|| (guardedDst && !guardedSource)) // Can step into a hostile tile once.
{
mq.push_back(dp);
}
}
} //neighbours loop
} //queue loop
out.isValid = true;
}
CGPathNode *CPathfinder::getNode(const int3 &coord)
{
return &out.nodes[coord.x][coord.y][coord.z];
}
bool CPathfinder::canMoveBetween(const int3 &a, const int3 &b) const
{
return gs->checkForVisitableDir(a, b) && gs->checkForVisitableDir(b, a);
}
bool CPathfinder::canStepOntoDst() const
{
//TODO remove
assert(0);
return false;
}
CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const TerrainTile *tinfo) const
{
CGPathNode::EAccessibility ret = (tinfo->blocked ? CGPathNode::BLOCKED : CGPathNode::ACCESSIBLE);
if(tinfo->tertype == TerrainTile::rock || !FoW[curPos.x][curPos.y][curPos.z])
return CGPathNode::BLOCKED;
if(tinfo->visitable)
{
if(tinfo->visitableObjects.front()->ID == 80 && tinfo->visitableObjects.back()->ID == HEROI_TYPE && tinfo->visitableObjects.back()->tempOwner != hero->tempOwner) //non-owned hero stands on Sanctuary
{
return CGPathNode::BLOCKED;
}
else
{
BOOST_FOREACH(const CGObjectInstance *obj, tinfo->visitableObjects)
{
if(obj->getPassableness() & 1<<hero->tempOwner) //special object instance specific passableness flag - overwrites other accessibility flags
{
ret = CGPathNode::ACCESSIBLE;
}
else if(obj->blockVisit)
{
return CGPathNode::BLOCKVIS;
}
else if(obj->ID != EVENTI_TYPE) //pathfinder should ignore placed events
{
ret = CGPathNode::VISITABLE;
}
}
}
}
else if (gs->map->isInTheMap(guardingCreaturePosition(curPos))
&& !tinfo->blocked)
{
// Monster close by; blocked visit for battle.
return CGPathNode::BLOCKVIS;
}
return ret;
}
bool CPathfinder::goodForLandSeaTransition()
{
if(cp->land != dp->land) //hero can traverse land<->sea only in special circumstances
{
if(cp->land) //from land to sea -> embark or assault hero on boat
{
if(dp->accessible == CGPathNode::ACCESSIBLE || destTopVisObjID < 0) //cannot enter empty water tile from land -> it has to be visitable
return false;
if(destTopVisObjID != HEROI_TYPE && destTopVisObjID != BOATI_TYPE) //only boat or hero can be accessed from land
return false;
if(destTopVisObjID == BOATI_TYPE)
useEmbarkCost = 1;
}
else //disembark
{
//can disembark only on coastal tiles
if(!dt->isCoastal())
return false;
//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))
return false;;
useEmbarkCost = 2;
}
}
return true;
}
CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero) : out(_out), CGameInfoCallback(_gs, -1), hero(_hero), FoW(getPlayerTeam(hero->tempOwner)->fogOfWarMap)
{
useSubterraneanGates = true;
allowEmbarkAndDisembark = true;
}