1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-20 20:23:03 +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

@ -572,17 +572,24 @@ void CTerrainRect::showPath(const SDL_Rect * extRect, SDL_Surface * to)
for (size_t i=0; i < currentPath->nodes.size()-1; ++i)
{
const int3 &curPos = currentPath->nodes[i].coord, &nextPos = currentPath->nodes[i+1].coord;
if(curPos.z != adventureInt->position.z)
continue;
int pn=-1;//number of picture
if (i==0) //last tile
{
int x = 32*(currentPath->nodes[i].coord.x-adventureInt->position.x)+CGI->mh->offsetX + pos.x,
y = 32*(currentPath->nodes[i].coord.y-adventureInt->position.y)+CGI->mh->offsetY + pos.y;
int x = 32*(curPos.x-adventureInt->position.x)+CGI->mh->offsetX + pos.x,
y = 32*(curPos.y-adventureInt->position.y)+CGI->mh->offsetY + pos.y;
if (x<0 || y<0 || x>pos.w || y>pos.h)
continue;
pn=0;
}
else
{
const int3 &prevPos = currentPath->nodes[i-1].coord;
std::vector<CGPathNode> & cv = currentPath->nodes;
/* Vector directions
* 0 1 2
* \ | /
@ -595,20 +602,25 @@ void CTerrainRect::showPath(const SDL_Rect * extRect, SDL_Surface * to)
* /
* is id1=7, id2=5 (pns[7][5])
*/
std::vector<CGPathNode> & cv = currentPath->nodes;
int id1=(cv[i].coord.x-cv[i+1].coord.x+1)+3*(cv[i].coord.y-cv[i+1].coord.y+1); //Direction of entering vector
int id2=(cv[i-1].coord.x-cv[i].coord.x+1)+3*(cv[i-1].coord.y-cv[i].coord.y+1); //Direction of exiting vector
pn=pns[id1][id2];
bool pathContinuous = curPos.areNeighbours(nextPos) && curPos.areNeighbours(prevPos);
if(pathContinuous && cv[i].land == cv[i+1].land)
{
int id1=(curPos.x-nextPos.x+1)+3*(curPos.y-nextPos.y+1); //Direction of entering vector
int id2=(cv[i-1].coord.x-curPos.x+1)+3*(cv[i-1].coord.y-curPos.y+1); //Direction of exiting vector
pn=pns[id1][id2];
}
else //path discontinuity or sea/land transition (eg. when moving through Subterranean Gate or Boat)
{
pn = 0;
}
}
if (currentPath->nodes[i].turns)
pn+=25;
if (pn>=0)
{
CDefEssential * arrows = graphics->heroMoveArrows;
int x = 32*(currentPath->nodes[i].coord.x-adventureInt->position.x)+CGI->mh->offsetX + pos.x,
y = 32*(currentPath->nodes[i].coord.y-adventureInt->position.y)+CGI->mh->offsetY + pos.y;
int x = 32*(curPos.x-adventureInt->position.x)+CGI->mh->offsetX + pos.x,
y = 32*(curPos.y-adventureInt->position.y)+CGI->mh->offsetY + pos.y;
if (x<0 || y<0 || x>pos.w || y>pos.h)
continue;
int hvx = (x+arrows->ourImages[pn].bitmap->w)-(pos.x+pos.w),
@ -690,7 +702,7 @@ void CTerrainRect::show(SDL_Surface * to)
//SDL_BlitSurface(teren,&genRect(pos.h,pos.w,0,0),screen,&genRect(547,594,7,6));
//SDL_FreeSurface(teren);
if (currentPath && adventureInt->position.z==currentPath->startPos().z) //drawing path
if (currentPath/* && adventureInt->position.z==currentPath->startPos().z*/) //drawing path
{
showPath(&pos, to);
}
@ -1655,7 +1667,7 @@ void CAdvMapInt::tileLClicked(const int3 &mp)
LOCPLINT->moveHero(currentHero,*terrain.currentPath);
return;
}
else if(mp.z == currentHero->pos.z) //remove old path and find a new one if we clicked on the map level on which hero is present
else/* if(mp.z == currentHero->pos.z)*/ //remove old path and find a new one if we clicked on the map level on which hero is present
{
CGPath &path = LOCPLINT->paths[currentHero];
terrain.currentPath = &path;
@ -1840,11 +1852,13 @@ void CAdvMapInt::tileHovered(const int3 &tile)
}
else //no objs
{
if(accessible && pnode->accessible != CGPathNode::FLYABLE)
if(accessible/* && pnode->accessible != CGPathNode::FLYABLE*/)
{
if (guardingCreature) {
if (guardingCreature)
{
CCS->curh->changeGraphic(0, 5 + turns*6);
} else {
} else
{
if(pnode->land)
{
if(LOCPLINT->cb->getTile(h->getPosition(false))->tertype != TerrainTile::water)

View File

@ -486,6 +486,12 @@ void MusicEntry::load(musicBase::musicID ID)
music = Mix_LoadMUS(filename.c_str());
if(!music)
{
tlog3 << "Warning: Cannot open " << filename << ": " << Mix_GetError() << std::endl;
return;
}
#ifdef _WIN32
//The assertion will fail if old MSVC libraries pack .dll is used
assert(Mix_GetMusicType(music) == MUS_MP3_MAD);

View File

@ -259,25 +259,32 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details)
{
//We may need to change music - select new track, music handler will change it if needed
CCS->musich->playMusic(CCS->musich->terrainMusics[LOCPLINT->cb->getTile(ho->visitablePos())->tertype]);
if(details.result == TryMoveHero::TELEPORTATION || details.start == details.end)
if(details.result == TryMoveHero::TELEPORTATION)
{
if(adventureInt->terrain.currentPath)
eraseCurrentPathOf(ho);
return; //teleport - no fancy moving animation
if(adventureInt->terrain.currentPath->nodes.size() && adventureInt->terrain.currentPath->nodes.back().coord == CGHeroInstance::convertPosition(hp, false))
removeLastNodeFromPath(ho);
// else
// eraseCurrentPathOf(ho);
return; //teleport - no fancy moving animation
//TODO: smooth disappear / appear effect
}
if ((details.result != TryMoveHero::SUCCESS && details.result != TryMoveHero::FAILED) //hero didn't change tile but visit succeeded
if(details.start == details.end) //last step
{
eraseCurrentPathOf(ho);
return;
}
if (ho->pos != details.end //hero didn't change tile but visit succeeded
|| directlyAttackingCreature) // or creature was attacked from endangering tile.
{
eraseCurrentPathOf(ho);
}
else if(adventureInt->terrain.currentPath && details.result == TryMoveHero::SUCCESS) //&& hero is moving
else if(adventureInt->terrain.currentPath && ho->pos == details.end) //&& hero is moving
{
//remove one node from the path (the one we went)
adventureInt->terrain.currentPath->nodes.erase(adventureInt->terrain.currentPath->nodes.end()-1);
if(!adventureInt->terrain.currentPath->nodes.size()) //if it was the last one, remove entire path
eraseCurrentPathOf(ho);
removeLastNodeFromPath(ho);
}
}
@ -1127,6 +1134,10 @@ bool CPlayerInterface::moveHero( const CGHeroInstance *h, CGPath path )
for(int i=path.nodes.size()-1; i>0 && (stillMoveHero.data == CONTINUE_MOVE || curTile->blocked); i--)
{
//changing z coordinate means we're moving through subterranean gate -> it's done automatically upon the visit, so we don't have to request that move here
if(path.nodes[i-1].coord.z != path.nodes[i].coord.z)
continue;
//stop sending move requests if the next node can't be reached at the current turn (hero exhausted his move points)
if(path.nodes[i-1].turns)
{
@ -1143,7 +1154,8 @@ bool CPlayerInterface::moveHero( const CGHeroInstance *h, CGPath path )
{
newTerrain = cb->getTile(CGHeroInstance::convertPosition(path.nodes[i].coord, false))->tertype;
if (newTerrain != currentTerrain) {
if (newTerrain != currentTerrain)
{
CCS->soundh->stopSound(sh);
sh = CCS->soundh->playSound(CCS->soundh->horseSounds[newTerrain], -1);
currentTerrain = newTerrain;
@ -2024,6 +2036,13 @@ void CPlayerInterface::eraseCurrentPathOf( const CGHeroInstance * ho, bool check
adventureInt->terrain.currentPath = NULL;
}
void CPlayerInterface::removeLastNodeFromPath(const CGHeroInstance *ho)
{
adventureInt->terrain.currentPath->nodes.erase(adventureInt->terrain.currentPath->nodes.end()-1);
if(!adventureInt->terrain.currentPath->nodes.size()) //if it was the last one, remove entire path
eraseCurrentPathOf(ho);
}
CGPath * CPlayerInterface::getAndVerifyPath(const CGHeroInstance * h)
{
if(vstd::contains(paths,h)) //hero has assigned path

View File

@ -256,6 +256,7 @@ public:
void movementPxStep( const TryMoveHero &details, int i, const int3 &hp, const CGHeroInstance * ho );//performing step of movement
void finishMovement( const TryMoveHero &details, const int3 &hp, const CGHeroInstance * ho ); //finish movement
void eraseCurrentPathOf( const CGHeroInstance * ho, bool checkForExistanceOfPath = true );
void removeLastNodeFromPath(const CGHeroInstance *ho);
CGPath *getAndVerifyPath( const CGHeroInstance * h );
void acceptTurn(); //used during hot seat after your turn message is close
void tryDiggging(const CGHeroInstance *h);

View File

@ -1059,7 +1059,7 @@ void CMapHandler::getTerrainDescr( const int3 &pos, std::string & out, bool terN
}
}
if(t.siodmyTajemniczyBajt & 128)
if(t.hasFavourableWinds())
out = CGI->generaltexth->names[225]; //Favourable Winds
else if(terName)
out = CGI->generaltexth->terrainNames[t.tertype];

View File

@ -104,8 +104,11 @@ typedef si32 TQuantity;
typedef ui32 TCreature; //creature id
const int ARMY_SIZE = 7;
const int HEROI_TYPE = 34,
const int
BOATI_TYPE = 8,
HEROI_TYPE = 34,
TOWNI_TYPE = 98,
SUBTERRANEAN_GATE_TYPE = 103,
CREI_TYPE = 54,
EVENTI_TYPE = 26;

2
int3.h
View File

@ -35,6 +35,8 @@ public:
{return int3(-x,-y,-z);}
inline double dist2d(const int3 other) const //distance (z coord is not used)
{return std::sqrt((double)(x-other.x)*(x-other.x) + (y-other.y)*(y-other.y));}
inline bool areNeighbours(const int3 other) const
{return dist2d(other) < 2. && z == other.z;}
inline void operator+=(const int3 & i)
{
x+=i.x;

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;
}

View File

@ -228,13 +228,12 @@ struct CPathNode
struct CGPathNode
{
enum
enum EAccessibility
{
ACCESSIBLE=1, //tile can be entered and passed
VISITABLE, //tile can be entered as the last tile in path
BLOCKVIS, //visitable from neighbouring tile but not passable
BLOCKED, //tile can't be entered nor visited
FLYABLE //if hero flies, he can pass this tile
BLOCKED //tile can't be entered nor visited
};
ui8 accessible; //the enum above
@ -313,6 +312,40 @@ struct DLL_EXPORT DuelParameters
}
};
class CPathfinder : private CGameInfoCallback
{
public:
bool useSubterraneanGates;
bool allowEmbarkAndDisembark;
CPathsInfo &out;
const CGHeroInstance *hero;
const std::vector<std::vector<std::vector<ui8> > > &FoW;
std::list<CGPathNode*> mq; //BFS queue -> nodes to be checked
int3 curPos;
CGPathNode *cp; //current (source) path node -> we took it from the queue
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;
CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero);;
CGPathNode *getNode(const int3 &coord);
void initializeGraph();
CGPathNode::EAccessibility evaluateAccessibility(const TerrainTile *tinfo) const;
void calculatePaths(int3 src = int3(-1,-1,-1), int movement = -1); //calculates possible paths for hero, by default uses current hero position and movement left; returns pointer to newly allocated CPath or NULL if path does not exists
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 canStepOntoDst() const;
bool goodForLandSeaTransition(); //checks if current move will be between sea<->land. If so, checks it legality (returns false if movement is not possible) and sets useEmbarkCost
};
struct BattleInfo;

View File

@ -1479,6 +1479,14 @@ CBonusSystemNode * CGHeroInstance::whereShouldBeAttached(CGameState *gs)
return CArmedInstance::whereShouldBeAttached(gs);
}
int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark /*= false*/) const
{
if(hasBonusOfType(Bonus::FREE_SHIP_BOARDING))
return (MPsBefore - basicCost) * ((float)(maxMovePoints(disembark)) / maxMovePoints(!disembark));
return 0; //take all MPs otherwise
}
void CGDwelling::initObj()
{
switch(ID)
@ -3573,22 +3581,8 @@ void CGTeleport::onHeroVisit( const CGHeroInstance * h ) const
break;
case 103: //find nearest subterranean gate on the other level
{
int i=0;
for(; i < gates.size(); i++)
{
if(gates[i].first == id)
{
destinationid = gates[i].second;
break;
}
else if(gates[i].second == id)
{
destinationid = gates[i].first;
break;
}
}
if(destinationid < 0 || i == gates.size()) //no exit
destinationid = getMatchingGate(id);
if(destinationid < 0) //no exit
{
InfoWindow iw;
iw.player = h->tempOwner;
@ -3676,6 +3670,19 @@ void CGTeleport::postInit() //matches subterranean gates into pairs
objs.erase(103);
}
int CGTeleport::getMatchingGate(int id)
{
for(int i=0; i < gates.size(); i++)
{
if(gates[i].first == id)
return gates[i].second;
if(gates[i].second == id)
return gates[i].first;
}
return -1;
}
void CGArtifact::initObj()
{
blockVisit = true;

View File

@ -354,6 +354,7 @@ public:
void setSecSkillLevel(SecondarySkill which, int val, bool abs);// abs == 0 - changes by value; 1 - sets to value
int maxMovePoints(bool onLand) const;
int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false) const;
// const CArtifact* getArtAtPos(ui16 pos) const; //NULL - no artifact
// const CArtifact * getArt(int pos) const;
@ -952,6 +953,7 @@ public:
void onHeroVisit(const CGHeroInstance * h) const;
void initObj();
static void postInit();
static int getMatchingGate(int id); //receives id of one subterranean gate and returns id of the paired one, -1 if none
template <typename Handler> void serialize(Handler &h, const int version)
{

View File

@ -1024,6 +1024,12 @@ CGameInfoCallback::CGameInfoCallback()
{
}
CGameInfoCallback::CGameInfoCallback(CGameState *GS, int Player)
{
gs = GS;
player = Player;
}
const std::vector< std::vector< std::vector<unsigned char> > > & CPlayerSpecificInfoCallback::getVisibilityMap() const
{
//boost::shared_lock<boost::shared_mutex> lock(*gs->mx);

View File

@ -2164,3 +2164,18 @@ bool TerrainTile::isClear(const TerrainTile *from /*= NULL*/) const
{
return entrableTerrain(from) && !blocked;
}
int TerrainTile::topVisitableID() const
{
return visitableObjects.size() ? visitableObjects.back()->ID : -1;
}
bool TerrainTile::isCoastal() const
{
return siodmyTajemniczyBajt & 64;
}
bool TerrainTile::hasFavourableWinds() const
{
return siodmyTajemniczyBajt & 128;
}

View File

@ -71,6 +71,10 @@ struct DLL_EXPORT TerrainTile
bool entrableTerrain(const TerrainTile *from = NULL) const; //checks if terrain is not a rock. If from is water/land, same type is also required.
bool entrableTerrain(bool allowLand, bool allowSea) const; //checks if terrain is not a rock. If from is water/land, same type is also required.
bool isClear(const TerrainTile *from = NULL) const; //checks for blocking objs and terraint type (water / land)
int topVisitableID() const; //returns ID of the top visitable ovject or -1 if there is none
bool isCoastal() const;
bool hasFavourableWinds() const;
};
/// name of starting hero

View File

@ -1423,13 +1423,7 @@ bool CGameHandler::moveHero( si32 hid, int3 dst, ui8 instant, ui8 asker /*= 255*
if(!h->boat && t.visitableObjects.size() && t.visitableObjects.back()->ID == 8)
{
tmh.result = TryMoveHero::EMBARK;
if (h->hasBonusOfType(Bonus::FREE_SHIP_BOARDING))
{
tmh.movePoints = (h->movement - cost)*((float)(h->maxMovePoints(false)) / h->maxMovePoints(true));
}
else
tmh.movePoints = 0; //embarking takes all move points
tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, false);
getTilesInRange(tmh.fowRevealed,h->getSightCenter()+(tmh.end-tmh.start),h->getSightRadious(),h->tempOwner,1);
sendAndApply(&tmh);
return true;
@ -1437,14 +1431,9 @@ bool CGameHandler::moveHero( si32 hid, int3 dst, ui8 instant, ui8 asker /*= 255*
//hero leaves the boat
else if(h->boat && t.tertype != TerrainTile::water && !t.blocked)
{
//TODO? code similarity with the block above
tmh.result = TryMoveHero::DISEMBARK;
if (h->hasBonusOfType(Bonus::FREE_SHIP_BOARDING))
{
tmh.movePoints = (h->movement - cost)*((float)(h->maxMovePoints(true)) / h->maxMovePoints(false));
}
else
tmh.movePoints = 0; //disembarking takes all move points
tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, true);
getTilesInRange(tmh.fowRevealed,h->getSightCenter()+(tmh.end-tmh.start),h->getSightRadious(),h->tempOwner,1);
sendAndApply(&tmh);
tryAttackingGuard(guardPos, h);