From 6ef44bde5af2cbcc38495f91982b73b63701ea65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=2E=20Urba=C5=84czyk?= Date: Mon, 19 Sep 2011 20:50:25 +0000 Subject: [PATCH] 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. --- client/CAdvmapInterface.cpp | 44 ++- client/CMusicHandler.cpp | 6 + client/CPlayerInterface.cpp | 39 ++- client/CPlayerInterface.h | 1 + client/mapHandler.cpp | 2 +- global.h | 5 +- int3.h | 2 + lib/CGameState.cpp | 648 ++++++++++++++++-------------------- lib/CGameState.h | 39 ++- lib/CObjectHandler.cpp | 39 ++- lib/CObjectHandler.h | 2 + lib/IGameCallback.cpp | 6 + lib/map.cpp | 15 + lib/map.h | 4 + server/CGameHandler.cpp | 17 +- 15 files changed, 440 insertions(+), 429 deletions(-) diff --git a/client/CAdvmapInterface.cpp b/client/CAdvmapInterface.cpp index 410f86b29..391ee95fc 100644 --- a/client/CAdvmapInterface.cpp +++ b/client/CAdvmapInterface.cpp @@ -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 & 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 & 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) diff --git a/client/CMusicHandler.cpp b/client/CMusicHandler.cpp index c3f6522ac..4a8b2ef71 100644 --- a/client/CMusicHandler.cpp +++ b/client/CMusicHandler.cpp @@ -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); diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 8dd100ca2..efa723db8 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -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 diff --git a/client/CPlayerInterface.h b/client/CPlayerInterface.h index 086178baf..c7b0ebfcf 100644 --- a/client/CPlayerInterface.h +++ b/client/CPlayerInterface.h @@ -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); diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index 633343e2e..88bd5f0b0 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -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]; diff --git a/global.h b/global.h index 482cd8cc8..1e6bd50a5 100644 --- a/global.h +++ b/global.h @@ -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; diff --git a/int3.h b/int3.h index 6cc880933..430da6d84 100644 --- a/int3.h +++ b/int3.h @@ -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; diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index f8c74385e..e6a343547 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -1762,10 +1762,9 @@ void CGameState::loadTownDInfos() void CGameState::getNeighbours(const TerrainTile &srct, int3 tile, std::vector &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::vectorgetTile(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 > > &FoW = getPlayerTeam(hero->tempOwner)->fogOfWarMap; - - //graph initialization - std::vector< std::vector > graph; - graph.resize(map->width); - for(size_t i=0; iheight); - for(size_t j=0; jterrain[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 mq; - mq.push(graph[src.x][src.y]); - - ui32 curDist = 0xffffffff; //total cost of path - init with max possible val - - std::vector 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 > > &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<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 mq; - mq.push(&graph[src.x][src.y][src.z]); - - //ui32 curDist = 0xffffffff; //total cost of path - init with max possible val - - std::vector 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 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<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; +} \ No newline at end of file diff --git a/lib/CGameState.h b/lib/CGameState.h index dc6f33ddc..1b55040ec 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -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 > > &FoW; + + std::list 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; diff --git a/lib/CObjectHandler.cpp b/lib/CObjectHandler.cpp index 930ef8d2f..57510b94a 100644 --- a/lib/CObjectHandler.cpp +++ b/lib/CObjectHandler.cpp @@ -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; diff --git a/lib/CObjectHandler.h b/lib/CObjectHandler.h index 66ec9103b..19e310bc6 100644 --- a/lib/CObjectHandler.h +++ b/lib/CObjectHandler.h @@ -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 void serialize(Handler &h, const int version) { diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index 3a6d96756..6f46b9a95 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -1024,6 +1024,12 @@ CGameInfoCallback::CGameInfoCallback() { } +CGameInfoCallback::CGameInfoCallback(CGameState *GS, int Player) +{ + gs = GS; + player = Player; +} + const std::vector< std::vector< std::vector > > & CPlayerSpecificInfoCallback::getVisibilityMap() const { //boost::shared_lock lock(*gs->mx); diff --git a/lib/map.cpp b/lib/map.cpp index cbc7fe138..43cc89721 100644 --- a/lib/map.cpp +++ b/lib/map.cpp @@ -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; +} \ No newline at end of file diff --git a/lib/map.h b/lib/map.h index e730a133f..15a45d707 100644 --- a/lib/map.h +++ b/lib/map.h @@ -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 diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 80bb4a327..1a18b867e 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -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);