From 118039a368102d2c97462e8d0552624f994d2e0f Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Mon, 2 Nov 2015 11:05:26 +0300 Subject: [PATCH 01/83] Pathfinding: add copy-pasted EPathfindingLayer --- lib/GameConstants.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 0a1f74945..5cf6c1ccb 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -745,6 +745,25 @@ DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const ETerrainType acti ID_LIKE_OPERATORS_DECLS(ETerrainType, ETerrainType::EETerrainType) +class DLL_LINKAGE EPathfindingLayer +{ +public: + enum EEPathfindingLayer + { + AUTO = -1, LAND = 0, SAIL = 1, WATER, AIR, NUM_LAYERS + }; + + EPathfindingLayer(EEPathfindingLayer _num = AUTO) : num(_num) + {} + + ID_LIKE_CLASS_COMMON(EPathfindingLayer, EEPathfindingLayer) + + EEPathfindingLayer num; +}; + +DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EPathfindingLayer actionType); + +ID_LIKE_OPERATORS_DECLS(EPathfindingLayer, EPathfindingLayer::EEPathfindingLayer) class BFieldType { From b8253206c734e53e0ca78ccc65a30b5c1baeae34 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Mon, 2 Nov 2015 11:06:06 +0300 Subject: [PATCH 02/83] Pathfinding: make related code aware that layers exist --- lib/CPathfinder.cpp | 100 +++++++++++++++++++++++++++++++------------- lib/CPathfinder.h | 13 +++--- 2 files changed, 80 insertions(+), 33 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 2eae0500c..6d35f0157 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -79,7 +79,7 @@ void CPathfinder::calculatePaths() //logGlobal->infoStream() << boost::format("Calculating paths for hero %s (adress %d) of player %d") % hero->name % hero % hero->tempOwner; //initial tile - set cost on 0 and add to the queue - CGPathNode &initialNode = *getNode(out.hpos); + CGPathNode &initialNode = *getNode(out.hpos, hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND); initialNode.turns = 0; initialNode.moveRemains = hero->movement; mq.push_back(&initialNode); @@ -100,7 +100,7 @@ void CPathfinder::calculatePaths() addNeighbours(cp->coord); for(auto & neighbour : neighbours) { - dp = getNode(neighbour); + dp = getNode(neighbour, EPathfindingLayer::LAND); dt = &gs->map->getTile(neighbour); useEmbarkCost = 0; //0 - usual movement; 1 - embark; 2 - disembark @@ -138,12 +138,12 @@ void CPathfinder::calculatePaths() } //neighbours loop //just add all passable teleport exits - if(sTileObj) + if(sTileObj && canVisitObject()) { addTeleportExits(); for(auto & neighbour : neighbours) { - dp = getNode(neighbour); + dp = getNode(neighbour, EPathfindingLayer::LAND); if(isBetterWay(movement, turn)) { dp->moveRemains = movement; @@ -164,13 +164,18 @@ void CPathfinder::addNeighbours(const int3 &coord) std::vector tiles; gs->getNeighbours(*ct, coord, tiles, boost::logic::indeterminate, !cp->land); sTileObj = ct->topVisitableObj(coord == CGHeroInstance::convertPosition(hero->pos, false)); - if(sTileObj) + if(canVisitObject()) { - for(int3 tile: tiles) + if(sTileObj) { - if(canMoveBetween(tile, sTileObj->visitablePos())) - neighbours.push_back(tile); + for(int3 tile: tiles) + { + if(canMoveBetween(tile, sTileObj->visitablePos())) + neighbours.push_back(tile); + } } + else + vstd::concatenate(neighbours, tiles); } else vstd::concatenate(neighbours, tiles); @@ -310,7 +315,20 @@ bool CPathfinder::isDestinationGuardian() void CPathfinder::initializeGraph() { int3 pos; - CGPathNode ***graph = out.nodes; + CGPathNode ****graph = out.nodes; + const TerrainTile *tinfo; + auto addNode = [&](EPathfindingLayer layer) + { + CGPathNode &node = graph[pos.x][pos.y][pos.z][layer]; + node.accessible = evaluateAccessibility(pos, tinfo); + node.turns = 0xff; + node.moveRemains = 0; + node.coord = pos; + node.land = tinfo->terType != ETerrainType::WATER; + node.theNodeBefore = nullptr; + node.layer = layer; + }; + for(pos.x=0; pos.x < out.sizes.x; ++pos.x) { for(pos.y=0; pos.y < out.sizes.y; ++pos.y) @@ -318,21 +336,33 @@ void CPathfinder::initializeGraph() for(pos.z=0; pos.z < out.sizes.z; ++pos.z) { const TerrainTile *tinfo = &gs->map->getTile(pos); - CGPathNode &node = graph[pos.x][pos.y][pos.z]; - node.accessible = evaluateAccessibility(pos, tinfo); - node.turns = 0xff; - node.moveRemains = 0; - node.coord = pos; - node.land = tinfo->terType != ETerrainType::WATER; - node.theNodeBefore = nullptr; + switch (tinfo->terType) + { + case ETerrainType::WRONG: + case ETerrainType::BORDER: + case ETerrainType::ROCK: + break; + case ETerrainType::WATER: + addNode(EPathfindingLayer::SAIL); + if(options.useFlying) + addNode(EPathfindingLayer::AIR); + if(options.useWaterWalking) + addNode(EPathfindingLayer::WATER); + break; + default: + addNode(EPathfindingLayer::LAND); + if(options.useFlying) + addNode(EPathfindingLayer::AIR); + break; + } } } } } -CGPathNode *CPathfinder::getNode(const int3 &coord) +CGPathNode *CPathfinder::getNode(const int3 &coord, const EPathfindingLayer &layer) { - return &out.nodes[coord.x][coord.y][coord.z]; + return &out.nodes[coord.x][coord.y][coord.z][layer]; } CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const int3 &pos, const TerrainTile *tinfo) const @@ -415,6 +445,12 @@ bool CPathfinder::addTeleportWhirlpool(const CGWhirlpool * obj) const return options.useTeleportWhirlpool && obj; } +bool CPathfinder::canVisitObject() const +{ + //hero can't visit objects while walking on water or flying + return cp->layer == EPathfindingLayer::LAND || cp->layer == EPathfindingLayer::SAIL; +} + CGPathNode::CGPathNode() :coord(-1,-1,-1) { @@ -455,13 +491,17 @@ CPathsInfo::CPathsInfo( const int3 &Sizes ) :sizes(Sizes) { hero = nullptr; - nodes = new CGPathNode**[sizes.x]; + nodes = new CGPathNode***[sizes.x]; for(int i = 0; i < sizes.x; i++) { - nodes[i] = new CGPathNode*[sizes.y]; + nodes[i] = new CGPathNode**[sizes.y]; for(int j = 0; j < sizes.y; j++) { - nodes[i][j] = new CGPathNode[sizes.z]; + nodes[i][j] = new CGPathNode*[sizes.z]; + for (int z = 0; z < sizes.z; z++) + { + nodes[i][j][z] = new CGPathNode[EPathfindingLayer::NUM_LAYERS]; + } } } } @@ -472,6 +512,10 @@ CPathsInfo::~CPathsInfo() { for(int j = 0; j < sizes.y; j++) { + for (int z = 0; z < sizes.z; z++) + { + delete [] nodes[i][j][z]; + } delete [] nodes[i][j]; } delete [] nodes[i]; @@ -479,21 +523,21 @@ CPathsInfo::~CPathsInfo() delete [] nodes; } -const CGPathNode * CPathsInfo::getPathInfo( int3 tile ) const +const CGPathNode * CPathsInfo::getPathInfo(const int3 &tile, const EPathfindingLayer &layer) const { boost::unique_lock pathLock(pathMx); - if(tile.x >= sizes.x || tile.y >= sizes.y || tile.z >= sizes.z) + if(tile.x >= sizes.x || tile.y >= sizes.y || tile.z >= sizes.z || layer >= EPathfindingLayer::NUM_LAYERS) return nullptr; - return &nodes[tile.x][tile.y][tile.z]; + return &nodes[tile.x][tile.y][tile.z][layer]; } -bool CPathsInfo::getPath( const int3 &dst, CGPath &out ) const +bool CPathsInfo::getPath(const int3 &dst, const EPathfindingLayer &layer, CGPath &out) const { boost::unique_lock pathLock(pathMx); out.nodes.clear(); - const CGPathNode *curnode = &nodes[dst.x][dst.y][dst.z]; + const CGPathNode *curnode = &nodes[dst.x][dst.y][dst.z][layer]; if(!curnode->theNodeBefore) return false; @@ -507,12 +551,12 @@ bool CPathsInfo::getPath( const int3 &dst, CGPath &out ) const return true; } -int CPathsInfo::getDistance( int3 tile ) const +int CPathsInfo::getDistance(const int3 &tile, const EPathfindingLayer &layer) const { boost::unique_lock pathLock(pathMx); CGPath ret; - if(getPath(tile, ret)) + if(getPath(tile, layer, ret)) return ret.nodes.size(); else return 255; diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 38d13821e..71f47e3bd 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -36,6 +36,7 @@ struct DLL_LINKAGE CGPathNode ui32 moveRemains; //remaining tiles after hero reaches the tile CGPathNode * theNodeBefore; int3 coord; //coordinates + EPathfindingLayer layer; CGPathNode(); bool reachable() const; @@ -57,13 +58,13 @@ struct DLL_LINKAGE CPathsInfo const CGHeroInstance *hero; int3 hpos; int3 sizes; - CGPathNode ***nodes; //[w][h][level] + CGPathNode ****nodes; //[w][h][level][layer] CPathsInfo(const int3 &Sizes); ~CPathsInfo(); - const CGPathNode * getPathInfo( int3 tile ) const; - bool getPath(const int3 &dst, CGPath &out) const; - int getDistance( int3 tile ) const; + const CGPathNode * getPathInfo(const int3 &tile, const EPathfindingLayer &layer) const; + bool getPath(const int3 &dst, const EPathfindingLayer &layer, CGPath &out) const; + int getDistance(const int3 &tile, const EPathfindingLayer &layer) const; }; class CPathfinder : private CGameInfoCallback @@ -113,7 +114,7 @@ private: void initializeGraph(); - CGPathNode *getNode(const int3 &coord); + CGPathNode *getNode(const int3 &coord, const EPathfindingLayer &layer); CGPathNode::EAccessibility evaluateAccessibility(const int3 &pos, const TerrainTile *tinfo) const; bool canMoveBetween(const int3 &a, const int3 &b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility) @@ -121,4 +122,6 @@ private: bool addTeleportOneWay(const CGTeleport * obj) const; bool addTeleportOneWayRandom(const CGTeleport * obj) const; bool addTeleportWhirlpool(const CGWhirlpool * obj) const; + + bool canVisitObject() const; }; From 2b6e1498d2c43655dbc7811f511b7445902d46ca Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Mon, 2 Nov 2015 11:14:32 +0300 Subject: [PATCH 03/83] Pathfinding: change argument order for getPath and AUTO layer as default This still need investigation, but likely most of external code shouldn't be aware of layers in order to work properly because only LAND and SAIL can be targeted and single tile can't have both of these layers. --- AI/VCAI/AIUtility.cpp | 2 +- AI/VCAI/VCAI.cpp | 4 ++-- client/CPlayerInterface.cpp | 4 ++-- client/windows/CAdvmapInterface.cpp | 4 ++-- lib/CPathfinder.cpp | 4 ++-- lib/CPathfinder.h | 6 +++--- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/AI/VCAI/AIUtility.cpp b/AI/VCAI/AIUtility.cpp index f05867d61..e55879ce1 100644 --- a/AI/VCAI/AIUtility.cpp +++ b/AI/VCAI/AIUtility.cpp @@ -372,7 +372,7 @@ int3 whereToExplore(HeroPtr h) { int3 op = obj->visitablePos(); CGPath p; - ai->myCb->getPathsInfo(h.get())->getPath(op, p); + ai->myCb->getPathsInfo(h.get())->getPath(p, ops); if (p.nodes.size() && p.endPos() == op && p.nodes.size() <= DIST_LIMIT) if (ai->isGoodForVisit(obj, h, sm)) nearbyVisitableObjs.push_back(obj); diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index dfa906580..4ef6f169d 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -1840,7 +1840,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) else { CGPath path; - cb->getPathsInfo(h.get())->getPath(dst, path); + cb->getPathsInfo(h.get())->getPath(path, dst); if(path.nodes.empty()) { logAi->errorStream() << "Hero " << h->name << " cannot reach " << dst; @@ -2519,7 +2519,7 @@ int3 VCAI::explorationNewPoint(HeroPtr h) continue; CGPath path; - cb->getPathsInfo(hero)->getPath(tile, path); + cb->getPathsInfo(hero)->getPath(path, tile); float ourValue = (float)howManyTilesWillBeDiscovered(tile, radius, cbp) / (path.nodes.size() + 1); //+1 prevents erratic jumps if (ourValue > bestValue) //avoid costly checks of tiles that don't reveal much diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 1bfe4ae98..a6109b0d6 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1288,7 +1288,7 @@ template void CPlayerInterface::serializeTempl( Handler &h, c for(auto &p : pathsMap) { CGPath path; - cb->getPathsInfo(p.first)->getPath(p.second, path); + cb->getPathsInfo(p.first)->getPath(path, p.second); paths[p.first] = path; logGlobal->traceStream() << boost::format("Restored path for hero %s leading to %s with %d nodes") % p.first->nodeName() % p.second % path.nodes.size(); @@ -2226,7 +2226,7 @@ CGPath * CPlayerInterface::getAndVerifyPath(const CGHeroInstance * h) { assert(h->getPosition(false) == path.startPos()); //update the hero path in case of something has changed on map - if(LOCPLINT->cb->getPathsInfo(h)->getPath(path.endPos(), path)) + if(LOCPLINT->cb->getPathsInfo(h)->getPath(path, path.endPos())) return &path; else paths.erase(h); diff --git a/client/windows/CAdvmapInterface.cpp b/client/windows/CAdvmapInterface.cpp index e1b96e049..ab3ab31b7 100644 --- a/client/windows/CAdvmapInterface.cpp +++ b/client/windows/CAdvmapInterface.cpp @@ -1190,7 +1190,7 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key) CGPath &path = LOCPLINT->paths[h]; terrain.currentPath = &path; int3 dst = h->getPosition(false) + dir; - if(dst != verifyPos(dst) || !LOCPLINT->cb->getPathsInfo(h)->getPath(dst, path)) + if(dst != verifyPos(dst) || !LOCPLINT->cb->getPathsInfo(h)->getPath(path, dst)) { terrain.currentPath = nullptr; return; @@ -1445,7 +1445,7 @@ void CAdvMapInt::tileLClicked(const int3 &mapPos) { CGPath &path = LOCPLINT->paths[currentHero]; terrain.currentPath = &path; - bool gotPath = LOCPLINT->cb->getPathsInfo(currentHero)->getPath(mapPos, path); //try getting path, erase if failed + bool gotPath = LOCPLINT->cb->getPathsInfo(currentHero)->getPath(path, mapPos); //try getting path, erase if failed updateMoveHero(currentHero); if (!gotPath) LOCPLINT->eraseCurrentPathOf(currentHero); diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 6d35f0157..7d052494a 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -532,7 +532,7 @@ const CGPathNode * CPathsInfo::getPathInfo(const int3 &tile, const EPathfindingL return &nodes[tile.x][tile.y][tile.z][layer]; } -bool CPathsInfo::getPath(const int3 &dst, const EPathfindingLayer &layer, CGPath &out) const +bool CPathsInfo::getPath(CGPath &out, const int3 &dst, const EPathfindingLayer &layer) const { boost::unique_lock pathLock(pathMx); @@ -556,7 +556,7 @@ int CPathsInfo::getDistance(const int3 &tile, const EPathfindingLayer &layer) co boost::unique_lock pathLock(pathMx); CGPath ret; - if(getPath(tile, layer, ret)) + if(getPath(ret, tile, layer)) return ret.nodes.size(); else return 255; diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 71f47e3bd..d62e1cb78 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -62,9 +62,9 @@ struct DLL_LINKAGE CPathsInfo CPathsInfo(const int3 &Sizes); ~CPathsInfo(); - const CGPathNode * getPathInfo(const int3 &tile, const EPathfindingLayer &layer) const; - bool getPath(const int3 &dst, const EPathfindingLayer &layer, CGPath &out) const; - int getDistance(const int3 &tile, const EPathfindingLayer &layer) const; + const CGPathNode * getPathInfo(const int3 &tile, const EPathfindingLayer &layer = EPathfindingLayer::AUTO) const; + bool getPath(CGPath &out, const int3 &dst, const EPathfindingLayer &layer = EPathfindingLayer::AUTO) const; + int getDistance(const int3 &tile, const EPathfindingLayer &layer = EPathfindingLayer::AUTO) const; }; class CPathfinder : private CGameInfoCallback From 4b64bec7112898f115c1d5e76df4d7254b6a0c74 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Mon, 2 Nov 2015 13:25:01 +0300 Subject: [PATCH 04/83] EPathfindingLayer: copy other code from ETerrainType for debugging --- AI/VCAI/AIUtility.cpp | 2 +- lib/CPathfinder.cpp | 1 + lib/GameConstants.cpp | 29 +++++++++++++++++++++++++++++ lib/GameConstants.h | 6 ++++-- 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/AI/VCAI/AIUtility.cpp b/AI/VCAI/AIUtility.cpp index e55879ce1..fb2c35bcb 100644 --- a/AI/VCAI/AIUtility.cpp +++ b/AI/VCAI/AIUtility.cpp @@ -372,7 +372,7 @@ int3 whereToExplore(HeroPtr h) { int3 op = obj->visitablePos(); CGPath p; - ai->myCb->getPathsInfo(h.get())->getPath(p, ops); + ai->myCb->getPathsInfo(h.get())->getPath(p, op); if (p.nodes.size() && p.endPos() == op && p.nodes.size() <= DIST_LIMIT) if (ai->isGoodForVisit(obj, h, sm)) nearbyVisitableObjs.push_back(obj); diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 7d052494a..efa1446f5 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -459,6 +459,7 @@ CGPathNode::CGPathNode() moveRemains = 0; turns = 255; theNodeBefore = nullptr; + layer = EPathfindingLayer::WRONG; } bool CGPathNode::reachable() const diff --git a/lib/GameConstants.cpp b/lib/GameConstants.cpp index 9a48b476f..d21f3f40c 100644 --- a/lib/GameConstants.cpp +++ b/lib/GameConstants.cpp @@ -63,6 +63,8 @@ ID_LIKE_OPERATORS(Obj, Obj::EObj) ID_LIKE_OPERATORS(ETerrainType, ETerrainType::EETerrainType) +ID_LIKE_OPERATORS(EPathfindingLayer, EPathfindingLayer::EEPathfindingLayer) + ID_LIKE_OPERATORS(ArtifactID, ArtifactID::EArtifactID) ID_LIKE_OPERATORS(ArtifactPosition, ArtifactPosition::EArtifactPosition) @@ -160,3 +162,30 @@ std::string ETerrainType::toString() const ss << *this; return ss.str(); } + +std::ostream & operator<<(std::ostream & os, const EPathfindingLayer actionType) +{ + static const std::map pathfinderLayerToString = + { + #define DEFINE_ELEMENT(element) {EPathfindingLayer::element, #element} + DEFINE_ELEMENT(WRONG), + DEFINE_ELEMENT(AUTO), + DEFINE_ELEMENT(LAND), + DEFINE_ELEMENT(SAIL), + DEFINE_ELEMENT(WATER), + DEFINE_ELEMENT(AIR), + DEFINE_ELEMENT(NUM_LAYERS) + + }; + + auto it = pathfinderLayerToString.find(actionType.num); + if (it == pathfinderLayerToString.end()) return os << ""; + else return os << it->second; +} + +std::string EPathfindingLayer::toString() const +{ + std::stringstream ss; + ss << *this; + return ss.str(); +} diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 5cf6c1ccb..e46c482d1 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -750,15 +750,17 @@ class DLL_LINKAGE EPathfindingLayer public: enum EEPathfindingLayer { - AUTO = -1, LAND = 0, SAIL = 1, WATER, AIR, NUM_LAYERS + WRONG = -2, AUTO = -1, LAND = 0, SAIL = 1, WATER, AIR, NUM_LAYERS }; - EPathfindingLayer(EEPathfindingLayer _num = AUTO) : num(_num) + EPathfindingLayer(EEPathfindingLayer _num = WRONG) : num(_num) {} ID_LIKE_CLASS_COMMON(EPathfindingLayer, EEPathfindingLayer) EEPathfindingLayer num; + + std::string toString() const; }; DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EPathfindingLayer actionType); From 9c1c7d0caf45c94d108ed845f156959f88ed1a8d Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Mon, 2 Nov 2015 14:04:26 +0300 Subject: [PATCH 05/83] Pathfinding: move getNode into CPathsInfo --- lib/CPathfinder.cpp | 27 +++++++++++++++++---------- lib/CPathfinder.h | 2 +- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index efa1446f5..79b6a4f06 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -79,7 +79,7 @@ void CPathfinder::calculatePaths() //logGlobal->infoStream() << boost::format("Calculating paths for hero %s (adress %d) of player %d") % hero->name % hero % hero->tempOwner; //initial tile - set cost on 0 and add to the queue - CGPathNode &initialNode = *getNode(out.hpos, hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND); + CGPathNode &initialNode = *out.getNode(out.hpos, hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND); initialNode.turns = 0; initialNode.moveRemains = hero->movement; mq.push_back(&initialNode); @@ -100,7 +100,7 @@ void CPathfinder::calculatePaths() addNeighbours(cp->coord); for(auto & neighbour : neighbours) { - dp = getNode(neighbour, EPathfindingLayer::LAND); + dp = out.getNode(neighbour, EPathfindingLayer::LAND); dt = &gs->map->getTile(neighbour); useEmbarkCost = 0; //0 - usual movement; 1 - embark; 2 - disembark @@ -143,7 +143,7 @@ void CPathfinder::calculatePaths() addTeleportExits(); for(auto & neighbour : neighbours) { - dp = getNode(neighbour, EPathfindingLayer::LAND); + dp = out.getNode(neighbour, EPathfindingLayer::LAND); if(isBetterWay(movement, turn)) { dp->moveRemains = movement; @@ -360,11 +360,6 @@ void CPathfinder::initializeGraph() } } -CGPathNode *CPathfinder::getNode(const int3 &coord, const EPathfindingLayer &layer) -{ - return &out.nodes[coord.x][coord.y][coord.z][layer]; -} - CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const int3 &pos, const TerrainTile *tinfo) const { CGPathNode::EAccessibility ret = (tinfo->blocked ? CGPathNode::BLOCKED : CGPathNode::ACCESSIBLE); @@ -530,7 +525,7 @@ const CGPathNode * CPathsInfo::getPathInfo(const int3 &tile, const EPathfindingL if(tile.x >= sizes.x || tile.y >= sizes.y || tile.z >= sizes.z || layer >= EPathfindingLayer::NUM_LAYERS) return nullptr; - return &nodes[tile.x][tile.y][tile.z][layer]; + return getNode(tile, layer); } bool CPathsInfo::getPath(CGPath &out, const int3 &dst, const EPathfindingLayer &layer) const @@ -538,7 +533,7 @@ bool CPathsInfo::getPath(CGPath &out, const int3 &dst, const EPathfindingLayer & boost::unique_lock pathLock(pathMx); out.nodes.clear(); - const CGPathNode *curnode = &nodes[dst.x][dst.y][dst.z][layer]; + const CGPathNode *curnode = getNode(dst, layer); if(!curnode->theNodeBefore) return false; @@ -562,3 +557,15 @@ int CPathsInfo::getDistance(const int3 &tile, const EPathfindingLayer &layer) co else return 255; } + +CGPathNode *CPathsInfo::getNode(const int3 &coord, const EPathfindingLayer &layer) const +{ + if(layer != EPathfindingLayer::AUTO) + return &nodes[coord.x][coord.y][coord.z][layer]; + + auto landNode = &nodes[coord.x][coord.y][coord.z][EPathfindingLayer::LAND]; + if(landNode->theNodeBefore) + return landNode; + else + return &nodes[coord.x][coord.y][coord.z][EPathfindingLayer::SAIL]; +} diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index d62e1cb78..2e3be5160 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -65,6 +65,7 @@ struct DLL_LINKAGE CPathsInfo const CGPathNode * getPathInfo(const int3 &tile, const EPathfindingLayer &layer = EPathfindingLayer::AUTO) const; bool getPath(CGPath &out, const int3 &dst, const EPathfindingLayer &layer = EPathfindingLayer::AUTO) const; int getDistance(const int3 &tile, const EPathfindingLayer &layer = EPathfindingLayer::AUTO) const; + CGPathNode *getNode(const int3 &coord, const EPathfindingLayer &layer) const; }; class CPathfinder : private CGameInfoCallback @@ -114,7 +115,6 @@ private: void initializeGraph(); - CGPathNode *getNode(const int3 &coord, const EPathfindingLayer &layer); CGPathNode::EAccessibility evaluateAccessibility(const int3 &pos, const TerrainTile *tinfo) const; bool canMoveBetween(const int3 &a, const int3 &b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility) From 400152caee7c7f9995a765b0c2378680248771a8 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Mon, 2 Nov 2015 16:03:03 +0300 Subject: [PATCH 06/83] CPathfinder: draft implementation of layers logic; not yet works --- lib/CPathfinder.cpp | 176 +++++++++++++++++++++++++++----------------- lib/CPathfinder.h | 2 + 2 files changed, 110 insertions(+), 68 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 79b6a4f06..901c7bad5 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -100,40 +100,44 @@ void CPathfinder::calculatePaths() addNeighbours(cp->coord); for(auto & neighbour : neighbours) { - dp = out.getNode(neighbour, EPathfindingLayer::LAND); dt = &gs->map->getTile(neighbour); - useEmbarkCost = 0; //0 - usual movement; 1 - embark; 2 - disembark - - if(!isMovementPossible()) - continue; - - int cost = gs->getMovementCost(hero, cp->coord, dp->coord, movement); - int remains = movement - cost; - if(useEmbarkCost) + for(EPathfindingLayer i = EPathfindingLayer::LAND; i <= EPathfindingLayer::AIR; i.advance(1)) { - remains = hero->movementPointsAfterEmbark(movement, cost, useEmbarkCost - 1); - cost = movement - remains; - } + useEmbarkCost = 0; //0 - usual movement; 1 - embark; 2 - disembark + dp = out.getNode(neighbour, i); + if(cp->layer != i && isLayerTransitionPossible()) + continue; - int turnAtNextTile = turn; - if(remains < 0) - { - //occurs rarely, when hero with low movepoints tries to leave the road - turnAtNextTile++; - int moveAtNextTile = maxMovePoints(cp); - cost = gs->getMovementCost(hero, cp->coord, dp->coord, moveAtNextTile); //cost must be updated, movement points changed :( - remains = moveAtNextTile - cost; - } + if(!isMovementPossible()) + continue; + int cost = gs->getMovementCost(hero, cp->coord, dp->coord, movement); + int remains = movement - cost; + if(useEmbarkCost) + { + remains = hero->movementPointsAfterEmbark(movement, cost, useEmbarkCost - 1); + cost = movement - remains; + } - if(isBetterWay(remains, turnAtNextTile)) - { - assert(dp != cp->theNodeBefore); //two tiles can't point to each other - dp->moveRemains = remains; - dp->turns = turnAtNextTile; - dp->theNodeBefore = cp; + int turnAtNextTile = turn; + if(remains < 0) + { + //occurs rarely, when hero with low movepoints tries to leave the road + turnAtNextTile++; + int moveAtNextTile = maxMovePoints(cp); + cost = gs->getMovementCost(hero, cp->coord, dp->coord, moveAtNextTile); //cost must be updated, movement points changed :( + remains = moveAtNextTile - cost; + } - if(checkDestinationTile()) - mq.push_back(dp); + if(isBetterWay(remains, turnAtNextTile)) + { + assert(dp != cp->theNodeBefore); //two tiles can't point to each other + dp->moveRemains = remains; + dp->turns = turnAtNextTile; + dp->theNodeBefore = cp; + + if(checkDestinationTile()) + mq.push_back(dp); + } } } //neighbours loop @@ -143,7 +147,7 @@ void CPathfinder::calculatePaths() addTeleportExits(); for(auto & neighbour : neighbours) { - dp = out.getNode(neighbour, EPathfindingLayer::LAND); + dp = out.getNode(neighbour, cp->layer); if(isBetterWay(movement, turn)) { dp->moveRemains = movement; @@ -220,38 +224,42 @@ void CPathfinder::addTeleportExits(bool noTeleportExcludes) bool CPathfinder::isMovementPossible() { - if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED) - return false; - - Obj destTopVisObjID = dt->topVisitableId(); - if(cp->land != dp->land) //hero can traverse land<->sea only in special circumstances + switch (dp->layer) { - 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 + case EPathfindingLayer::LAND: + if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED) return false; - if(destTopVisObjID != Obj::HERO && destTopVisObjID != Obj::BOAT) //only boat or hero can be accessed from land - return false; - if(destTopVisObjID == Obj::BOAT) - useEmbarkCost = 1; - } - else //disembark - { - //can disembark only on coastal tiles - if(!dt->isCoastal()) + if(isSourceGuarded() && !isDestinationGuardian()) // Can step into tile of guard 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)) - || dt->visitable) //TODO: passableness problem -> town says it's passable (thus accessible) but we obviously can't disembark onto town gate - return false;; + break; + case EPathfindingLayer::SAIL: + if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED) + return false; + if(isSourceGuarded() && !isDestinationGuardian()) // Can step into tile of guard + return false; - useEmbarkCost = 2; - } + break; + + case EPathfindingLayer::AIR: + if(!options.useFlying) + return false; + if(!canMoveBetween(cp->coord, dp->coord)) + return false; + + break; + + case EPathfindingLayer::WATER: + if(!options.useWaterWalking) + return false; + if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED) + return false; + if(isDestinationGuarded()) + return false; + + break; } - if(isSourceGuarded() && !isDestinationGuardian()) // Can step into tile of guard - return false; return true; } @@ -314,12 +322,9 @@ bool CPathfinder::isDestinationGuardian() void CPathfinder::initializeGraph() { - int3 pos; - CGPathNode ****graph = out.nodes; - const TerrainTile *tinfo; - auto addNode = [&](EPathfindingLayer layer) + auto addNode = [&](EPathfindingLayer layer, const TerrainTile *tinfo, int3 pos) { - CGPathNode &node = graph[pos.x][pos.y][pos.z][layer]; + CGPathNode &node = out.nodes[pos.x][pos.y][pos.z][layer]; node.accessible = evaluateAccessibility(pos, tinfo); node.turns = 0xff; node.moveRemains = 0; @@ -329,6 +334,7 @@ void CPathfinder::initializeGraph() node.layer = layer; }; + int3 pos; for(pos.x=0; pos.x < out.sizes.x; ++pos.x) { for(pos.y=0; pos.y < out.sizes.y; ++pos.y) @@ -343,16 +349,16 @@ void CPathfinder::initializeGraph() case ETerrainType::ROCK: break; case ETerrainType::WATER: - addNode(EPathfindingLayer::SAIL); - if(options.useFlying) - addNode(EPathfindingLayer::AIR); - if(options.useWaterWalking) - addNode(EPathfindingLayer::WATER); + addNode(EPathfindingLayer::SAIL, tinfo, pos); +// if(options.useFlying) + addNode(EPathfindingLayer::AIR, tinfo, pos); +// if(options.useWaterWalking) + addNode(EPathfindingLayer::WATER, tinfo, pos); break; default: - addNode(EPathfindingLayer::LAND); - if(options.useFlying) - addNode(EPathfindingLayer::AIR); + addNode(EPathfindingLayer::LAND, tinfo, pos); +// if(options.useFlying) + addNode(EPathfindingLayer::AIR, tinfo, pos); break; } } @@ -446,6 +452,40 @@ bool CPathfinder::canVisitObject() const return cp->layer == EPathfindingLayer::LAND || cp->layer == EPathfindingLayer::SAIL; } +bool CPathfinder::isLayerTransitionPossible() +{ + Obj destTopVisObjID = dt->topVisitableId(); + if((cp->layer == EPathfindingLayer::AIR || EPathfindingLayer::WATER) + && dp->layer != EPathfindingLayer::LAND) + { + return false; + } + else if(cp->layer == EPathfindingLayer::SAIL && dp->layer != EPathfindingLayer::LAND) + return false; + else if(cp->layer == EPathfindingLayer::SAIL && dp->layer == EPathfindingLayer::LAND) + { + 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)) + || dt->visitable) //TODO: passableness problem -> town says it's passable (thus accessible) but we obviously can't disembark onto town gate + return false; + + useEmbarkCost = 2; + } + else if(cp->layer == EPathfindingLayer::LAND && dp->layer == EPathfindingLayer::SAIL) + { + if(dp->accessible == CGPathNode::ACCESSIBLE || destTopVisObjID < 0) //cannot enter empty water tile from land -> it has to be visitable + return false; + if(destTopVisObjID != Obj::HERO && destTopVisObjID != Obj::BOAT) //only boat or hero can be accessed from land + return false; + if(destTopVisObjID == Obj::BOAT) + useEmbarkCost = 1; + } + return true; +} + CGPathNode::CGPathNode() :coord(-1,-1,-1) { diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 2e3be5160..0626ffe24 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -124,4 +124,6 @@ private: bool addTeleportWhirlpool(const CGWhirlpool * obj) const; bool canVisitObject() const; + + bool isLayerTransitionPossible(); }; From c85c7f4b61fc54e87626acd3ce6a70ea08587a4b Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Tue, 3 Nov 2015 01:29:43 +0300 Subject: [PATCH 07/83] CPathfinder: always initialize all nodes within initializeGraph --- lib/CPathfinder.cpp | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 901c7bad5..0efbc4efb 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -42,8 +42,6 @@ CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance throw std::runtime_error("Wrong checksum"); } - initializeGraph(); - if(hero->canFly()) options.useFlying = true; if(hero->canWalkOnSea()) @@ -51,6 +49,7 @@ CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance if(CGWhirlpool::isProtected(hero)) options.useTeleportWhirlpool = true; + initializeGraph(); neighbours.reserve(16); } @@ -79,10 +78,10 @@ void CPathfinder::calculatePaths() //logGlobal->infoStream() << boost::format("Calculating paths for hero %s (adress %d) of player %d") % hero->name % hero % hero->tempOwner; //initial tile - set cost on 0 and add to the queue - CGPathNode &initialNode = *out.getNode(out.hpos, hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND); - initialNode.turns = 0; - initialNode.moveRemains = hero->movement; - mq.push_back(&initialNode); + CGPathNode *initialNode = out.getNode(out.hpos, hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND); + initialNode->turns = 0; + initialNode->moveRemains = hero->movement; + mq.push_back(initialNode); while(!mq.empty()) { @@ -322,16 +321,16 @@ bool CPathfinder::isDestinationGuardian() void CPathfinder::initializeGraph() { - auto addNode = [&](EPathfindingLayer layer, const TerrainTile *tinfo, int3 pos) + auto initializeNode = [&](int3 pos, EPathfindingLayer layer, const TerrainTile *tinfo) { - CGPathNode &node = out.nodes[pos.x][pos.y][pos.z][layer]; - node.accessible = evaluateAccessibility(pos, tinfo); - node.turns = 0xff; - node.moveRemains = 0; - node.coord = pos; - node.land = tinfo->terType != ETerrainType::WATER; - node.theNodeBefore = nullptr; - node.layer = layer; + auto node = out.getNode(pos, layer); + node->accessible = evaluateAccessibility(pos, tinfo); + node->turns = 0xff; + node->moveRemains = 0; + node->coord = pos; + node->land = tinfo->terType != ETerrainType::WATER; + node->theNodeBefore = nullptr; + node->layer = layer; }; int3 pos; @@ -342,6 +341,10 @@ void CPathfinder::initializeGraph() for(pos.z=0; pos.z < out.sizes.z; ++pos.z) { const TerrainTile *tinfo = &gs->map->getTile(pos); + for(EPathfindingLayer i = EPathfindingLayer::LAND; i <= EPathfindingLayer::AIR; i.advance(1)) + { + initializeNode(pos, i, tinfo); + } switch (tinfo->terType) { case ETerrainType::WRONG: @@ -349,16 +352,16 @@ void CPathfinder::initializeGraph() case ETerrainType::ROCK: break; case ETerrainType::WATER: - addNode(EPathfindingLayer::SAIL, tinfo, pos); +// initializeNode(EPathfindingLayer::SAIL, tinfo, pos); // if(options.useFlying) - addNode(EPathfindingLayer::AIR, tinfo, pos); +// initializeNode(EPathfindingLayer::AIR, tinfo, pos); // if(options.useWaterWalking) - addNode(EPathfindingLayer::WATER, tinfo, pos); +// initializeNode(EPathfindingLayer::WATER, tinfo, pos); break; default: - addNode(EPathfindingLayer::LAND, tinfo, pos); +// initializeNode(EPathfindingLayer::LAND, tinfo, pos); // if(options.useFlying) - addNode(EPathfindingLayer::AIR, tinfo, pos); +// initializeNode(EPathfindingLayer::AIR, tinfo, pos); break; } } From dfd70849e91a85785c13d851d7c0ef74cc861c95 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Tue, 3 Nov 2015 03:25:12 +0300 Subject: [PATCH 08/83] CPathfinder: restore selective tile initialization to initializeGraph This way we can avoid layer checks when calculating paths by ignoring unitialized tiles entirely. Also at this point pathfinder and movement actually works for everything except flying. --- lib/CPathfinder.cpp | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 0efbc4efb..ac60c842f 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -104,11 +104,15 @@ void CPathfinder::calculatePaths() { useEmbarkCost = 0; //0 - usual movement; 1 - embark; 2 - disembark dp = out.getNode(neighbour, i); - if(cp->layer != i && isLayerTransitionPossible()) + if(dp->accessible == CGPathNode::NOT_SET) + continue; + + if(cp->layer != i && !isLayerTransitionPossible()) continue; if(!isMovementPossible()) continue; + int cost = gs->getMovementCost(hero, cp->coord, dp->coord, movement); int remains = movement - cost; if(useEmbarkCost) @@ -241,16 +245,12 @@ bool CPathfinder::isMovementPossible() break; case EPathfindingLayer::AIR: - if(!options.useFlying) - return false; - if(!canMoveBetween(cp->coord, dp->coord)) - return false; + //if(!canMoveBetween(cp->coord, dp->coord)) + // return false; break; case EPathfindingLayer::WATER: - if(!options.useWaterWalking) - return false; if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED) return false; if(isDestinationGuarded()) @@ -341,10 +341,6 @@ void CPathfinder::initializeGraph() for(pos.z=0; pos.z < out.sizes.z; ++pos.z) { const TerrainTile *tinfo = &gs->map->getTile(pos); - for(EPathfindingLayer i = EPathfindingLayer::LAND; i <= EPathfindingLayer::AIR; i.advance(1)) - { - initializeNode(pos, i, tinfo); - } switch (tinfo->terType) { case ETerrainType::WRONG: @@ -352,16 +348,16 @@ void CPathfinder::initializeGraph() case ETerrainType::ROCK: break; case ETerrainType::WATER: -// initializeNode(EPathfindingLayer::SAIL, tinfo, pos); -// if(options.useFlying) -// initializeNode(EPathfindingLayer::AIR, tinfo, pos); -// if(options.useWaterWalking) -// initializeNode(EPathfindingLayer::WATER, tinfo, pos); + initializeNode(pos, EPathfindingLayer::SAIL, tinfo); + if(options.useFlying) + initializeNode(pos, EPathfindingLayer::AIR, tinfo); + if(options.useWaterWalking) + initializeNode(pos, EPathfindingLayer::WATER, tinfo); break; default: -// initializeNode(EPathfindingLayer::LAND, tinfo, pos); -// if(options.useFlying) -// initializeNode(EPathfindingLayer::AIR, tinfo, pos); + initializeNode(pos, EPathfindingLayer::LAND, tinfo); + if(options.useFlying) + initializeNode(pos, EPathfindingLayer::AIR, tinfo); break; } } @@ -457,8 +453,7 @@ bool CPathfinder::canVisitObject() const bool CPathfinder::isLayerTransitionPossible() { - Obj destTopVisObjID = dt->topVisitableId(); - if((cp->layer == EPathfindingLayer::AIR || EPathfindingLayer::WATER) + if((cp->layer == EPathfindingLayer::AIR || cp->layer == EPathfindingLayer::WATER) && dp->layer != EPathfindingLayer::LAND) { return false; @@ -479,6 +474,7 @@ bool CPathfinder::isLayerTransitionPossible() } else if(cp->layer == EPathfindingLayer::LAND && dp->layer == EPathfindingLayer::SAIL) { + Obj destTopVisObjID = dt->topVisitableId(); if(dp->accessible == CGPathNode::ACCESSIBLE || destTopVisObjID < 0) //cannot enter empty water tile from land -> it has to be visitable return false; if(destTopVisObjID != Obj::HERO && destTopVisObjID != Obj::BOAT) //only boat or hero can be accessed from land @@ -580,7 +576,6 @@ bool CPathsInfo::getPath(CGPath &out, const int3 &dst, const EPathfindingLayer & if(!curnode->theNodeBefore) return false; - while(curnode) { CGPathNode cpn = *curnode; From 595deda2707d9093a1dd45e167570ee66909127d Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Wed, 4 Nov 2015 11:47:43 +0300 Subject: [PATCH 09/83] CPathfinder: rename functions to better represent what they doing --- lib/CPathfinder.cpp | 8 ++++---- lib/CPathfinder.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index ac60c842f..95cd4c615 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -110,7 +110,7 @@ void CPathfinder::calculatePaths() if(cp->layer != i && !isLayerTransitionPossible()) continue; - if(!isMovementPossible()) + if(!isMovementToDestPossible()) continue; int cost = gs->getMovementCost(hero, cp->coord, dp->coord, movement); @@ -138,7 +138,7 @@ void CPathfinder::calculatePaths() dp->turns = turnAtNextTile; dp->theNodeBefore = cp; - if(checkDestinationTile()) + if(isMovementAfterDestPossible()) mq.push_back(dp); } } @@ -225,7 +225,7 @@ void CPathfinder::addTeleportExits(bool noTeleportExcludes) } } -bool CPathfinder::isMovementPossible() +bool CPathfinder::isMovementToDestPossible() { switch (dp->layer) { @@ -263,7 +263,7 @@ bool CPathfinder::isMovementPossible() return true; } -bool CPathfinder::checkDestinationTile() +bool CPathfinder::isMovementAfterDestPossible() { if(dp->accessible == CGPathNode::ACCESSIBLE) return true; diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 0626ffe24..4a7b9f790 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -105,8 +105,8 @@ private: void addNeighbours(const int3 &coord); void addTeleportExits(bool noTeleportExcludes = false); - bool isMovementPossible(); //checks if current move will be between sea<->land. If so, checks it legality (returns false if movement is not possible) and sets useEmbarkCost - bool checkDestinationTile(); + bool isMovementToDestPossible(); //checks if current move will be between sea<->land. If so, checks it legality (returns false if movement is not possible) and sets useEmbarkCost + bool isMovementAfterDestPossible(); int3 getSourceGuardPosition(); bool isSourceGuarded(); From 934c68273392be2cce2943b15a0c80e78c934b1e Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Wed, 4 Nov 2015 11:53:52 +0300 Subject: [PATCH 10/83] CPathfinder: always add air and water layer nodes to queue It's should be possible to go into air layer from visitable object (but opposite isn't allowed). And when walking on water player can't really interact with any object at all so future movement always possible. --- lib/CPathfinder.cpp | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 95cd4c615..0647981d1 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -265,16 +265,28 @@ bool CPathfinder::isMovementToDestPossible() bool CPathfinder::isMovementAfterDestPossible() { - if(dp->accessible == CGPathNode::ACCESSIBLE) - return true; - if(dp->coord == CGHeroInstance::convertPosition(hero->pos, false)) - return true; // This one is tricky, we can ignore fact that tile is not ACCESSIBLE in case if it's our hero block it. Though this need investigation - if(dp->accessible == CGPathNode::VISITABLE && CGTeleport::isTeleport(dt->topVisitableObj())) - return true; // For now we'll always allow transit for teleporters - if(useEmbarkCost && options.useEmbarkAndDisembark) - return true; - if(isDestinationGuarded() && !isSourceGuarded()) - return true; // Can step into a hostile tile once + switch (dp->layer) + { + case EPathfindingLayer::LAND: + case EPathfindingLayer::SAIL: + if(dp->accessible == CGPathNode::ACCESSIBLE) + return true; + if(dp->coord == CGHeroInstance::convertPosition(hero->pos, false)) + return true; // This one is tricky, we can ignore fact that tile is not ACCESSIBLE in case if it's our hero block it. Though this need investigation + if(dp->accessible == CGPathNode::VISITABLE && CGTeleport::isTeleport(dt->topVisitableObj())) + return true; // For now we'll always allow transit for teleporters + if(useEmbarkCost && options.useEmbarkAndDisembark) + return true; + if(isDestinationGuarded() && !isSourceGuarded()) + return true; // Can step into a hostile tile once + break; + + case EPathfindingLayer::AIR: + case EPathfindingLayer::WATER: + return true; + + break; + } return false; } From 330c1666fccd666ba507fbed9d0bf9941c3af954 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Wed, 4 Nov 2015 12:29:51 +0300 Subject: [PATCH 11/83] CPathfinder: move isLayerTransitionPossible and remove outdated comment --- lib/CPathfinder.cpp | 68 ++++++++++++++++++++++----------------------- lib/CPathfinder.h | 4 +-- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 0647981d1..9f4911d91 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -225,6 +225,40 @@ void CPathfinder::addTeleportExits(bool noTeleportExcludes) } } +bool CPathfinder::isLayerTransitionPossible() +{ + if((cp->layer == EPathfindingLayer::AIR || cp->layer == EPathfindingLayer::WATER) + && dp->layer != EPathfindingLayer::LAND) + { + return false; + } + else if(cp->layer == EPathfindingLayer::SAIL && dp->layer != EPathfindingLayer::LAND) + return false; + else if(cp->layer == EPathfindingLayer::SAIL && dp->layer == EPathfindingLayer::LAND) + { + 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)) + || dt->visitable) //TODO: passableness problem -> town says it's passable (thus accessible) but we obviously can't disembark onto town gate + return false; + + useEmbarkCost = 2; + } + else if(cp->layer == EPathfindingLayer::LAND && dp->layer == EPathfindingLayer::SAIL) + { + Obj destTopVisObjID = dt->topVisitableId(); + if(dp->accessible == CGPathNode::ACCESSIBLE || destTopVisObjID < 0) //cannot enter empty water tile from land -> it has to be visitable + return false; + if(destTopVisObjID != Obj::HERO && destTopVisObjID != Obj::BOAT) //only boat or hero can be accessed from land + return false; + if(destTopVisObjID == Obj::BOAT) + useEmbarkCost = 1; + } + return true; +} + bool CPathfinder::isMovementToDestPossible() { switch (dp->layer) @@ -463,40 +497,6 @@ bool CPathfinder::canVisitObject() const return cp->layer == EPathfindingLayer::LAND || cp->layer == EPathfindingLayer::SAIL; } -bool CPathfinder::isLayerTransitionPossible() -{ - if((cp->layer == EPathfindingLayer::AIR || cp->layer == EPathfindingLayer::WATER) - && dp->layer != EPathfindingLayer::LAND) - { - return false; - } - else if(cp->layer == EPathfindingLayer::SAIL && dp->layer != EPathfindingLayer::LAND) - return false; - else if(cp->layer == EPathfindingLayer::SAIL && dp->layer == EPathfindingLayer::LAND) - { - 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)) - || dt->visitable) //TODO: passableness problem -> town says it's passable (thus accessible) but we obviously can't disembark onto town gate - return false; - - useEmbarkCost = 2; - } - else if(cp->layer == EPathfindingLayer::LAND && dp->layer == EPathfindingLayer::SAIL) - { - Obj destTopVisObjID = dt->topVisitableId(); - if(dp->accessible == CGPathNode::ACCESSIBLE || destTopVisObjID < 0) //cannot enter empty water tile from land -> it has to be visitable - return false; - if(destTopVisObjID != Obj::HERO && destTopVisObjID != Obj::BOAT) //only boat or hero can be accessed from land - return false; - if(destTopVisObjID == Obj::BOAT) - useEmbarkCost = 1; - } - return true; -} - CGPathNode::CGPathNode() :coord(-1,-1,-1) { diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 4a7b9f790..660e472cc 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -105,7 +105,8 @@ private: void addNeighbours(const int3 &coord); void addTeleportExits(bool noTeleportExcludes = false); - bool isMovementToDestPossible(); //checks if current move will be between sea<->land. If so, checks it legality (returns false if movement is not possible) and sets useEmbarkCost + bool isLayerTransitionPossible(); + bool isMovementToDestPossible(); bool isMovementAfterDestPossible(); int3 getSourceGuardPosition(); @@ -125,5 +126,4 @@ private: bool canVisitObject() const; - bool isLayerTransitionPossible(); }; From 1bc335323d8a4618eea31341bdf61474e7127816 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Wed, 4 Nov 2015 15:05:22 +0300 Subject: [PATCH 12/83] CPathfinder: add lightweightFlyingMode option suggested by @alexvins --- lib/CPathfinder.cpp | 7 +++++++ lib/CPathfinder.h | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 9f4911d91..da35e6e0b 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -27,6 +27,8 @@ CPathfinder::PathfinderOptions::PathfinderOptions() useTeleportOneWay = true; useTeleportOneWayRandom = false; useTeleportWhirlpool = false; + + lightweightFlyingMode = false; } CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero) : CGameInfoCallback(_gs, boost::optional()), out(_out), hero(_hero), FoW(getPlayerTeam(hero->tempOwner)->fogOfWarMap) @@ -232,6 +234,11 @@ bool CPathfinder::isLayerTransitionPossible() { return false; } + else if(cp->layer == EPathfindingLayer::LAND && dp->layer == EPathfindingLayer::AIR) + { + if(options.lightweightFlyingMode && cp->coord != hero->getPosition(false)) + return false; + } else if(cp->layer == EPathfindingLayer::SAIL && dp->layer != EPathfindingLayer::LAND) return false; else if(cp->layer == EPathfindingLayer::SAIL && dp->layer == EPathfindingLayer::LAND) diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 660e472cc..e6b1f6791 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -85,6 +85,11 @@ private: bool useTeleportOneWayRandom; // One-way monoliths with more than one known exit bool useTeleportWhirlpool; // Force enabled if hero protected or unaffected (have one stack of one creature) + /// If true transition into air layer only possible from initial node. + /// This is drastically decrease path calculation complexity (and time). + /// Downside is less MP effective paths calculation. + bool lightweightFlyingMode; + PathfinderOptions(); } options; From f4dea88e3b76db093dbdb5fce91e7f614332cd27 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Wed, 4 Nov 2015 15:38:15 +0300 Subject: [PATCH 13/83] CPathfinder: get rid of hero object usage when it's not needed --- lib/CPathfinder.cpp | 14 +++++++++----- lib/CPathfinder.h | 1 + 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index da35e6e0b..f1ab61629 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -172,7 +172,7 @@ void CPathfinder::addNeighbours(const int3 &coord) std::vector tiles; gs->getNeighbours(*ct, coord, tiles, boost::logic::indeterminate, !cp->land); - sTileObj = ct->topVisitableObj(coord == CGHeroInstance::convertPosition(hero->pos, false)); + sTileObj = ct->topVisitableObj(coord == out.hpos); if(canVisitObject()) { if(sTileObj) @@ -236,7 +236,7 @@ bool CPathfinder::isLayerTransitionPossible() } else if(cp->layer == EPathfindingLayer::LAND && dp->layer == EPathfindingLayer::AIR) { - if(options.lightweightFlyingMode && cp->coord != hero->getPosition(false)) + if(options.lightweightFlyingMode && !isSourceInitialPosition()) return false; } else if(cp->layer == EPathfindingLayer::SAIL && dp->layer != EPathfindingLayer::LAND) @@ -312,7 +312,7 @@ bool CPathfinder::isMovementAfterDestPossible() case EPathfindingLayer::SAIL: if(dp->accessible == CGPathNode::ACCESSIBLE) return true; - if(dp->coord == CGHeroInstance::convertPosition(hero->pos, false)) + if(dp->coord == out.hpos) return true; // This one is tricky, we can ignore fact that tile is not ACCESSIBLE in case if it's our hero block it. Though this need investigation if(dp->accessible == CGPathNode::VISITABLE && CGTeleport::isTeleport(dt->topVisitableObj())) return true; // For now we'll always allow transit for teleporters @@ -332,6 +332,11 @@ bool CPathfinder::isMovementAfterDestPossible() return false; } +bool CPathfinder::isSourceInitialPosition() +{ + return cp->coord == out.hpos; +} + int3 CPathfinder::getSourceGuardPosition() { return gs->map->guardingCreaturePositions[cp->coord.x][cp->coord.y][cp->coord.z]; @@ -341,8 +346,7 @@ bool CPathfinder::isSourceGuarded() { //map can start with hero on guarded tile or teleport there using dimension door //so threat tile hero standing on like it's not guarded because it's should be possible to move out of here - if(getSourceGuardPosition() != int3(-1, -1, -1) - && cp->coord != hero->getPosition(false)) + if(getSourceGuardPosition() != int3(-1, -1, -1) && !isSourceInitialPosition()) { //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 diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index e6b1f6791..7d591cdbb 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -114,6 +114,7 @@ private: bool isMovementToDestPossible(); bool isMovementAfterDestPossible(); + bool isSourceInitialPosition(); int3 getSourceGuardPosition(); bool isSourceGuarded(); bool isDestinationGuarded(); From ac12a0735e22f421cb4b5dfb642d9c8ad137810a Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Thu, 5 Nov 2015 10:02:13 +0300 Subject: [PATCH 14/83] Plumbing on client and server to make flying actually work --- AI/VCAI/VCAI.cpp | 2 ++ client/CPlayerInterface.cpp | 2 ++ server/CGameHandler.cpp | 24 ++++++++++++++++++------ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 4ef6f169d..73ac5dab2 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -1915,6 +1915,8 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) { // Hero should be able to go through object if it's allow transit doMovement(endpos, true); } + else if(path.nodes[i-1].layer == EPathfindingLayer::AIR) + doMovement(endpos, true); else doMovement(endpos, false); diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index a6109b0d6..9c55ce471 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -2689,6 +2689,8 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) { // Hero should be able to go through object if it's allow transit doMovement(endpos, true); } + else if(path.nodes[i-1].layer == EPathfindingLayer::AIR) + doMovement(endpos, true); else doMovement(endpos, false); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 4634ae5c7..b1060fa62 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1782,9 +1782,9 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo //it's a rock or blocked and not visitable tile //OR hero is on land and dest is water and (there is not present only one object - boat) - if(((t.terType == ETerrainType::ROCK || (t.blocked && !t.visitable && !h->hasBonusOfType(Bonus::FLYING_MOVEMENT) )) + if(((t.terType == ETerrainType::ROCK || (t.blocked && !t.visitable && !h->canFly() )) && complain("Cannot move hero, destination tile is blocked!")) - || ((!h->boat && !h->canWalkOnSea() && t.terType == ETerrainType::WATER && (t.visitableObjects.size() < 1 || (t.visitableObjects.back()->ID != Obj::BOAT && t.visitableObjects.back()->ID != Obj::HERO))) //hero is not on boat/water walking and dst water tile doesn't contain boat/hero (objs visitable from land) -> we test back cause boat may be on top of another object (#276) + || ((!h->boat && !h->canWalkOnSea() && !h->canFly() && t.terType == ETerrainType::WATER && (t.visitableObjects.size() < 1 || (t.visitableObjects.back()->ID != Obj::BOAT && t.visitableObjects.back()->ID != Obj::HERO))) //hero is not on boat/water walking and dst water tile doesn't contain boat/hero (objs visitable from land) -> we test back cause boat may be on top of another object (#276) && complain("Cannot move hero, destination tile is on water!")) || ((h->boat && t.terType != ETerrainType::WATER && t.blocked) && complain("Cannot disembark hero, tile is blocked!")) @@ -1843,8 +1843,7 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo } else if(visitDest == VISIT_DEST) { - if(!transit || !CGTeleport::isTeleport(t.topVisitableObj())) - visitObjectOnTile(t, h); + visitObjectOnTile(t, h); } queries.popIfTop(moveQuery); @@ -1905,10 +1904,23 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo ? h->movement - cost : 0; - if(blockingVisit()) + EGuardLook lookForGuards = CHECK_FOR_GUARDS; + EVisitDest visitDest = VISIT_DEST; + if(transit) + { + if(CGTeleport::isTeleport(t.topVisitableObj())) + visitDest = DONT_VISIT_DEST; + + if(h->canFly()) + { + lookForGuards = IGNORE_GUARDS; + visitDest = DONT_VISIT_DEST; + } + } + else if(blockingVisit()) return true; - doMove(TryMoveHero::SUCCESS, CHECK_FOR_GUARDS, VISIT_DEST, LEAVING_TILE); + doMove(TryMoveHero::SUCCESS, lookForGuards, visitDest, LEAVING_TILE); return true; } } From 3de94a8b9972d5d74a7a6d81cef18de144671b58 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Thu, 5 Nov 2015 10:22:38 +0300 Subject: [PATCH 15/83] CPathfinder: don't allow future movement after guarded tile There is two exceptions: - Hero start movement from guarded tile. - Hero that embarking into boat that standing on guarded tile. In other cases future movement is impossible. --- lib/CPathfinder.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index f1ab61629..992c94991 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -318,8 +318,6 @@ bool CPathfinder::isMovementAfterDestPossible() return true; // For now we'll always allow transit for teleporters if(useEmbarkCost && options.useEmbarkAndDisembark) return true; - if(isDestinationGuarded() && !isSourceGuarded()) - return true; // Can step into a hostile tile once break; case EPathfindingLayer::AIR: From c188ff0a9291308e398b62d3115aba728f5b537d Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Thu, 5 Nov 2015 10:37:22 +0300 Subject: [PATCH 16/83] doMoveHero: dont stop movement on guarded tiles when transit used --- client/CPlayerInterface.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 9c55ce471..7e3b5a243 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -2683,20 +2683,21 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) int3 endpos(nextCoord.x, nextCoord.y, h->pos.z); logGlobal->traceStream() << "Requesting hero movement to " << endpos; + bool useTransit = false; if((i-2 >= 0) // Check there is node after next one; otherwise transit is pointless && (CGTeleport::isConnected(nextObject, getObj(path.nodes[i-2].coord, false)) || CGTeleport::isTeleport(nextObject))) { // Hero should be able to go through object if it's allow transit - doMovement(endpos, true); + useTransit = true; } else if(path.nodes[i-1].layer == EPathfindingLayer::AIR) - doMovement(endpos, true); - else - doMovement(endpos, false); + useTransit = true; + + doMovement(endpos, useTransit); logGlobal->traceStream() << "Resuming " << __FUNCTION__; bool guarded = cb->isInTheMap(cb->getGuardingCreaturePosition(endpos - int3(1, 0, 0))); - if(guarded || showingDialog->get() == true) // Abort movement if a guard was fought or there is a dialog to display (Mantis #1136) + if((!useTransit && guarded) || showingDialog->get() == true) // Abort movement if a guard was fought or there is a dialog to display (Mantis #1136) break; } From 62dc070c0aabb317530a7df1e554a1ed3c6b1c90 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Thu, 5 Nov 2015 10:50:47 +0300 Subject: [PATCH 17/83] moveHero: add transit validation and avoid embarking on transit Hero shouldn't embark into boat when attempt to transit over air layer. --- server/CGameHandler.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index b1060fa62..c9c8ffb32 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1794,6 +1794,8 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo && complain("Can not move garrisoned hero!")) || ((h->movement < cost && dst != h->pos && !teleporting) && complain("Hero doesn't have any movement points left!")) + || ((transit && !h->canFly() && !CGTeleport::isTeleport(t.topVisitableObj())) + && complain("Hero cannot transit over this tile!")) /*|| (states.checkFlag(h->tempOwner, &PlayerStatus::engagedIntoBattle) && complain("Cannot move hero during the battle"))*/) { @@ -1866,7 +1868,7 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo }; - if(embarking) + if(!transit && embarking) { tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, false); return doMove(TryMoveHero::EMBARK, IGNORE_GUARDS, DONT_VISIT_DEST, LEAVING_TILE); From a1290f548babb9fa92e862c0b64b6423666fafcf Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Thu, 5 Nov 2015 11:25:41 +0300 Subject: [PATCH 18/83] CPathfinder: only allow water walking over accessible tiles --- lib/CPathfinder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 992c94991..b4cd10d1f 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -292,7 +292,7 @@ bool CPathfinder::isMovementToDestPossible() break; case EPathfindingLayer::WATER: - if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED) + if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible != CGPathNode::ACCESSIBLE) return false; if(isDestinationGuarded()) return false; From 5f3e9deda768e140ff5d64c8410c78f1532cc634 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Thu, 5 Nov 2015 11:34:01 +0300 Subject: [PATCH 19/83] CPathfinder: deny transit over whirlpools when hero not protected --- lib/CPathfinder.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index b4cd10d1f..2aa6ba39e 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -315,7 +315,13 @@ bool CPathfinder::isMovementAfterDestPossible() if(dp->coord == out.hpos) return true; // This one is tricky, we can ignore fact that tile is not ACCESSIBLE in case if it's our hero block it. Though this need investigation if(dp->accessible == CGPathNode::VISITABLE && CGTeleport::isTeleport(dt->topVisitableObj())) - return true; // For now we'll always allow transit for teleporters + { + /// For now we'll always allow transit over teleporters + /// Transit over whirlpools only allowed when hero protected + auto whirlpool = dynamic_cast(dt->topVisitableObj()); + if(!whirlpool || options.useTeleportWhirlpool) + return true; + } if(useEmbarkCost && options.useEmbarkAndDisembark) return true; break; From 9fe442537b39982eb8a01d9bf8196c8254e54369 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Thu, 5 Nov 2015 12:46:44 +0300 Subject: [PATCH 20/83] Pass on EPathfindingLayer and small fixes for code around it --- lib/GameConstants.cpp | 20 +++++++------------- lib/GameConstants.h | 6 ++---- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/lib/GameConstants.cpp b/lib/GameConstants.cpp index d21f3f40c..2b9556091 100644 --- a/lib/GameConstants.cpp +++ b/lib/GameConstants.cpp @@ -132,7 +132,7 @@ std::ostream & operator<<(std::ostream & os, const Battle::ActionType actionType else return os << it->second; } -std::ostream & operator<<(std::ostream & os, const ETerrainType actionType) +std::ostream & operator<<(std::ostream & os, const ETerrainType terrainType) { static const std::map terrainTypeToString = { @@ -149,9 +149,10 @@ std::ostream & operator<<(std::ostream & os, const ETerrainType actionType) DEFINE_ELEMENT(LAVA), DEFINE_ELEMENT(WATER), DEFINE_ELEMENT(ROCK) + #undef DEFINE_ELEMENT }; - auto it = terrainTypeToString.find(actionType.num); + auto it = terrainTypeToString.find(terrainType.num); if (it == terrainTypeToString.end()) return os << ""; else return os << it->second; } @@ -163,9 +164,9 @@ std::string ETerrainType::toString() const return ss.str(); } -std::ostream & operator<<(std::ostream & os, const EPathfindingLayer actionType) +std::ostream & operator<<(std::ostream & os, const EPathfindingLayer pathfindingLayer) { - static const std::map pathfinderLayerToString = + static const std::map pathfinderLayerToString { #define DEFINE_ELEMENT(element) {EPathfindingLayer::element, #element} DEFINE_ELEMENT(WRONG), @@ -175,17 +176,10 @@ std::ostream & operator<<(std::ostream & os, const EPathfindingLayer actionType) DEFINE_ELEMENT(WATER), DEFINE_ELEMENT(AIR), DEFINE_ELEMENT(NUM_LAYERS) - + #undef DEFINE_ELEMENT }; - auto it = pathfinderLayerToString.find(actionType.num); + auto it = pathfinderLayerToString.find(pathfindingLayer.num); if (it == pathfinderLayerToString.end()) return os << ""; else return os << it->second; } - -std::string EPathfindingLayer::toString() const -{ - std::stringstream ss; - ss << *this; - return ss.str(); -} diff --git a/lib/GameConstants.h b/lib/GameConstants.h index e46c482d1..4cb33d0db 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -741,7 +741,7 @@ public: std::string toString() const; }; -DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const ETerrainType actionType); +DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const ETerrainType terrainType); ID_LIKE_OPERATORS_DECLS(ETerrainType, ETerrainType::EETerrainType) @@ -759,11 +759,9 @@ public: ID_LIKE_CLASS_COMMON(EPathfindingLayer, EEPathfindingLayer) EEPathfindingLayer num; - - std::string toString() const; }; -DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EPathfindingLayer actionType); +DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EPathfindingLayer pathfindingLayer); ID_LIKE_OPERATORS_DECLS(EPathfindingLayer, EPathfindingLayer::EEPathfindingLayer) From 148355908d968e4e87375a3b727fc8c3a03d8451 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Thu, 5 Nov 2015 15:04:56 +0300 Subject: [PATCH 21/83] CPathfinder: get rid of FoW variable and bunch of small fixes --- lib/CPathfinder.cpp | 34 ++++++++++++++++------------------ lib/CPathfinder.h | 1 - 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 2aa6ba39e..70be1c2fc 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -31,7 +31,8 @@ CPathfinder::PathfinderOptions::PathfinderOptions() lightweightFlyingMode = false; } -CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero) : CGameInfoCallback(_gs, boost::optional()), out(_out), hero(_hero), FoW(getPlayerTeam(hero->tempOwner)->fogOfWarMap) +CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero) + : CGameInfoCallback(_gs, boost::optional()), out(_out), hero(_hero) { assert(hero); assert(hero == getHero(hero->id)); @@ -268,7 +269,7 @@ bool CPathfinder::isLayerTransitionPossible() bool CPathfinder::isMovementToDestPossible() { - switch (dp->layer) + switch(dp->layer) { case EPathfindingLayer::LAND: if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED) @@ -306,7 +307,7 @@ bool CPathfinder::isMovementToDestPossible() bool CPathfinder::isMovementAfterDestPossible() { - switch (dp->layer) + switch(dp->layer) { case EPathfindingLayer::LAND: case EPathfindingLayer::SAIL: @@ -382,7 +383,7 @@ bool CPathfinder::isDestinationGuardian() void CPathfinder::initializeGraph() { - auto initializeNode = [&](int3 pos, EPathfindingLayer layer, const TerrainTile *tinfo) + auto updateNode = [&](int3 pos, EPathfindingLayer layer, const TerrainTile *tinfo) { auto node = out.getNode(pos, layer); node->accessible = evaluateAccessibility(pos, tinfo); @@ -402,23 +403,21 @@ void CPathfinder::initializeGraph() for(pos.z=0; pos.z < out.sizes.z; ++pos.z) { const TerrainTile *tinfo = &gs->map->getTile(pos); - switch (tinfo->terType) + switch(tinfo->terType) { - case ETerrainType::WRONG: - case ETerrainType::BORDER: case ETerrainType::ROCK: break; case ETerrainType::WATER: - initializeNode(pos, EPathfindingLayer::SAIL, tinfo); + updateNode(pos, EPathfindingLayer::SAIL, tinfo); if(options.useFlying) - initializeNode(pos, EPathfindingLayer::AIR, tinfo); + updateNode(pos, EPathfindingLayer::AIR, tinfo); if(options.useWaterWalking) - initializeNode(pos, EPathfindingLayer::WATER, tinfo); + updateNode(pos, EPathfindingLayer::WATER, tinfo); break; default: - initializeNode(pos, EPathfindingLayer::LAND, tinfo); + updateNode(pos, EPathfindingLayer::LAND, tinfo); if(options.useFlying) - initializeNode(pos, EPathfindingLayer::AIR, tinfo); + updateNode(pos, EPathfindingLayer::AIR, tinfo); break; } } @@ -430,8 +429,7 @@ CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const int3 &pos, c { CGPathNode::EAccessibility ret = (tinfo->blocked ? CGPathNode::BLOCKED : CGPathNode::ACCESSIBLE); - - if(tinfo->terType == ETerrainType::ROCK || !FoW[pos.x][pos.y][pos.z]) + if(tinfo->terType == ETerrainType::ROCK || !isVisible(pos, hero->tempOwner)) return CGPathNode::BLOCKED; if(tinfo->visitable) @@ -513,7 +511,7 @@ bool CPathfinder::canVisitObject() const } CGPathNode::CGPathNode() -:coord(-1,-1,-1) + : coord(-1,-1,-1) { accessible = NOT_SET; land = 0; @@ -538,7 +536,7 @@ int3 CGPath::endPos() const return nodes[0].coord; } -void CGPath::convert( ui8 mode ) +void CGPath::convert(ui8 mode) { if(mode==0) { @@ -549,8 +547,8 @@ void CGPath::convert( ui8 mode ) } } -CPathsInfo::CPathsInfo( const int3 &Sizes ) -:sizes(Sizes) +CPathsInfo::CPathsInfo(const int3 &Sizes) + : sizes(Sizes) { hero = nullptr; nodes = new CGPathNode***[sizes.x]; diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 7d591cdbb..d6bf2483c 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -95,7 +95,6 @@ private: CPathsInfo &out; const CGHeroInstance *hero; - const std::vector > > &FoW; std::list mq; //BFS queue -> nodes to be checked From 3f2cdf31379ca12be7630e4b0c0477574fe16972 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sat, 7 Nov 2015 21:11:07 +0300 Subject: [PATCH 22/83] CPathfinder: implement priority queue and node locking --- lib/CPathfinder.cpp | 21 +++++++++++++++------ lib/CPathfinder.h | 17 ++++++++++++++++- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 70be1c2fc..74dd2dfa6 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -84,12 +84,13 @@ void CPathfinder::calculatePaths() CGPathNode *initialNode = out.getNode(out.hpos, hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND); initialNode->turns = 0; initialNode->moveRemains = hero->movement; - mq.push_back(initialNode); + pq.push(initialNode); - while(!mq.empty()) + while(!pq.empty()) { - cp = mq.front(); - mq.pop_front(); + cp = pq.top(); + pq.pop(); + cp->locked = true; int movement = cp->moveRemains, turn = cp->turns; if(!movement) @@ -110,6 +111,9 @@ void CPathfinder::calculatePaths() if(dp->accessible == CGPathNode::NOT_SET) continue; + if(dp->locked) + continue; + if(cp->layer != i && !isLayerTransitionPossible()) continue; @@ -142,7 +146,7 @@ void CPathfinder::calculatePaths() dp->theNodeBefore = cp; if(isMovementAfterDestPossible()) - mq.push_back(dp); + pq.push(dp); } } } //neighbours loop @@ -154,12 +158,15 @@ void CPathfinder::calculatePaths() for(auto & neighbour : neighbours) { dp = out.getNode(neighbour, cp->layer); + if(dp->locked) + continue; + if(isBetterWay(movement, turn)) { dp->moveRemains = movement; dp->turns = turn; dp->theNodeBefore = cp; - mq.push_back(dp); + pq.push(dp); } } } @@ -386,6 +393,7 @@ void CPathfinder::initializeGraph() auto updateNode = [&](int3 pos, EPathfindingLayer layer, const TerrainTile *tinfo) { auto node = out.getNode(pos, layer); + node->locked = false; node->accessible = evaluateAccessibility(pos, tinfo); node->turns = 0xff; node->moveRemains = 0; @@ -513,6 +521,7 @@ bool CPathfinder::canVisitObject() const CGPathNode::CGPathNode() : coord(-1,-1,-1) { + locked = false; accessible = NOT_SET; land = 0; moveRemains = 0; diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index d6bf2483c..96e6c72ac 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -5,6 +5,8 @@ #include "IGameCallback.h" #include "int3.h" +#include + /* * CPathfinder.h, part of VCMI engine * @@ -30,6 +32,7 @@ struct DLL_LINKAGE CGPathNode BLOCKED //tile can't be entered nor visited }; + bool locked; EAccessibility accessible; ui8 land; ui8 turns; //how many turns we have to wait before reachng the tile - 0 means current turn @@ -96,7 +99,19 @@ private: CPathsInfo &out; const CGHeroInstance *hero; - std::list mq; //BFS queue -> nodes to be checked + struct NodeComparer + { + bool operator()(const CGPathNode * lhs, const CGPathNode * rhs) const + { + if(rhs->turns > lhs->turns) + return false; + else if(rhs->turns == lhs->turns && rhs->moveRemains < lhs->moveRemains) + return false; + + return true; + } + }; + boost::heap::priority_queue > pq; std::vector neighbours; From d8a612f5d64a8727c63959cbca9fe4a71f7a3f6c Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sat, 7 Nov 2015 22:00:31 +0300 Subject: [PATCH 23/83] CPathsInfo: use boost::multi_array for storing graph of nodes --- lib/CPathfinder.cpp | 39 +++++++++------------------------------ lib/CPathfinder.h | 4 ++-- 2 files changed, 11 insertions(+), 32 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 74dd2dfa6..6ace8f104 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -518,8 +518,8 @@ bool CPathfinder::canVisitObject() const return cp->layer == EPathfindingLayer::LAND || cp->layer == EPathfindingLayer::SAIL; } -CGPathNode::CGPathNode() - : coord(-1,-1,-1) +CGPathNode::CGPathNode(int3 Coord, EPathfindingLayer Layer) + : coord(Coord), layer(Layer) { locked = false; accessible = NOT_SET; @@ -527,7 +527,6 @@ CGPathNode::CGPathNode() moveRemains = 0; turns = 255; theNodeBefore = nullptr; - layer = EPathfindingLayer::WRONG; } bool CGPathNode::reachable() const @@ -560,36 +559,16 @@ CPathsInfo::CPathsInfo(const int3 &Sizes) : sizes(Sizes) { hero = nullptr; - nodes = new CGPathNode***[sizes.x]; + nodes.resize(boost::extents[sizes.x][sizes.y][sizes.z][EPathfindingLayer::NUM_LAYERS]); for(int i = 0; i < sizes.x; i++) - { - nodes[i] = new CGPathNode**[sizes.y]; for(int j = 0; j < sizes.y; j++) - { - nodes[i][j] = new CGPathNode*[sizes.z]; - for (int z = 0; z < sizes.z; z++) - { - nodes[i][j][z] = new CGPathNode[EPathfindingLayer::NUM_LAYERS]; - } - } - } + for(int z = 0; z < sizes.z; z++) + for(int l = 0; l < EPathfindingLayer::NUM_LAYERS; l++) + nodes[i][j][z][l] = new CGPathNode(int3(i, j, z), static_cast(l)); } CPathsInfo::~CPathsInfo() { - for(int i = 0; i < sizes.x; i++) - { - for(int j = 0; j < sizes.y; j++) - { - for (int z = 0; z < sizes.z; z++) - { - delete [] nodes[i][j][z]; - } - delete [] nodes[i][j]; - } - delete [] nodes[i]; - } - delete [] nodes; } const CGPathNode * CPathsInfo::getPathInfo(const int3 &tile, const EPathfindingLayer &layer) const @@ -633,11 +612,11 @@ int CPathsInfo::getDistance(const int3 &tile, const EPathfindingLayer &layer) co CGPathNode *CPathsInfo::getNode(const int3 &coord, const EPathfindingLayer &layer) const { if(layer != EPathfindingLayer::AUTO) - return &nodes[coord.x][coord.y][coord.z][layer]; + return nodes[coord.x][coord.y][coord.z][layer]; - auto landNode = &nodes[coord.x][coord.y][coord.z][EPathfindingLayer::LAND]; + auto landNode = nodes[coord.x][coord.y][coord.z][EPathfindingLayer::LAND]; if(landNode->theNodeBefore) return landNode; else - return &nodes[coord.x][coord.y][coord.z][EPathfindingLayer::SAIL]; + return nodes[coord.x][coord.y][coord.z][EPathfindingLayer::SAIL]; } diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 96e6c72ac..b4c7aea17 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -41,7 +41,7 @@ struct DLL_LINKAGE CGPathNode int3 coord; //coordinates EPathfindingLayer layer; - CGPathNode(); + CGPathNode(int3 Coord, EPathfindingLayer Layer); bool reachable() const; }; @@ -61,7 +61,7 @@ struct DLL_LINKAGE CPathsInfo const CGHeroInstance *hero; int3 hpos; int3 sizes; - CGPathNode ****nodes; //[w][h][level][layer] + boost::multi_array nodes; //[w][h][level][layer] CPathsInfo(const int3 &Sizes); ~CPathsInfo(); From bd8eec7fb8c1c29a243fa994478172f3d05788ff Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sat, 7 Nov 2015 22:16:45 +0300 Subject: [PATCH 24/83] CGPathNode: move resetting code into separate function --- lib/CPathfinder.cpp | 12 ++++++------ lib/CPathfinder.h | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 6ace8f104..918a12d15 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -393,14 +393,9 @@ void CPathfinder::initializeGraph() auto updateNode = [&](int3 pos, EPathfindingLayer layer, const TerrainTile *tinfo) { auto node = out.getNode(pos, layer); - node->locked = false; + node->reset(); node->accessible = evaluateAccessibility(pos, tinfo); - node->turns = 0xff; - node->moveRemains = 0; - node->coord = pos; node->land = tinfo->terType != ETerrainType::WATER; - node->theNodeBefore = nullptr; - node->layer = layer; }; int3 pos; @@ -520,6 +515,11 @@ bool CPathfinder::canVisitObject() const CGPathNode::CGPathNode(int3 Coord, EPathfindingLayer Layer) : coord(Coord), layer(Layer) +{ + reset(); +} + +void CGPathNode::reset() { locked = false; accessible = NOT_SET; diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index b4c7aea17..2d3616f42 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -42,6 +42,7 @@ struct DLL_LINKAGE CGPathNode EPathfindingLayer layer; CGPathNode(int3 Coord, EPathfindingLayer Layer); + void reset(); bool reachable() const; }; From 82048cbf2dc8c6305f59bbf65d5d06572076e049 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sun, 8 Nov 2015 00:26:41 +0300 Subject: [PATCH 25/83] Pathfinder: implement new feature - node action No action going to simplify isMovementAfterDestPossible and should be as well useful for cost calculations. It's can be also used by client interface to show appropriate cursors and path. --- lib/CPathfinder.cpp | 57 +++++++++++++++++++++++++++++++++++---------- lib/CPathfinder.h | 14 ++++++++++- 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 918a12d15..b54d915c8 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -106,7 +106,6 @@ void CPathfinder::calculatePaths() dt = &gs->map->getTile(neighbour); for(EPathfindingLayer i = EPathfindingLayer::LAND; i <= EPathfindingLayer::AIR; i.advance(1)) { - useEmbarkCost = 0; //0 - usual movement; 1 - embark; 2 - disembark dp = out.getNode(neighbour, i); if(dp->accessible == CGPathNode::NOT_SET) continue; @@ -117,14 +116,15 @@ void CPathfinder::calculatePaths() if(cp->layer != i && !isLayerTransitionPossible()) continue; + destAction = CGPathNode::UNKNOWN; if(!isMovementToDestPossible()) continue; int cost = gs->getMovementCost(hero, cp->coord, dp->coord, movement); int remains = movement - cost; - if(useEmbarkCost) + if(destAction == CGPathNode::EMBARK || destAction == CGPathNode::DISEMBARK) { - remains = hero->movementPointsAfterEmbark(movement, cost, useEmbarkCost - 1); + remains = hero->movementPointsAfterEmbark(movement, cost, destAction - 1); cost = movement - remains; } @@ -144,6 +144,7 @@ void CPathfinder::calculatePaths() dp->moveRemains = remains; dp->turns = turnAtNextTile; dp->theNodeBefore = cp; + dp->action = destAction; if(isMovementAfterDestPossible()) pq.push(dp); @@ -166,6 +167,7 @@ void CPathfinder::calculatePaths() dp->moveRemains = movement; dp->turns = turn; dp->theNodeBefore = cp; + dp->action = CGPathNode::NORMAL; pq.push(dp); } } @@ -258,24 +260,18 @@ bool CPathfinder::isLayerTransitionPossible() if((dp->accessible != CGPathNode::ACCESSIBLE && (dp->accessible != CGPathNode::BLOCKVIS || dt->blocked)) || dt->visitable) //TODO: passableness problem -> town says it's passable (thus accessible) but we obviously can't disembark onto town gate return false; - - useEmbarkCost = 2; } else if(cp->layer == EPathfindingLayer::LAND && dp->layer == EPathfindingLayer::SAIL) { - Obj destTopVisObjID = dt->topVisitableId(); - if(dp->accessible == CGPathNode::ACCESSIBLE || destTopVisObjID < 0) //cannot enter empty water tile from land -> it has to be visitable + if(dp->accessible == CGPathNode::ACCESSIBLE) //cannot enter empty water tile from land -> it has to be visitable return false; - if(destTopVisObjID != Obj::HERO && destTopVisObjID != Obj::BOAT) //only boat or hero can be accessed from land - return false; - if(destTopVisObjID == Obj::BOAT) - useEmbarkCost = 1; } return true; } bool CPathfinder::isMovementToDestPossible() { + auto obj = dt->topVisitableObj(); switch(dp->layer) { case EPathfindingLayer::LAND: @@ -284,6 +280,9 @@ bool CPathfinder::isMovementToDestPossible() if(isSourceGuarded() && !isDestinationGuardian()) // Can step into tile of guard return false; + if(cp->layer == EPathfindingLayer::SAIL) + destAction = CGPathNode::DISEMBARK; + break; case EPathfindingLayer::SAIL: if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED) @@ -291,6 +290,16 @@ bool CPathfinder::isMovementToDestPossible() if(isSourceGuarded() && !isDestinationGuardian()) // Can step into tile of guard return false; + if(cp->layer == EPathfindingLayer::LAND) + { + if(!obj) + return false; + + if(obj->ID == Obj::BOAT) + destAction = CGPathNode::EMBARK; + else if(obj->ID != Obj::HERO) + return false; + } break; case EPathfindingLayer::AIR: @@ -308,6 +317,29 @@ bool CPathfinder::isMovementToDestPossible() break; } + if(destAction == CGPathNode::UNKNOWN) + { + destAction = CGPathNode::NORMAL; + if(dp->layer == EPathfindingLayer::LAND || dp->layer == EPathfindingLayer::SAIL) + { + if(obj) + { + if(obj->ID == Obj::HERO || obj->ID == Obj::TOWN) + { + if(getPlayerRelations(obj->tempOwner, hero->tempOwner) == PlayerRelations::ENEMIES) + destAction = CGPathNode::BATTLE; + else + destAction = CGPathNode::BLOCKING_VISIT; // TODO: Probably you should be able to go into air from town too + } + else if(obj->blockVisit) + destAction = CGPathNode::BLOCKING_VISIT; + else + destAction = CGPathNode::VISIT; + } + else if(isDestinationGuarded()) + destAction = CGPathNode::BATTLE; + } + } return true; } @@ -330,7 +362,7 @@ bool CPathfinder::isMovementAfterDestPossible() if(!whirlpool || options.useTeleportWhirlpool) return true; } - if(useEmbarkCost && options.useEmbarkAndDisembark) + if((destAction == CGPathNode::EMBARK || destAction == CGPathNode::DISEMBARK) && options.useEmbarkAndDisembark) return true; break; @@ -527,6 +559,7 @@ void CGPathNode::reset() moveRemains = 0; turns = 255; theNodeBefore = nullptr; + action = UNKNOWN; } bool CGPathNode::reachable() const diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 2d3616f42..f9afcd698 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -23,6 +23,17 @@ struct TerrainTile; struct DLL_LINKAGE CGPathNode { + enum ENodeAction + { + UNKNOWN = -1, + NORMAL = 0, + EMBARK = 1, + DISEMBARK, //2 + BATTLE,//3 + VISIT,//4 + BLOCKING_VISIT//5 + }; + enum EAccessibility { NOT_SET = 0, @@ -40,6 +51,7 @@ struct DLL_LINKAGE CGPathNode CGPathNode * theNodeBefore; int3 coord; //coordinates EPathfindingLayer layer; + ENodeAction action; CGPathNode(int3 Coord, EPathfindingLayer Layer); void reset(); @@ -120,7 +132,7 @@ private: CGPathNode *dp; //destination node -> it's a neighbour of cp that we consider const TerrainTile *ct, *dt; //tile info for both nodes const CGObjectInstance *sTileObj; - ui8 useEmbarkCost; //0 - usual movement; 1 - embark; 2 - disembark + CGPathNode::ENodeAction destAction; void addNeighbours(const int3 &coord); void addTeleportExits(bool noTeleportExcludes = false); From 160fa382544824c8e94617f7b0bc7c04cd1d3b5b Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sun, 8 Nov 2015 03:10:48 +0300 Subject: [PATCH 26/83] Client: change cursor using node action information of pathfinder --- client/windows/CAdvmapInterface.cpp | 147 ++++++++-------------------- lib/CPathfinder.cpp | 29 ++++-- lib/CPathfinder.h | 2 +- 3 files changed, 65 insertions(+), 113 deletions(-) diff --git a/client/windows/CAdvmapInterface.cpp b/client/windows/CAdvmapInterface.cpp index ab3ab31b7..65d5b0324 100644 --- a/client/windows/CAdvmapInterface.cpp +++ b/client/windows/CAdvmapInterface.cpp @@ -1477,8 +1477,10 @@ void CAdvMapInt::tileHovered(const int3 &mapPos) } const CGObjectInstance *objAtTile = getActiveObject(mapPos); - if (objAtTile) + auto objRelations = PlayerRelations::ALLIES; + if(objAtTile) { + objRelations = LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, objAtTile->tempOwner); std::string text = curHero() ? objAtTile->getHoverText(curHero()) : objAtTile->getHoverText(LOCPLINT->playerID); boost::replace_all(text,"\n"," "); statusbar.setText(text); @@ -1516,8 +1518,6 @@ void CAdvMapInt::tileHovered(const int3 &mapPos) } } - const bool guardingCreature = CGI->mh->map->isInTheMap(LOCPLINT->cb->getGuardingCreaturePosition(mapPos)); - if(selection->ID == Obj::TOWN) { if(objAtTile) @@ -1534,125 +1534,60 @@ void CAdvMapInt::tileHovered(const int3 &mapPos) } else if(const CGHeroInstance *h = curHero()) { - const CGPathNode *pnode = LOCPLINT->cb->getPathsInfo(h)->getPathInfo(mapPos); - + const CGPathNode * pnode = LOCPLINT->cb->getPathsInfo(h)->getPathInfo(mapPos); int turns = pnode->turns; vstd::amin(turns, 3); - bool accessible = pnode->turns < 255; - - if(objAtTile) + switch(pnode->action) { + case CGPathNode::NORMAL: + if(pnode->layer == EPathfindingLayer::LAND) + CCS->curh->changeGraphic(ECursor::ADVENTURE, 4 + turns*6); + else + CCS->curh->changeGraphic(ECursor::ADVENTURE, 28 + turns); + break; + + case CGPathNode::VISIT: + case CGPathNode::BLOCKING_VISIT: if(objAtTile->ID == Obj::HERO) { - if(!LOCPLINT->cb->getPlayerRelations( LOCPLINT->playerID, objAtTile->tempOwner)) //enemy hero - { - if(accessible) - CCS->curh->changeGraphic(ECursor::ADVENTURE, 5 + turns*6); - else - CCS->curh->changeGraphic(ECursor::ADVENTURE, 0); - } - else //our or ally hero - { - if(selection == objAtTile) - CCS->curh->changeGraphic(ECursor::ADVENTURE, 2); - else if(accessible) - CCS->curh->changeGraphic(ECursor::ADVENTURE, 8 + turns*6); - else - CCS->curh->changeGraphic(ECursor::ADVENTURE, 2); - } - } - else if(objAtTile->ID == Obj::TOWN) - { - if(!LOCPLINT->cb->getPlayerRelations( LOCPLINT->playerID, objAtTile->tempOwner)) //enemy town - { - if(accessible) - { - const CGTownInstance* townObj = dynamic_cast(objAtTile); - - // Show movement cursor for unguarded enemy towns, otherwise attack cursor. - if (townObj && !townObj->armedGarrison()) - CCS->curh->changeGraphic(ECursor::ADVENTURE, 9 + turns*6); - else - CCS->curh->changeGraphic(ECursor::ADVENTURE, 5 + turns*6); - - } - else - { - CCS->curh->changeGraphic(ECursor::ADVENTURE, 0); - } - } - else //our or ally town - { - if(accessible) - CCS->curh->changeGraphic(ECursor::ADVENTURE, 9 + turns*6); - else - CCS->curh->changeGraphic(ECursor::ADVENTURE, 3); - } - } - else if(objAtTile->ID == Obj::BOAT) - { - if(accessible) - CCS->curh->changeGraphic(ECursor::ADVENTURE, 6 + turns*6); + if(selection == objAtTile) + CCS->curh->changeGraphic(ECursor::ADVENTURE, 2); else - CCS->curh->changeGraphic(ECursor::ADVENTURE, 0); - } - else if (objAtTile->ID == Obj::GARRISON || objAtTile->ID == Obj::GARRISON2) - { - if (accessible) - { - const CGGarrison* garrObj = dynamic_cast(objAtTile); //TODO evil evil cast! - - // Show battle cursor for guarded enemy garrisons or garrisons have guarding creature behind, otherwise movement cursor. - if (garrObj && ((garrObj->stacksCount() - && !LOCPLINT->cb->getPlayerRelations( LOCPLINT->playerID, garrObj->tempOwner)) - || guardingCreature)) - CCS->curh->changeGraphic(ECursor::ADVENTURE, 5 + turns*6); - else - CCS->curh->changeGraphic(ECursor::ADVENTURE, 9 + turns*6); - } - else - CCS->curh->changeGraphic(ECursor::ADVENTURE, 0); - } - else if (guardingCreature && accessible) //(objAtTile->ID == 54) //monster - { - CCS->curh->changeGraphic(ECursor::ADVENTURE, 5 + turns*6); + CCS->curh->changeGraphic(ECursor::ADVENTURE, 8 + turns*6); } + else if(pnode->layer == EPathfindingLayer::LAND) + CCS->curh->changeGraphic(ECursor::ADVENTURE, 9 + turns*6); else + CCS->curh->changeGraphic(ECursor::ADVENTURE, 28 + turns); + break; + + case CGPathNode::BATTLE: + CCS->curh->changeGraphic(ECursor::ADVENTURE, 5 + turns*6); + break; + + case CGPathNode::EMBARK: + CCS->curh->changeGraphic(ECursor::ADVENTURE, 6 + turns*6); + break; + + case CGPathNode::DISEMBARK: + CCS->curh->changeGraphic(ECursor::ADVENTURE, 7 + turns*6); + break; + + default: + if(objAtTile && objRelations != PlayerRelations::ENEMIES) { - if(accessible) + if(objAtTile->ID == Obj::TOWN) { - if(pnode->land) - CCS->curh->changeGraphic(ECursor::ADVENTURE, 9 + turns*6); - else - CCS->curh->changeGraphic(ECursor::ADVENTURE, 28 + turns); + CCS->curh->changeGraphic(ECursor::ADVENTURE, 3); } - else - CCS->curh->changeGraphic(ECursor::ADVENTURE, 0); - } - } - else //no objs - { - if(accessible/* && pnode->accessible != CGPathNode::FLYABLE*/) - { - if (guardingCreature) + else if(objAtTile->ID == Obj::HERO && objRelations == PlayerRelations::SAME_PLAYER) { - CCS->curh->changeGraphic(ECursor::ADVENTURE, 5 + turns*6); - } - else - { - if(pnode->land) - { - if(LOCPLINT->cb->getTile(h->getPosition(false))->terType != ETerrainType::WATER) - CCS->curh->changeGraphic(ECursor::ADVENTURE, 4 + turns*6); - else - CCS->curh->changeGraphic(ECursor::ADVENTURE, 7 + turns*6); //anchor - } - else - CCS->curh->changeGraphic(ECursor::ADVENTURE, 6 + turns*6); + CCS->curh->changeGraphic(ECursor::ADVENTURE, 2); } } else CCS->curh->changeGraphic(ECursor::ADVENTURE, 0); + break; } } diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index b54d915c8..2f6cdef6a 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -324,16 +324,33 @@ bool CPathfinder::isMovementToDestPossible() { if(obj) { - if(obj->ID == Obj::HERO || obj->ID == Obj::TOWN) + auto objRel = getPlayerRelations(obj->tempOwner, hero->tempOwner); + if(obj->ID == Obj::HERO) { - if(getPlayerRelations(obj->tempOwner, hero->tempOwner) == PlayerRelations::ENEMIES) + if(objRel == PlayerRelations::ENEMIES) destAction = CGPathNode::BATTLE; else - destAction = CGPathNode::BLOCKING_VISIT; // TODO: Probably you should be able to go into air from town too + destAction = CGPathNode::BLOCKING_VISIT; } + else if(obj->ID == Obj::TOWN && objRel == PlayerRelations::ENEMIES) + { + const CGTownInstance * townObj = dynamic_cast(obj); + if (townObj->armedGarrison()) + destAction = CGPathNode::BATTLE; + } + else if(obj->ID == Obj::GARRISON || obj->ID == Obj::GARRISON2) + { + const CGGarrison * garrisonObj = dynamic_cast(obj); + if((garrisonObj->stacksCount() && objRel == PlayerRelations::ENEMIES) || isDestinationGuarded(true)) + destAction = CGPathNode::BATTLE; + } + else if(isDestinationGuardian()) + destAction = CGPathNode::BATTLE; else if(obj->blockVisit) destAction = CGPathNode::BLOCKING_VISIT; - else + + + if(destAction == CGPathNode::NORMAL) destAction = CGPathNode::VISIT; } else if(isDestinationGuarded()) @@ -404,10 +421,10 @@ bool CPathfinder::isSourceGuarded() return false; } -bool CPathfinder::isDestinationGuarded() +bool CPathfinder::isDestinationGuarded(bool ignoreAccessibility) { if(gs->map->guardingCreaturePositions[dp->coord.x][dp->coord.y][dp->coord.z].valid() - && dp->accessible == CGPathNode::BLOCKVIS) + && (ignoreAccessibility || dp->accessible == CGPathNode::BLOCKVIS)) { return true; } diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index f9afcd698..69b7f91a8 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -144,7 +144,7 @@ private: bool isSourceInitialPosition(); int3 getSourceGuardPosition(); bool isSourceGuarded(); - bool isDestinationGuarded(); + bool isDestinationGuarded(bool ignoreAccessibility = true); bool isDestinationGuardian(); void initializeGraph(); From 842da69a3e2132ad049d08d75ca6190f5312ece3 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sun, 8 Nov 2015 03:33:01 +0300 Subject: [PATCH 27/83] CAdvMapInt::tileHovered cleanup function a bit more --- client/windows/CAdvmapInterface.cpp | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/client/windows/CAdvmapInterface.cpp b/client/windows/CAdvmapInterface.cpp index 65d5b0324..4899732c0 100644 --- a/client/windows/CAdvmapInterface.cpp +++ b/client/windows/CAdvmapInterface.cpp @@ -1467,7 +1467,8 @@ void CAdvMapInt::tileLClicked(const int3 &mapPos) void CAdvMapInt::tileHovered(const int3 &mapPos) { - if(mode != EAdvMapMode::NORMAL) + if(mode != EAdvMapMode::NORMAL //disable in world view + || !selection) //may occur just at the start of game (fake move before full intiialization) return; if(!LOCPLINT->cb->isVisible(mapPos)) { @@ -1475,9 +1476,8 @@ void CAdvMapInt::tileHovered(const int3 &mapPos) statusbar.clear(); return; } - const CGObjectInstance *objAtTile = getActiveObject(mapPos); - auto objRelations = PlayerRelations::ALLIES; + const CGObjectInstance *objAtTile = getActiveObject(mapPos); if(objAtTile) { objRelations = LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, objAtTile->tempOwner); @@ -1492,9 +1492,6 @@ void CAdvMapInt::tileHovered(const int3 &mapPos) statusbar.setText(hlp); } - if(!selection) //may occur just at the start of game (fake move before full intiialization) - return; - if(spellBeingCasted) { switch(spellBeingCasted->id) @@ -1507,9 +1504,9 @@ void CAdvMapInt::tileHovered(const int3 &mapPos) return; case SpellID::DIMENSION_DOOR: { - const TerrainTile *t = LOCPLINT->cb->getTile(mapPos, false); + const TerrainTile * t = LOCPLINT->cb->getTile(mapPos, false); int3 hpos = selection->getSightCenter(); - if((!t || t->isClear(LOCPLINT->cb->getTile(hpos))) && isInScreenRange(hpos, mapPos)) + if((!t || t->isClear(LOCPLINT->cb->getTile(hpos))) && isInScreenRange(hpos, mapPos)) CCS->curh->changeGraphic(ECursor::ADVENTURE, 41); else CCS->curh->changeGraphic(ECursor::ADVENTURE, 0); @@ -1522,9 +1519,9 @@ void CAdvMapInt::tileHovered(const int3 &mapPos) { if(objAtTile) { - if(objAtTile->ID == Obj::TOWN && LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, objAtTile->tempOwner) != PlayerRelations::ENEMIES) + if(objAtTile->ID == Obj::TOWN && objRelations != PlayerRelations::ENEMIES) CCS->curh->changeGraphic(ECursor::ADVENTURE, 3); - else if(objAtTile->ID == Obj::HERO && objAtTile->tempOwner == LOCPLINT->playerID) + else if(objAtTile->ID == Obj::HERO && objRelations == PlayerRelations::SAME_PLAYER) CCS->curh->changeGraphic(ECursor::ADVENTURE, 2); else CCS->curh->changeGraphic(ECursor::ADVENTURE, 0); @@ -1532,7 +1529,7 @@ void CAdvMapInt::tileHovered(const int3 &mapPos) else CCS->curh->changeGraphic(ECursor::ADVENTURE, 0); } - else if(const CGHeroInstance *h = curHero()) + else if(const CGHeroInstance * h = curHero()) { const CGPathNode * pnode = LOCPLINT->cb->getPathsInfo(h)->getPathInfo(mapPos); int turns = pnode->turns; @@ -1577,13 +1574,9 @@ void CAdvMapInt::tileHovered(const int3 &mapPos) if(objAtTile && objRelations != PlayerRelations::ENEMIES) { if(objAtTile->ID == Obj::TOWN) - { CCS->curh->changeGraphic(ECursor::ADVENTURE, 3); - } else if(objAtTile->ID == Obj::HERO && objRelations == PlayerRelations::SAME_PLAYER) - { CCS->curh->changeGraphic(ECursor::ADVENTURE, 2); - } } else CCS->curh->changeGraphic(ECursor::ADVENTURE, 0); From 4973a1ec90b7df2c48619fa6f90b4402d6b22e3b Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sun, 8 Nov 2015 04:01:58 +0300 Subject: [PATCH 28/83] CGPathNode: get rid of land member as it's now obsolete CTerrainRect::showPath behaviour changed so it's will only add cross path graphics on embark/disembark and path ending. We want continuous paths for flying and water walking even when land<-> water transition occur. --- client/windows/CAdvmapInterface.cpp | 2 +- lib/CPathfinder.cpp | 8 +++----- lib/CPathfinder.h | 1 - 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/client/windows/CAdvmapInterface.cpp b/client/windows/CAdvmapInterface.cpp index 4899732c0..44274fa3c 100644 --- a/client/windows/CAdvmapInterface.cpp +++ b/client/windows/CAdvmapInterface.cpp @@ -188,7 +188,7 @@ void CTerrainRect::showPath(const SDL_Rect * extRect, SDL_Surface * to) * is id1=7, id2=5 (pns[7][5]) */ bool pathContinuous = curPos.areNeighbours(nextPos) && curPos.areNeighbours(prevPos); - if(pathContinuous && cv[i].land == cv[i+1].land) + if(pathContinuous && cv[i].action != CGPathNode::EMBARK && cv[i].action != CGPathNode::DISEMBARK) { 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 diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 2f6cdef6a..7994ec902 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -63,7 +63,7 @@ void CPathfinder::calculatePaths() auto maxMovePoints = [&](CGPathNode *cp) -> int { - return cp->land ? maxMovePointsLand : maxMovePointsWater; + return cp->layer == EPathfindingLayer::SAIL ? maxMovePointsWater : maxMovePointsLand; }; auto isBetterWay = [&](int remains, int turn) -> bool @@ -181,7 +181,7 @@ void CPathfinder::addNeighbours(const int3 &coord) ct = &gs->map->getTile(coord); std::vector tiles; - gs->getNeighbours(*ct, coord, tiles, boost::logic::indeterminate, !cp->land); + gs->getNeighbours(*ct, coord, tiles, boost::logic::indeterminate, cp->layer == EPathfindingLayer::SAIL); // TODO: find out if we still need "limitCoastSailing" option sTileObj = ct->topVisitableObj(coord == out.hpos); if(canVisitObject()) { @@ -411,7 +411,7 @@ bool CPathfinder::isSourceGuarded() { //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 - || !cp->theNodeBefore->land + || !cp->theNodeBefore->layer != EPathfindingLayer::LAND || ct->topVisitableId() != Obj::BOAT) { return true; @@ -444,7 +444,6 @@ void CPathfinder::initializeGraph() auto node = out.getNode(pos, layer); node->reset(); node->accessible = evaluateAccessibility(pos, tinfo); - node->land = tinfo->terType != ETerrainType::WATER; }; int3 pos; @@ -572,7 +571,6 @@ void CGPathNode::reset() { locked = false; accessible = NOT_SET; - land = 0; moveRemains = 0; turns = 255; theNodeBefore = nullptr; diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 69b7f91a8..e0bd8cea5 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -45,7 +45,6 @@ struct DLL_LINKAGE CGPathNode bool locked; EAccessibility accessible; - ui8 land; ui8 turns; //how many turns we have to wait before reachng the tile - 0 means current turn ui32 moveRemains; //remaining tiles after hero reaches the tile CGPathNode * theNodeBefore; From 2fb73c55e1b98fcd26dd8b5daacccb20bca44829 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sun, 8 Nov 2015 04:41:06 +0300 Subject: [PATCH 29/83] CPathfinder: use node action in isMovementAfterDestPossible checks --- lib/CPathfinder.cpp | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 7994ec902..209dfee29 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -363,31 +363,31 @@ bool CPathfinder::isMovementToDestPossible() bool CPathfinder::isMovementAfterDestPossible() { - switch(dp->layer) + switch(destAction) { - case EPathfindingLayer::LAND: - case EPathfindingLayer::SAIL: - if(dp->accessible == CGPathNode::ACCESSIBLE) + /// TODO: Investigate what kind of limitation is possible to apply on movement from visitable tiles + /// Likely in many cases we don't need to add visitable tile to queue when hero don't fly + case CGPathNode::VISIT: + if(CGTeleport::isTeleport(dt->topVisitableObj())) + { + /// For now we'll always allow transit over teleporters + /// Transit over whirlpools only allowed when hero protected + auto whirlpool = dynamic_cast(dt->topVisitableObj()); + if(!whirlpool || options.useTeleportWhirlpool) return true; - if(dp->coord == out.hpos) - return true; // This one is tricky, we can ignore fact that tile is not ACCESSIBLE in case if it's our hero block it. Though this need investigation - if(dp->accessible == CGPathNode::VISITABLE && CGTeleport::isTeleport(dt->topVisitableObj())) - { - /// For now we'll always allow transit over teleporters - /// Transit over whirlpools only allowed when hero protected - auto whirlpool = dynamic_cast(dt->topVisitableObj()); - if(!whirlpool || options.useTeleportWhirlpool) - return true; - } - if((destAction == CGPathNode::EMBARK || destAction == CGPathNode::DISEMBARK) && options.useEmbarkAndDisembark) - return true; - break; + } + else + return true; + case CGPathNode::NORMAL: + return true; - case EPathfindingLayer::AIR: - case EPathfindingLayer::WATER: - return true; + case CGPathNode::EMBARK: + if(options.useEmbarkAndDisembark) + return true; - break; + case CGPathNode::DISEMBARK: + if(options.useEmbarkAndDisembark && !isDestinationGuarded()) + return true; } return false; From 4af9c7c29dd849dc97571df3ef82a0f5342f7ac2 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sun, 8 Nov 2015 07:44:00 +0300 Subject: [PATCH 30/83] CPathfinder: add one turn boundary for flying and water walking Now when oneTurnSpecialLayersLimit is enabled hero won't be able to stop on blocked or water tiles between turns. It's default H3 mechanics so it's enabled by default. --- lib/CPathfinder.cpp | 41 ++++++++++++++++++++++++++++++++--------- lib/CPathfinder.h | 7 +++++++ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 209dfee29..9e19f1854 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -29,6 +29,7 @@ CPathfinder::PathfinderOptions::PathfinderOptions() useTeleportWhirlpool = false; lightweightFlyingMode = false; + oneTurnSpecialLayersLimit = true; } CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero) @@ -66,6 +67,19 @@ void CPathfinder::calculatePaths() return cp->layer == EPathfindingLayer::SAIL ? maxMovePointsWater : maxMovePointsLand; }; + auto passOneTurnLimitCheck = [&](bool shouldCheck) -> bool + { + if(options.oneTurnSpecialLayersLimit && shouldCheck) + { + if((cp->layer == EPathfindingLayer::AIR || cp->layer == EPathfindingLayer::WATER) + && cp->accessible != CGPathNode::ACCESSIBLE) + { + return false; + } + } + return true; + }; + auto isBetterWay = [&](int remains, int turn) -> bool { if(dp->turns == 0xff) //we haven't been here before @@ -113,9 +127,13 @@ void CPathfinder::calculatePaths() if(dp->locked) continue; + if(!passOneTurnLimitCheck(cp->turns != turn)) + continue; + if(cp->layer != i && !isLayerTransitionPossible()) continue; + destAction = CGPathNode::UNKNOWN; if(!isMovementToDestPossible()) continue; @@ -127,7 +145,6 @@ void CPathfinder::calculatePaths() remains = hero->movementPointsAfterEmbark(movement, cost, destAction - 1); cost = movement - remains; } - int turnAtNextTile = turn; if(remains < 0) { @@ -138,7 +155,8 @@ void CPathfinder::calculatePaths() remains = moveAtNextTile - cost; } - if(isBetterWay(remains, turnAtNextTile)) + if(isBetterWay(remains, turnAtNextTile) + && passOneTurnLimitCheck(cp->turns != turnAtNextTile || !remains)) { assert(dp != cp->theNodeBefore); //two tiles can't point to each other dp->moveRemains = remains; @@ -439,11 +457,16 @@ bool CPathfinder::isDestinationGuardian() void CPathfinder::initializeGraph() { - auto updateNode = [&](int3 pos, EPathfindingLayer layer, const TerrainTile *tinfo) + auto updateNode = [&](int3 pos, EPathfindingLayer layer, const TerrainTile *tinfo, bool blockNotAccessible) { auto node = out.getNode(pos, layer); node->reset(); - node->accessible = evaluateAccessibility(pos, tinfo); + + auto accessibility = evaluateAccessibility(pos, tinfo); + if(blockNotAccessible + && (accessibility != CGPathNode::ACCESSIBLE || tinfo->terType == ETerrainType::WATER)) + accessibility = CGPathNode::BLOCKED; + node->accessible = accessibility; }; int3 pos; @@ -459,16 +482,16 @@ void CPathfinder::initializeGraph() case ETerrainType::ROCK: break; case ETerrainType::WATER: - updateNode(pos, EPathfindingLayer::SAIL, tinfo); + updateNode(pos, EPathfindingLayer::SAIL, tinfo, false); if(options.useFlying) - updateNode(pos, EPathfindingLayer::AIR, tinfo); + updateNode(pos, EPathfindingLayer::AIR, tinfo, true); if(options.useWaterWalking) - updateNode(pos, EPathfindingLayer::WATER, tinfo); + updateNode(pos, EPathfindingLayer::WATER, tinfo, true); break; default: - updateNode(pos, EPathfindingLayer::LAND, tinfo); + updateNode(pos, EPathfindingLayer::LAND, tinfo, false); if(options.useFlying) - updateNode(pos, EPathfindingLayer::AIR, tinfo); + updateNode(pos, EPathfindingLayer::AIR, tinfo, true); break; } } diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index e0bd8cea5..209e89493 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -105,6 +105,13 @@ private: /// Downside is less MP effective paths calculation. bool lightweightFlyingMode; + /// This option enable one turn limitation for flying and water walking. + /// So if we're out of MP while cp is blocked or water tile we won't add dest tile to queue. + /// + /// Following imitation is default H3 mechanics, but someone may want to disable it in mods. + /// After all this limit should benefit performance on maps with tons of water or blocked tiles. + bool oneTurnSpecialLayersLimit; + PathfinderOptions(); } options; From f590b364c53c16ac212a77abe9b8afe9aa991fe8 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sun, 8 Nov 2015 08:27:51 +0300 Subject: [PATCH 31/83] Pathfinder: shorten EPathfindingLayer to ELayer --- lib/CPathfinder.cpp | 76 ++++++++++++++++++++++----------------------- lib/CPathfinder.h | 18 +++++++---- 2 files changed, 50 insertions(+), 44 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 9e19f1854..1bd34d30d 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -64,14 +64,14 @@ void CPathfinder::calculatePaths() auto maxMovePoints = [&](CGPathNode *cp) -> int { - return cp->layer == EPathfindingLayer::SAIL ? maxMovePointsWater : maxMovePointsLand; + return cp->layer == ELayer::SAIL ? maxMovePointsWater : maxMovePointsLand; }; auto passOneTurnLimitCheck = [&](bool shouldCheck) -> bool { if(options.oneTurnSpecialLayersLimit && shouldCheck) { - if((cp->layer == EPathfindingLayer::AIR || cp->layer == EPathfindingLayer::WATER) + if((cp->layer == ELayer::AIR || cp->layer == ELayer::WATER) && cp->accessible != CGPathNode::ACCESSIBLE) { return false; @@ -95,7 +95,7 @@ void CPathfinder::calculatePaths() //logGlobal->infoStream() << boost::format("Calculating paths for hero %s (adress %d) of player %d") % hero->name % hero % hero->tempOwner; //initial tile - set cost on 0 and add to the queue - CGPathNode *initialNode = out.getNode(out.hpos, hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND); + CGPathNode *initialNode = out.getNode(out.hpos, hero->boat ? ELayer::SAIL : ELayer::LAND); initialNode->turns = 0; initialNode->moveRemains = hero->movement; pq.push(initialNode); @@ -118,7 +118,7 @@ void CPathfinder::calculatePaths() for(auto & neighbour : neighbours) { dt = &gs->map->getTile(neighbour); - for(EPathfindingLayer i = EPathfindingLayer::LAND; i <= EPathfindingLayer::AIR; i.advance(1)) + for(ELayer i = ELayer::LAND; i <= ELayer::AIR; i.advance(1)) { dp = out.getNode(neighbour, i); if(dp->accessible == CGPathNode::NOT_SET) @@ -199,7 +199,7 @@ void CPathfinder::addNeighbours(const int3 &coord) ct = &gs->map->getTile(coord); std::vector tiles; - gs->getNeighbours(*ct, coord, tiles, boost::logic::indeterminate, cp->layer == EPathfindingLayer::SAIL); // TODO: find out if we still need "limitCoastSailing" option + gs->getNeighbours(*ct, coord, tiles, boost::logic::indeterminate, cp->layer == ELayer::SAIL); // TODO: find out if we still need "limitCoastSailing" option sTileObj = ct->topVisitableObj(coord == out.hpos); if(canVisitObject()) { @@ -257,19 +257,19 @@ void CPathfinder::addTeleportExits(bool noTeleportExcludes) bool CPathfinder::isLayerTransitionPossible() { - if((cp->layer == EPathfindingLayer::AIR || cp->layer == EPathfindingLayer::WATER) - && dp->layer != EPathfindingLayer::LAND) + if((cp->layer == ELayer::AIR || cp->layer == ELayer::WATER) + && dp->layer != ELayer::LAND) { return false; } - else if(cp->layer == EPathfindingLayer::LAND && dp->layer == EPathfindingLayer::AIR) + else if(cp->layer == ELayer::LAND && dp->layer == ELayer::AIR) { if(options.lightweightFlyingMode && !isSourceInitialPosition()) return false; } - else if(cp->layer == EPathfindingLayer::SAIL && dp->layer != EPathfindingLayer::LAND) + else if(cp->layer == ELayer::SAIL && dp->layer != ELayer::LAND) return false; - else if(cp->layer == EPathfindingLayer::SAIL && dp->layer == EPathfindingLayer::LAND) + else if(cp->layer == ELayer::SAIL && dp->layer == ELayer::LAND) { if(!dt->isCoastal()) return false; @@ -279,7 +279,7 @@ bool CPathfinder::isLayerTransitionPossible() || dt->visitable) //TODO: passableness problem -> town says it's passable (thus accessible) but we obviously can't disembark onto town gate return false; } - else if(cp->layer == EPathfindingLayer::LAND && dp->layer == EPathfindingLayer::SAIL) + else if(cp->layer == ELayer::LAND && dp->layer == ELayer::SAIL) { if(dp->accessible == CGPathNode::ACCESSIBLE) //cannot enter empty water tile from land -> it has to be visitable return false; @@ -292,23 +292,23 @@ bool CPathfinder::isMovementToDestPossible() auto obj = dt->topVisitableObj(); switch(dp->layer) { - case EPathfindingLayer::LAND: + case ELayer::LAND: if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED) return false; if(isSourceGuarded() && !isDestinationGuardian()) // Can step into tile of guard return false; - if(cp->layer == EPathfindingLayer::SAIL) + if(cp->layer == ELayer::SAIL) destAction = CGPathNode::DISEMBARK; break; - case EPathfindingLayer::SAIL: + case ELayer::SAIL: if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED) return false; if(isSourceGuarded() && !isDestinationGuardian()) // Can step into tile of guard return false; - if(cp->layer == EPathfindingLayer::LAND) + if(cp->layer == ELayer::LAND) { if(!obj) return false; @@ -320,13 +320,13 @@ bool CPathfinder::isMovementToDestPossible() } break; - case EPathfindingLayer::AIR: + case ELayer::AIR: //if(!canMoveBetween(cp->coord, dp->coord)) // return false; break; - case EPathfindingLayer::WATER: + case ELayer::WATER: if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible != CGPathNode::ACCESSIBLE) return false; if(isDestinationGuarded()) @@ -338,7 +338,7 @@ bool CPathfinder::isMovementToDestPossible() if(destAction == CGPathNode::UNKNOWN) { destAction = CGPathNode::NORMAL; - if(dp->layer == EPathfindingLayer::LAND || dp->layer == EPathfindingLayer::SAIL) + if(dp->layer == ELayer::LAND || dp->layer == ELayer::SAIL) { if(obj) { @@ -429,7 +429,7 @@ bool CPathfinder::isSourceGuarded() { //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 - || !cp->theNodeBefore->layer != EPathfindingLayer::LAND + || !cp->theNodeBefore->layer != ELayer::LAND || ct->topVisitableId() != Obj::BOAT) { return true; @@ -457,7 +457,7 @@ bool CPathfinder::isDestinationGuardian() void CPathfinder::initializeGraph() { - auto updateNode = [&](int3 pos, EPathfindingLayer layer, const TerrainTile *tinfo, bool blockNotAccessible) + auto updateNode = [&](int3 pos, ELayer layer, const TerrainTile *tinfo, bool blockNotAccessible) { auto node = out.getNode(pos, layer); node->reset(); @@ -482,16 +482,16 @@ void CPathfinder::initializeGraph() case ETerrainType::ROCK: break; case ETerrainType::WATER: - updateNode(pos, EPathfindingLayer::SAIL, tinfo, false); + updateNode(pos, ELayer::SAIL, tinfo, false); if(options.useFlying) - updateNode(pos, EPathfindingLayer::AIR, tinfo, true); + updateNode(pos, ELayer::AIR, tinfo, true); if(options.useWaterWalking) - updateNode(pos, EPathfindingLayer::WATER, tinfo, true); + updateNode(pos, ELayer::WATER, tinfo, true); break; default: - updateNode(pos, EPathfindingLayer::LAND, tinfo, false); + updateNode(pos, ELayer::LAND, tinfo, false); if(options.useFlying) - updateNode(pos, EPathfindingLayer::AIR, tinfo, true); + updateNode(pos, ELayer::AIR, tinfo, true); break; } } @@ -581,10 +581,10 @@ bool CPathfinder::addTeleportWhirlpool(const CGWhirlpool * obj) const bool CPathfinder::canVisitObject() const { //hero can't visit objects while walking on water or flying - return cp->layer == EPathfindingLayer::LAND || cp->layer == EPathfindingLayer::SAIL; + return cp->layer == ELayer::LAND || cp->layer == ELayer::SAIL; } -CGPathNode::CGPathNode(int3 Coord, EPathfindingLayer Layer) +CGPathNode::CGPathNode(int3 Coord, ELayer Layer) : coord(Coord), layer(Layer) { reset(); @@ -630,28 +630,28 @@ CPathsInfo::CPathsInfo(const int3 &Sizes) : sizes(Sizes) { hero = nullptr; - nodes.resize(boost::extents[sizes.x][sizes.y][sizes.z][EPathfindingLayer::NUM_LAYERS]); + nodes.resize(boost::extents[sizes.x][sizes.y][sizes.z][ELayer::NUM_LAYERS]); for(int i = 0; i < sizes.x; i++) for(int j = 0; j < sizes.y; j++) for(int z = 0; z < sizes.z; z++) - for(int l = 0; l < EPathfindingLayer::NUM_LAYERS; l++) - nodes[i][j][z][l] = new CGPathNode(int3(i, j, z), static_cast(l)); + for(int l = 0; l < ELayer::NUM_LAYERS; l++) + nodes[i][j][z][l] = new CGPathNode(int3(i, j, z), static_cast(l)); } CPathsInfo::~CPathsInfo() { } -const CGPathNode * CPathsInfo::getPathInfo(const int3 &tile, const EPathfindingLayer &layer) const +const CGPathNode * CPathsInfo::getPathInfo(const int3 &tile, const ELayer &layer) const { boost::unique_lock pathLock(pathMx); - if(tile.x >= sizes.x || tile.y >= sizes.y || tile.z >= sizes.z || layer >= EPathfindingLayer::NUM_LAYERS) + if(tile.x >= sizes.x || tile.y >= sizes.y || tile.z >= sizes.z || layer >= ELayer::NUM_LAYERS) return nullptr; return getNode(tile, layer); } -bool CPathsInfo::getPath(CGPath &out, const int3 &dst, const EPathfindingLayer &layer) const +bool CPathsInfo::getPath(CGPath &out, const int3 &dst, const ELayer &layer) const { boost::unique_lock pathLock(pathMx); @@ -669,7 +669,7 @@ bool CPathsInfo::getPath(CGPath &out, const int3 &dst, const EPathfindingLayer & return true; } -int CPathsInfo::getDistance(const int3 &tile, const EPathfindingLayer &layer) const +int CPathsInfo::getDistance(const int3 &tile, const ELayer &layer) const { boost::unique_lock pathLock(pathMx); @@ -680,14 +680,14 @@ int CPathsInfo::getDistance(const int3 &tile, const EPathfindingLayer &layer) co return 255; } -CGPathNode *CPathsInfo::getNode(const int3 &coord, const EPathfindingLayer &layer) const +CGPathNode *CPathsInfo::getNode(const int3 &coord, const ELayer &layer) const { - if(layer != EPathfindingLayer::AUTO) + if(layer != ELayer::AUTO) return nodes[coord.x][coord.y][coord.z][layer]; - auto landNode = nodes[coord.x][coord.y][coord.z][EPathfindingLayer::LAND]; + auto landNode = nodes[coord.x][coord.y][coord.z][ELayer::LAND]; if(landNode->theNodeBefore) return landNode; else - return nodes[coord.x][coord.y][coord.z][EPathfindingLayer::SAIL]; + return nodes[coord.x][coord.y][coord.z][ELayer::SAIL]; } diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 209e89493..0f748f4fe 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -23,6 +23,8 @@ struct TerrainTile; struct DLL_LINKAGE CGPathNode { + typedef EPathfindingLayer ELayer; + enum ENodeAction { UNKNOWN = -1, @@ -49,10 +51,10 @@ struct DLL_LINKAGE CGPathNode ui32 moveRemains; //remaining tiles after hero reaches the tile CGPathNode * theNodeBefore; int3 coord; //coordinates - EPathfindingLayer layer; + ELayer layer; ENodeAction action; - CGPathNode(int3 Coord, EPathfindingLayer Layer); + CGPathNode(int3 Coord, ELayer Layer); void reset(); bool reachable() const; }; @@ -68,6 +70,8 @@ struct DLL_LINKAGE CGPath struct DLL_LINKAGE CPathsInfo { + typedef EPathfindingLayer ELayer; + mutable boost::mutex pathMx; const CGHeroInstance *hero; @@ -77,10 +81,10 @@ struct DLL_LINKAGE CPathsInfo CPathsInfo(const int3 &Sizes); ~CPathsInfo(); - const CGPathNode * getPathInfo(const int3 &tile, const EPathfindingLayer &layer = EPathfindingLayer::AUTO) const; - bool getPath(CGPath &out, const int3 &dst, const EPathfindingLayer &layer = EPathfindingLayer::AUTO) const; - int getDistance(const int3 &tile, const EPathfindingLayer &layer = EPathfindingLayer::AUTO) const; - CGPathNode *getNode(const int3 &coord, const EPathfindingLayer &layer) const; + const CGPathNode * getPathInfo(const int3 &tile, const ELayer &layer = ELayer::AUTO) const; + bool getPath(CGPath &out, const int3 &dst, const ELayer &layer = ELayer::AUTO) const; + int getDistance(const int3 &tile, const ELayer &layer = ELayer::AUTO) const; + CGPathNode *getNode(const int3 &coord, const ELayer &layer) const; }; class CPathfinder : private CGameInfoCallback @@ -90,6 +94,8 @@ public: void calculatePaths(); //calculates possible paths for hero, uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists private: + typedef EPathfindingLayer ELayer; + struct PathfinderOptions { bool useFlying; From be37e1cd8bb380a8b29b5c386d59046d930b64ce Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sun, 8 Nov 2015 08:39:00 +0300 Subject: [PATCH 32/83] CPathfinder: add const to appropriate methods --- lib/CPathfinder.cpp | 16 ++++++++-------- lib/CPathfinder.h | 14 +++++++------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 1bd34d30d..427f12dfd 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -255,7 +255,7 @@ void CPathfinder::addTeleportExits(bool noTeleportExcludes) } } -bool CPathfinder::isLayerTransitionPossible() +bool CPathfinder::isLayerTransitionPossible() const { if((cp->layer == ELayer::AIR || cp->layer == ELayer::WATER) && dp->layer != ELayer::LAND) @@ -379,7 +379,7 @@ bool CPathfinder::isMovementToDestPossible() return true; } -bool CPathfinder::isMovementAfterDestPossible() +bool CPathfinder::isMovementAfterDestPossible() const { switch(destAction) { @@ -411,17 +411,17 @@ bool CPathfinder::isMovementAfterDestPossible() return false; } -bool CPathfinder::isSourceInitialPosition() +bool CPathfinder::isSourceInitialPosition() const { return cp->coord == out.hpos; } -int3 CPathfinder::getSourceGuardPosition() +int3 CPathfinder::getSourceGuardPosition() const { return gs->map->guardingCreaturePositions[cp->coord.x][cp->coord.y][cp->coord.z]; } -bool CPathfinder::isSourceGuarded() +bool CPathfinder::isSourceGuarded() const { //map can start with hero on guarded tile or teleport there using dimension door //so threat tile hero standing on like it's not guarded because it's should be possible to move out of here @@ -429,7 +429,7 @@ bool CPathfinder::isSourceGuarded() { //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 - || !cp->theNodeBefore->layer != ELayer::LAND + || cp->theNodeBefore->layer == ELayer::LAND || ct->topVisitableId() != Obj::BOAT) { return true; @@ -439,7 +439,7 @@ bool CPathfinder::isSourceGuarded() return false; } -bool CPathfinder::isDestinationGuarded(bool ignoreAccessibility) +bool CPathfinder::isDestinationGuarded(const bool ignoreAccessibility) const { if(gs->map->guardingCreaturePositions[dp->coord.x][dp->coord.y][dp->coord.z].valid() && (ignoreAccessibility || dp->accessible == CGPathNode::BLOCKVIS)) @@ -450,7 +450,7 @@ bool CPathfinder::isDestinationGuarded(bool ignoreAccessibility) return false; } -bool CPathfinder::isDestinationGuardian() +bool CPathfinder::isDestinationGuardian() const { return getSourceGuardPosition() == dp->coord; } diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 0f748f4fe..e3946a6d9 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -149,15 +149,15 @@ private: void addNeighbours(const int3 &coord); void addTeleportExits(bool noTeleportExcludes = false); - bool isLayerTransitionPossible(); + bool isLayerTransitionPossible() const; bool isMovementToDestPossible(); - bool isMovementAfterDestPossible(); + bool isMovementAfterDestPossible() const; - bool isSourceInitialPosition(); - int3 getSourceGuardPosition(); - bool isSourceGuarded(); - bool isDestinationGuarded(bool ignoreAccessibility = true); - bool isDestinationGuardian(); + bool isSourceInitialPosition() const; + int3 getSourceGuardPosition() const; + bool isSourceGuarded() const; + bool isDestinationGuarded(const bool ignoreAccessibility = true) const; + bool isDestinationGuardian() const; void initializeGraph(); From 9cf35d1bfd8110502c4653697281c3b8ee6b5b49 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sun, 8 Nov 2015 10:06:24 +0300 Subject: [PATCH 33/83] CPathfinder: support for Castle Gate No support for client / server implemented yet. --- lib/CPathfinder.cpp | 21 ++++++++++++++++++++- lib/CPathfinder.h | 5 +++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 427f12dfd..732261152 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -28,6 +28,8 @@ CPathfinder::PathfinderOptions::PathfinderOptions() useTeleportOneWayRandom = false; useTeleportWhirlpool = false; + useCastleGate = false; + lightweightFlyingMode = false; oneTurnSpecialLayersLimit = true; } @@ -253,6 +255,23 @@ void CPathfinder::addTeleportExits(bool noTeleportExcludes) neighbours.push_back(obj->visitablePos()); } } + + if(options.useCastleGate + && (sTileObj->ID == Obj::TOWN && sTileObj->subID == ETownType::INFERNO + && getPlayerRelations(hero->tempOwner, sTileObj->tempOwner) != PlayerRelations::ENEMIES)) + { + /// TODO: Find way to reuse CPlayerSpecificInfoCallback::getTownsInfo + /// This may be handy if we allow to use teleportation to friendly towns + auto towns = gs->getPlayer(hero->tempOwner)->towns; + for(const auto & town : towns) + { + if(town->id != sTileObj->id && town->visitingHero == nullptr + && town->hasBuilt(BuildingID::CASTLE_GATE, ETownType::INFERNO)) + { + neighbours.push_back(town->visitablePos()); + } + } + } } bool CPathfinder::isLayerTransitionPossible() const @@ -364,7 +383,7 @@ bool CPathfinder::isMovementToDestPossible() } else if(isDestinationGuardian()) destAction = CGPathNode::BATTLE; - else if(obj->blockVisit) + else if(obj->blockVisit && (!options.useCastleGate || obj->ID != Obj::TOWN)) destAction = CGPathNode::BLOCKING_VISIT; diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index e3946a6d9..e6c346dee 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -106,6 +106,11 @@ private: bool useTeleportOneWayRandom; // One-way monoliths with more than one known exit bool useTeleportWhirlpool; // Force enabled if hero protected or unaffected (have one stack of one creature) + /// TODO: Find out with client and server code, merge with normal teleporters. + /// Likely proper implementation would require some refactoring of CGTeleport. + /// So for now this is unfinished and disabled by default. + bool useCastleGate; + /// If true transition into air layer only possible from initial node. /// This is drastically decrease path calculation complexity (and time). /// Downside is less MP effective paths calculation. From f376b2799941638fc3ac8f0ea4cb57da7d38ae12 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sun, 8 Nov 2015 19:19:48 +0300 Subject: [PATCH 34/83] Pathfinding: do path calculation in separate thread --- lib/CGameState.cpp | 5 ++++- lib/CGameState.h | 2 ++ lib/CPathfinder.cpp | 21 +++++++++++++++++++++ lib/CPathfinder.h | 1 + 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 3b4217ac5..575601fce 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -2164,8 +2164,11 @@ void CGameState::apply(CPack *pack) void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out) { + if(pathfinderWorking) + pathfinderWorking->interrupt(); + CPathfinder pathfinder(out, this, hero); - pathfinder.calculatePaths(); + pathfinderWorking = make_unique(&CPathfinder::startPathfinder, pathfinder); } /** diff --git a/lib/CGameState.h b/lib/CGameState.h index 6702616bf..d0cd983ab 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -314,6 +314,8 @@ public: boost::shared_mutex *mx; + unique_ptr pathfinderWorking; + void giveHeroArtifact(CGHeroInstance *h, ArtifactID aid); void apply(CPack *pack); diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 732261152..6fa558eaf 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -7,6 +7,7 @@ #include "mapObjects/CGHeroInstance.h" #include "GameConstants.h" #include "CStopWatch.h" +#include "CThreadHelper.h" /* * CPathfinder.cpp, part of VCMI engine @@ -59,6 +60,24 @@ CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance neighbours.reserve(16); } +void CPathfinder::startPathfinder() +{ + try + { + setThreadName("CPathfinder::startPathfinder"); + + calculatePaths(); + } + catch(boost::thread_interrupted &e) + { + gs->pathfinderWorking.reset(); + return; + } + + gs->pathfinderWorking.reset(); +} + + void CPathfinder::calculatePaths() { int maxMovePointsLand = hero->maxMovePoints(true); @@ -192,6 +211,8 @@ void CPathfinder::calculatePaths() } } } + + boost::this_thread::interruption_point(); } //queue loop } diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index e6c346dee..0238110cb 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -91,6 +91,7 @@ class CPathfinder : private CGameInfoCallback { public: CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero); + void startPathfinder(); void calculatePaths(); //calculates possible paths for hero, uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists private: From 45e4cf354ef1d5a7fc2a5468db2cccdc6612356f Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Mon, 9 Nov 2015 09:35:56 +0300 Subject: [PATCH 35/83] Revert "Pathfinding: do path calculation in separate thread" This reverts commit f376b2799941638fc3ac8f0ea4cb57da7d38ae12. --- lib/CGameState.cpp | 5 +---- lib/CGameState.h | 2 -- lib/CPathfinder.cpp | 21 --------------------- lib/CPathfinder.h | 1 - 4 files changed, 1 insertion(+), 28 deletions(-) diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 575601fce..3b4217ac5 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -2164,11 +2164,8 @@ void CGameState::apply(CPack *pack) void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out) { - if(pathfinderWorking) - pathfinderWorking->interrupt(); - CPathfinder pathfinder(out, this, hero); - pathfinderWorking = make_unique(&CPathfinder::startPathfinder, pathfinder); + pathfinder.calculatePaths(); } /** diff --git a/lib/CGameState.h b/lib/CGameState.h index d0cd983ab..6702616bf 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -314,8 +314,6 @@ public: boost::shared_mutex *mx; - unique_ptr pathfinderWorking; - void giveHeroArtifact(CGHeroInstance *h, ArtifactID aid); void apply(CPack *pack); diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 6fa558eaf..732261152 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -7,7 +7,6 @@ #include "mapObjects/CGHeroInstance.h" #include "GameConstants.h" #include "CStopWatch.h" -#include "CThreadHelper.h" /* * CPathfinder.cpp, part of VCMI engine @@ -60,24 +59,6 @@ CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance neighbours.reserve(16); } -void CPathfinder::startPathfinder() -{ - try - { - setThreadName("CPathfinder::startPathfinder"); - - calculatePaths(); - } - catch(boost::thread_interrupted &e) - { - gs->pathfinderWorking.reset(); - return; - } - - gs->pathfinderWorking.reset(); -} - - void CPathfinder::calculatePaths() { int maxMovePointsLand = hero->maxMovePoints(true); @@ -211,8 +192,6 @@ void CPathfinder::calculatePaths() } } } - - boost::this_thread::interruption_point(); } //queue loop } diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 0238110cb..e6c346dee 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -91,7 +91,6 @@ class CPathfinder : private CGameInfoCallback { public: CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero); - void startPathfinder(); void calculatePaths(); //calculates possible paths for hero, uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists private: From d3c8ca7c1c3e2c526a3bf02f37a80ca989b49fca Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Mon, 9 Nov 2015 19:57:26 +0300 Subject: [PATCH 36/83] Pathfinding: implement duration checking for fly and water walking Now pathfinder take into account different bonuses for different tuns. So if you only have FLYING_MOVEMENT bonus from Fly spell for one turn then pathfinder will only let you use air layer within one turn only. That work for cost calculations too. Let's say you have two bonuses: - FLYING_MOVEMENT with 20% penalty for next 2 turns - FLYING_MOVEMENT with 40% penalty for 5 turns Now pathfinder using correct penalty for each turn so movements in air layer going to be more expensive on 3-5 turns. --- lib/CGameState.cpp | 16 +++++++++------- lib/CGameState.h | 2 +- lib/CPathfinder.cpp | 26 ++++++++++++++++++++++++-- lib/CPathfinder.h | 1 + lib/mapObjects/CGHeroInstance.cpp | 13 ++++--------- lib/mapObjects/CGHeroInstance.h | 5 ++--- server/CGameHandler.cpp | 10 ++++++---- 7 files changed, 47 insertions(+), 26 deletions(-) diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 57bb16597..db3bd9fbd 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -2100,7 +2100,7 @@ void CGameState::getNeighbours(const TerrainTile &srct, int3 tile, std::vectorgetTile(dest); //get basic cost - int ret = h->getTileCost(d,s); + int ret = h->getTileCost(d, s, turn); - if(d.blocked && h->canFly()) + auto flyBonus = h->getBonusAtTurn(Bonus::FLYING_MOVEMENT, turn); + auto waterWalkingBonus = h->getBonusAtTurn(Bonus::WATER_WALKING, turn); + if(d.blocked && flyBonus) { - ret *= (100.0 + h->valOfBonuses(Bonus::FLYING_MOVEMENT)) / 100.0; + ret *= (100.0 + flyBonus->val) / 100.0; } else if(d.terType == ETerrainType::WATER) { if(h->boat && s.hasFavourableWinds() && d.hasFavourableWinds()) //Favourable Winds ret *= 0.666; - else if(!h->boat && h->canWalkOnSea()) + else if(!h->boat && waterWalkingBonus) { - ret *= (100.0 + h->valOfBonuses(Bonus::WATER_WALKING)) / 100.0; + ret *= (100.0 + waterWalkingBonus->val) / 100.0; } } @@ -2145,7 +2147,7 @@ int CGameState::getMovementCost(const CGHeroInstance *h, const int3 &src, const getNeighbours(d, dest, vec, s.terType != ETerrainType::WATER, true); for(auto & elem : vec) { - int fcost = getMovementCost(h, dest, elem, left, false); + int fcost = getMovementCost(h, dest, elem, left, turn, false); if(fcost <= left) { return ret; diff --git a/lib/CGameState.h b/lib/CGameState.h index 6702616bf..6f03191a0 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -340,7 +340,7 @@ public: bool isVisible(const CGObjectInstance *obj, boost::optional player); void getNeighbours(const TerrainTile &srct, int3 tile, std::vector &vec, const boost::logic::tribool &onLand, bool limitCoastSailing); - int getMovementCost(const CGHeroInstance *h, const int3 &src, const int3 &dest, int remainingMovePoints=-1, bool checkLast=true); + int getMovementCost(const CGHeroInstance * h, const int3 &src, const int3 &dest, int remainingMovePoints =- 1, const int &turn = 0, bool checkLast = true); int getDate(Date::EDateType mode=Date::DAY) const; //mode=0 - total days in game, mode=1 - day of week, mode=2 - current week, mode=3 - current month // ----- getters, setters ----- diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 732261152..9f8a1b4a1 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -48,9 +48,9 @@ CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance throw std::runtime_error("Wrong checksum"); } - if(hero->canFly()) + if(hero->getBonusAtTurn(Bonus::FLYING_MOVEMENT)) options.useFlying = true; - if(hero->canWalkOnSea()) + if(hero->getBonusAtTurn(Bonus::WATER_WALKING)) options.useWaterWalking = true; if(CGWhirlpool::isProtected(hero)) options.useTeleportWhirlpool = true; @@ -132,6 +132,9 @@ void CPathfinder::calculatePaths() if(!passOneTurnLimitCheck(cp->turns != turn)) continue; + if(!isLayerAvailable(i, turn)) + continue; + if(cp->layer != i && !isLayerTransitionPossible()) continue; @@ -274,6 +277,25 @@ void CPathfinder::addTeleportExits(bool noTeleportExcludes) } } +bool CPathfinder::isLayerAvailable(const ELayer &layer, const int &turn) const +{ + switch(layer) + { + case ELayer::AIR: + if(!hero->getBonusAtTurn(Bonus::FLYING_MOVEMENT, turn)) + return false; + + break; + + case ELayer::WATER: + if(!hero->getBonusAtTurn(Bonus::WATER_WALKING, turn)) + return false; + break; + } + + return true; +} + bool CPathfinder::isLayerTransitionPossible() const { if((cp->layer == ELayer::AIR || cp->layer == ELayer::WATER) diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index e6c346dee..06b4d993e 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -154,6 +154,7 @@ private: void addNeighbours(const int3 &coord); void addTeleportExits(bool noTeleportExcludes = false); + bool isLayerAvailable(const ELayer &layer, const int &turn) const; bool isLayerTransitionPossible() const; bool isMovementToDestPossible(); bool isMovementAfterDestPossible() const; diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index c50055bcc..71bcf089e 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -56,7 +56,7 @@ static int lowestSpeed(const CGHeroInstance * chi) return ret; } -ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &from) const +ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &from, const int &turn) const { unsigned ret = GameConstants::BASE_MOVEMENT_COST; @@ -80,7 +80,7 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &fro break; } } - else if(!hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType)) + else if(!getBonusAtTurn(Bonus::NO_TERRAIN_PENALTY, turn, from.terType)) { // NOTE: in H3 neutral stacks will ignore terrain penalty only if placed as topmost stack(s) in hero army. // This is clearly bug in H3 however intended behaviour is not clear. @@ -129,14 +129,9 @@ int3 CGHeroInstance::getPosition(bool h3m) const //h3m=true - returns position o } } -bool CGHeroInstance::canFly() const +const Bonus * CGHeroInstance::getBonusAtTurn(const Bonus::BonusType &type, const int &turn, const TBonusSubtype &subType) const { - return hasBonusOfType(Bonus::FLYING_MOVEMENT); -} - -bool CGHeroInstance::canWalkOnSea() const -{ - return hasBonusOfType(Bonus::WATER_WALKING); + return getBonus(Selector::type(type).And(Selector::days(turn)).And(Selector::subtype(subType))); } ui8 CGHeroInstance::getSecSkillLevel(SecondarySkill skill) const diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index c8338fb79..d05b035df 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -129,12 +129,11 @@ public: EAlignment::EAlignment getAlignment() const; const std::string &getBiography() const; bool needsLastStack()const override; - ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling + ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from, const int &turn) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling ui32 getLowestCreatureSpeed() const; int3 getPosition(bool h3m = false) const; //h3m=true - returns position of hero object; h3m=false - returns position of hero 'manifestation' si32 manaRegain() const; //how many points of mana can hero regain "naturally" in one day - bool canFly() const; - bool canWalkOnSea() const; + const Bonus * getBonusAtTurn(const Bonus::BonusType &type, const int &turn = 0, const TBonusSubtype &subType = -1) const; int getCurrentLuck(int stack=-1, bool town=false) const; int getSpellCost(const CSpell *sp) const; //do not use during battles -> bonuses from army would be ignored diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 8971a4c8d..ea2ca3310 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1779,12 +1779,14 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo tmh.movePoints = h->movement; //check if destination tile is available + bool canFly = h->getBonusAtTurn(Bonus::FLYING_MOVEMENT); + bool canWalkOnSea = h->getBonusAtTurn(Bonus::WATER_WALKING); //it's a rock or blocked and not visitable tile //OR hero is on land and dest is water and (there is not present only one object - boat) - if(((t.terType == ETerrainType::ROCK || (t.blocked && !t.visitable && !h->canFly() )) + if(((t.terType == ETerrainType::ROCK || (t.blocked && !t.visitable && !canFly)) && complain("Cannot move hero, destination tile is blocked!")) - || ((!h->boat && !h->canWalkOnSea() && !h->canFly() && t.terType == ETerrainType::WATER && (t.visitableObjects.size() < 1 || (t.visitableObjects.back()->ID != Obj::BOAT && t.visitableObjects.back()->ID != Obj::HERO))) //hero is not on boat/water walking and dst water tile doesn't contain boat/hero (objs visitable from land) -> we test back cause boat may be on top of another object (#276) + || ((!h->boat && !canWalkOnSea && !canFly && t.terType == ETerrainType::WATER && (t.visitableObjects.size() < 1 || (t.visitableObjects.back()->ID != Obj::BOAT && t.visitableObjects.back()->ID != Obj::HERO))) //hero is not on boat/water walking and dst water tile doesn't contain boat/hero (objs visitable from land) -> we test back cause boat may be on top of another object (#276) && complain("Cannot move hero, destination tile is on water!")) || ((h->boat && t.terType != ETerrainType::WATER && t.blocked) && complain("Cannot disembark hero, tile is blocked!")) @@ -1794,7 +1796,7 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo && complain("Can not move garrisoned hero!")) || ((h->movement < cost && dst != h->pos && !teleporting) && complain("Hero doesn't have any movement points left!")) - || ((transit && !h->canFly() && !CGTeleport::isTeleport(t.topVisitableObj())) + || ((transit && !canFly && !CGTeleport::isTeleport(t.topVisitableObj())) && complain("Hero cannot transit over this tile!")) /*|| (states.checkFlag(h->tempOwner, &PlayerStatus::engagedIntoBattle) && complain("Cannot move hero during the battle"))*/) @@ -1913,7 +1915,7 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo if(CGTeleport::isTeleport(t.topVisitableObj())) visitDest = DONT_VISIT_DEST; - if(h->canFly()) + if(canFly) { lookForGuards = IGNORE_GUARDS; visitDest = DONT_VISIT_DEST; From b2e1ee53634de5ed0a3ba07d11b67b30d552c13e Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Tue, 10 Nov 2015 02:15:27 +0300 Subject: [PATCH 37/83] CGameState: move two pathfinding-related functions to CPathfinderHelper Both getMovementCost and getNeighbours have nothing to do with gamestate. --- AI/VCAI/Fuzzy.cpp | 4 +- CCallback.cpp | 5 -- CCallback.h | 1 - lib/CGameState.cpp | 97 -------------------------------------- lib/CGameState.h | 2 - lib/CPathfinder.cpp | 100 ++++++++++++++++++++++++++++++++++++++-- lib/CPathfinder.h | 9 ++++ server/CGameHandler.cpp | 6 +-- 8 files changed, 111 insertions(+), 113 deletions(-) diff --git a/AI/VCAI/Fuzzy.cpp b/AI/VCAI/Fuzzy.cpp index f91b61963..04cf3d22a 100644 --- a/AI/VCAI/Fuzzy.cpp +++ b/AI/VCAI/Fuzzy.cpp @@ -421,7 +421,7 @@ float FuzzyHelper::evaluate (Goals::VisitTile & g) //assert(cb->isInTheMap(g.tile)); float turns = 0; - float distance = cb->getMovementCost(g.hero.h, g.tile); + float distance = CPathfinderHelper::getCost(g.hero.h, g.tile); if (!distance) //we stand on that tile turns = 0; else @@ -530,4 +530,4 @@ float FuzzyHelper::evaluate (Goals::AbstractGoal & g) void FuzzyHelper::setPriority (Goals::TSubgoal & g) { g->setpriority(g->accept(this)); //this enforces returned value is set -} \ No newline at end of file +} diff --git a/CCallback.cpp b/CCallback.cpp index 605dd2afa..e48197d63 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -290,11 +290,6 @@ bool CCallback::canMoveBetween(const int3 &a, const int3 &b) return gs->checkForVisitableDir(a, b) && gs->checkForVisitableDir(b, a); } -int CCallback::getMovementCost(const CGHeroInstance * hero, int3 dest) -{ - return gs->getMovementCost(hero, hero->visitablePos(), dest, hero->movement); -} - const CPathsInfo * CCallback::getPathsInfo(const CGHeroInstance *h) { return cl->getPathsInfo(h); diff --git a/CCallback.h b/CCallback.h index 73167a1b3..46afc2adf 100644 --- a/CCallback.h +++ b/CCallback.h @@ -104,7 +104,6 @@ public: //client-specific functionalities (pathfinding) virtual bool canMoveBetween(const int3 &a, const int3 &b); - virtual int getMovementCost(const CGHeroInstance * hero, int3 dest); virtual int3 getGuardingCreaturePosition(int3 tile); virtual const CPathsInfo * getPathsInfo(const CGHeroInstance *h); diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index db3bd9fbd..411628370 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -2061,103 +2061,6 @@ PlayerRelations::PlayerRelations CGameState::getPlayerRelations( PlayerColor col return PlayerRelations::ENEMIES; } -void CGameState::getNeighbours(const TerrainTile &srct, int3 tile, std::vector &vec, const boost::logic::tribool &onLand, bool limitCoastSailing) -{ - 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.reserve(8); //optimization - for (auto & dir : dirs) - { - const int3 hlp = tile + dir; - if(!map->isInTheMap(hlp)) - continue; - - 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; -// } - - if(srct.terType == ETerrainType::WATER && limitCoastSailing && hlpt.terType == ETerrainType::WATER && dir.x && dir.y) //diagonal move through water - { - int3 hlp1 = tile, - hlp2 = tile; - hlp1.x += dir.x; - hlp2.y += dir.y; - - if(map->getTile(hlp1).terType != ETerrainType::WATER || map->getTile(hlp2).terType != ETerrainType::WATER) - continue; - } - - if((indeterminate(onLand) || onLand == (hlpt.terType!=ETerrainType::WATER) ) - && hlpt.terType != ETerrainType::ROCK) - { - vec.push_back(hlp); - } - } -} - -int CGameState::getMovementCost(const CGHeroInstance *h, const int3 &src, const int3 &dest, int remainingMovePoints, const int &turn, bool checkLast) -{ - if(src == dest) //same tile - return 0; - - TerrainTile &s = map->getTile(src), - &d = map->getTile(dest); - - //get basic cost - int ret = h->getTileCost(d, s, turn); - - auto flyBonus = h->getBonusAtTurn(Bonus::FLYING_MOVEMENT, turn); - auto waterWalkingBonus = h->getBonusAtTurn(Bonus::WATER_WALKING, turn); - if(d.blocked && flyBonus) - { - ret *= (100.0 + flyBonus->val) / 100.0; - } - else if(d.terType == ETerrainType::WATER) - { - if(h->boat && s.hasFavourableWinds() && d.hasFavourableWinds()) //Favourable Winds - ret *= 0.666; - else if(!h->boat && waterWalkingBonus) - { - ret *= (100.0 + waterWalkingBonus->val) / 100.0; - } - } - - if(src.x != dest.x && src.y != dest.y) //it's diagonal move - { - int old = ret; - ret *= 1.414213; - //diagonal move costs too much but normal move is possible - allow diagonal move for remaining move points - if(ret > remainingMovePoints && remainingMovePoints >= old) - { - return remainingMovePoints; - } - } - - - int left = remainingMovePoints-ret; - if(checkLast && left > 0 && remainingMovePoints-ret < 250) //it might be the last tile - if no further move possible we take all move points - { - std::vector vec; - vec.reserve(8); //optimization - getNeighbours(d, dest, vec, s.terType != ETerrainType::WATER, true); - for(auto & elem : vec) - { - int fcost = getMovementCost(h, dest, elem, left, turn, false); - if(fcost <= left) - { - return ret; - } - } - ret = remainingMovePoints; - } - return ret; -} - void CGameState::apply(CPack *pack) { ui16 typ = typeList.getTypeID(pack); diff --git a/lib/CGameState.h b/lib/CGameState.h index 6f03191a0..c60a9aa34 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -339,8 +339,6 @@ public: bool isVisible(int3 pos, PlayerColor player); bool isVisible(const CGObjectInstance *obj, boost::optional player); - void getNeighbours(const TerrainTile &srct, int3 tile, std::vector &vec, const boost::logic::tribool &onLand, bool limitCoastSailing); - int getMovementCost(const CGHeroInstance * h, const int3 &src, const int3 &dest, int remainingMovePoints =- 1, const int &turn = 0, bool checkLast = true); int getDate(Date::EDateType mode=Date::DAY) const; //mode=0 - total days in game, mode=1 - day of week, mode=2 - current week, mode=3 - current month // ----- getters, setters ----- diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 9f8a1b4a1..b62c8b858 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -143,7 +143,7 @@ void CPathfinder::calculatePaths() if(!isMovementToDestPossible()) continue; - int cost = gs->getMovementCost(hero, cp->coord, dp->coord, movement); + int cost = CPathfinderHelper::getCost(hero, cp->coord, dp->coord, movement); int remains = movement - cost; if(destAction == CGPathNode::EMBARK || destAction == CGPathNode::DISEMBARK) { @@ -156,7 +156,7 @@ void CPathfinder::calculatePaths() //occurs rarely, when hero with low movepoints tries to leave the road turnAtNextTile++; int moveAtNextTile = maxMovePoints(cp); - cost = gs->getMovementCost(hero, cp->coord, dp->coord, moveAtNextTile); //cost must be updated, movement points changed :( + cost = CPathfinderHelper::getCost(hero, cp->coord, dp->coord, moveAtNextTile); //cost must be updated, movement points changed :( remains = moveAtNextTile - cost; } @@ -204,7 +204,7 @@ void CPathfinder::addNeighbours(const int3 &coord) ct = &gs->map->getTile(coord); std::vector tiles; - gs->getNeighbours(*ct, coord, tiles, boost::logic::indeterminate, cp->layer == ELayer::SAIL); // TODO: find out if we still need "limitCoastSailing" option + CPathfinderHelper::getNeighbours(gs, *ct, coord, tiles, boost::logic::indeterminate, cp->layer == ELayer::SAIL); // TODO: find out if we still need "limitCoastSailing" option sTileObj = ct->topVisitableObj(coord == out.hpos); if(canVisitObject()) { @@ -625,6 +625,100 @@ bool CPathfinder::canVisitObject() const return cp->layer == ELayer::LAND || cp->layer == ELayer::SAIL; } +void CPathfinderHelper::getNeighbours(CGameState * gs, const TerrainTile &srct, const int3 &tile, std::vector &vec, const boost::logic::tribool &onLand, const bool &limitCoastSailing) +{ + 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.reserve(8); //optimization + for (auto & dir : dirs) + { + const int3 hlp = tile + dir; + if(!gs->isInTheMap(hlp)) + continue; + + const TerrainTile &hlpt = gs->map->getTile(hlp); + +// //we cannot visit things from blocked tiles +// if(srct.blocked && !srct.visitable && hlpt.visitable && srct.blockingObjects.front()->ID != HEROI_TYPE) +// { +// continue; +// } + + if(srct.terType == ETerrainType::WATER && limitCoastSailing && hlpt.terType == ETerrainType::WATER && dir.x && dir.y) //diagonal move through water + { + int3 hlp1 = tile, + hlp2 = tile; + hlp1.x += dir.x; + hlp2.y += dir.y; + + if(gs->map->getTile(hlp1).terType != ETerrainType::WATER || gs->map->getTile(hlp2).terType != ETerrainType::WATER) + continue; + } + + if((indeterminate(onLand) || onLand == (hlpt.terType!=ETerrainType::WATER) ) + && hlpt.terType != ETerrainType::ROCK) + { + vec.push_back(hlp); + } + } +} + +int CPathfinderHelper::getCost(const CGHeroInstance * h, const int3 &src, const int3 &dst, const int &remainingMovePoints, const int &turn, const bool &checkLast) +{ + if(src == dst) //same tile + return 0; + + auto s = h->cb->getTile(src), d = h->cb->getTile(dst); + int ret = h->getTileCost(*d, *s, turn); + + auto flyBonus = h->getBonusAtTurn(Bonus::FLYING_MOVEMENT, turn); + auto waterWalkingBonus = h->getBonusAtTurn(Bonus::WATER_WALKING, turn); + if(d->blocked && flyBonus) + { + ret *= (100.0 + flyBonus->val) / 100.0; + } + else if(d->terType == ETerrainType::WATER) + { + if(h->boat && s->hasFavourableWinds() && d->hasFavourableWinds()) //Favourable Winds + ret *= 0.666; + else if(!h->boat && waterWalkingBonus) + { + ret *= (100.0 + waterWalkingBonus->val) / 100.0; + } + } + + if(src.x != dst.x && src.y != dst.y) //it's diagonal move + { + int old = ret; + ret *= 1.414213; + //diagonal move costs too much but normal move is possible - allow diagonal move for remaining move points + if(ret > remainingMovePoints && remainingMovePoints >= old) + return remainingMovePoints; + } + + int left = remainingMovePoints-ret; + if(checkLast && left > 0 && remainingMovePoints-ret < 250) //it might be the last tile - if no further move possible we take all move points + { + std::vector vec; + vec.reserve(8); //optimization + getNeighbours(h->cb->gameState(), *d, dst, vec, s->terType != ETerrainType::WATER, true); + for(auto & elem : vec) + { + int fcost = getCost(h, dst, elem, left, turn, false); + if(fcost <= left) + return ret; + } + ret = remainingMovePoints; + } + return ret; +} + +int CPathfinderHelper::getCost(const CGHeroInstance * h, const int3 &dst) +{ + return getCost(h, h->visitablePos(), dst, h->movement); +} + CGPathNode::CGPathNode(int3 Coord, ELayer Layer) : coord(Coord), layer(Layer) { diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 06b4d993e..cbf0c75dd 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -178,3 +178,12 @@ private: bool canVisitObject() const; }; + +class DLL_LINKAGE CPathfinderHelper +{ +public: + static void getNeighbours(CGameState * gs, const TerrainTile &srct, const int3 &tile, std::vector &vec, const boost::logic::tribool &onLand, const bool &limitCoastSailing); + + static int getCost(const CGHeroInstance * h, const int3 &src, const int3 &dst, const int &remainingMovePoints =- 1, const int &turn = 0, const bool &checkLast = true); + static int getCost(const CGHeroInstance * h, const int3 &dst); +}; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index ea2ca3310..008f72496 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1764,7 +1764,6 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo } const TerrainTile t = *gs->getTile(hmpos); - const int cost = gs->getMovementCost(h, h->getPosition(), hmpos, h->movement); const int3 guardPos = gs->guardingCreaturePosition(hmpos); const bool embarking = !h->boat && !t.visitableObjects.empty() && t.visitableObjects.back()->ID == Obj::BOAT; @@ -1779,8 +1778,9 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo tmh.movePoints = h->movement; //check if destination tile is available - bool canFly = h->getBonusAtTurn(Bonus::FLYING_MOVEMENT); - bool canWalkOnSea = h->getBonusAtTurn(Bonus::WATER_WALKING); + const bool canFly = h->getBonusAtTurn(Bonus::FLYING_MOVEMENT); + const bool canWalkOnSea = h->getBonusAtTurn(Bonus::WATER_WALKING); + const int cost = CPathfinderHelper::getCost(h, h->getPosition(), hmpos, h->movement); //it's a rock or blocked and not visitable tile //OR hero is on land and dest is water and (there is not present only one object - boat) From 2ef9d7c3ec6b2221cd8d15fe765a01a27223cb2a Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Tue, 10 Nov 2015 02:30:05 +0300 Subject: [PATCH 38/83] Rename getCost back to getMovementCost Initially wanter to name main class differently and back then getCost make sense. Then renamed class to CPathfinderHelper, but forgot to rename function back. --- AI/VCAI/Fuzzy.cpp | 2 +- lib/CPathfinder.cpp | 12 ++++++------ lib/CPathfinder.h | 4 ++-- server/CGameHandler.cpp | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/AI/VCAI/Fuzzy.cpp b/AI/VCAI/Fuzzy.cpp index 04cf3d22a..bc7b60cfe 100644 --- a/AI/VCAI/Fuzzy.cpp +++ b/AI/VCAI/Fuzzy.cpp @@ -421,7 +421,7 @@ float FuzzyHelper::evaluate (Goals::VisitTile & g) //assert(cb->isInTheMap(g.tile)); float turns = 0; - float distance = CPathfinderHelper::getCost(g.hero.h, g.tile); + float distance = CPathfinderHelper::getMovementCost(g.hero.h, g.tile); if (!distance) //we stand on that tile turns = 0; else diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index b62c8b858..e1150e886 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -143,7 +143,7 @@ void CPathfinder::calculatePaths() if(!isMovementToDestPossible()) continue; - int cost = CPathfinderHelper::getCost(hero, cp->coord, dp->coord, movement); + int cost = CPathfinderHelper::getMovementCost(hero, cp->coord, dp->coord, movement); int remains = movement - cost; if(destAction == CGPathNode::EMBARK || destAction == CGPathNode::DISEMBARK) { @@ -156,7 +156,7 @@ void CPathfinder::calculatePaths() //occurs rarely, when hero with low movepoints tries to leave the road turnAtNextTile++; int moveAtNextTile = maxMovePoints(cp); - cost = CPathfinderHelper::getCost(hero, cp->coord, dp->coord, moveAtNextTile); //cost must be updated, movement points changed :( + cost = CPathfinderHelper::getMovementCost(hero, cp->coord, dp->coord, moveAtNextTile); //cost must be updated, movement points changed :( remains = moveAtNextTile - cost; } @@ -664,7 +664,7 @@ void CPathfinderHelper::getNeighbours(CGameState * gs, const TerrainTile &srct, } } -int CPathfinderHelper::getCost(const CGHeroInstance * h, const int3 &src, const int3 &dst, const int &remainingMovePoints, const int &turn, const bool &checkLast) +int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 &src, const int3 &dst, const int &remainingMovePoints, const int &turn, const bool &checkLast) { if(src == dst) //same tile return 0; @@ -705,7 +705,7 @@ int CPathfinderHelper::getCost(const CGHeroInstance * h, const int3 &src, const getNeighbours(h->cb->gameState(), *d, dst, vec, s->terType != ETerrainType::WATER, true); for(auto & elem : vec) { - int fcost = getCost(h, dst, elem, left, turn, false); + int fcost = getMovementCost(h, dst, elem, left, turn, false); if(fcost <= left) return ret; } @@ -714,9 +714,9 @@ int CPathfinderHelper::getCost(const CGHeroInstance * h, const int3 &src, const return ret; } -int CPathfinderHelper::getCost(const CGHeroInstance * h, const int3 &dst) +int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 &dst) { - return getCost(h, h->visitablePos(), dst, h->movement); + return getMovementCost(h, h->visitablePos(), dst, h->movement); } CGPathNode::CGPathNode(int3 Coord, ELayer Layer) diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index cbf0c75dd..60d17132b 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -184,6 +184,6 @@ class DLL_LINKAGE CPathfinderHelper public: static void getNeighbours(CGameState * gs, const TerrainTile &srct, const int3 &tile, std::vector &vec, const boost::logic::tribool &onLand, const bool &limitCoastSailing); - static int getCost(const CGHeroInstance * h, const int3 &src, const int3 &dst, const int &remainingMovePoints =- 1, const int &turn = 0, const bool &checkLast = true); - static int getCost(const CGHeroInstance * h, const int3 &dst); + static int getMovementCost(const CGHeroInstance * h, const int3 &src, const int3 &dst, const int &remainingMovePoints =- 1, const int &turn = 0, const bool &checkLast = true); + static int getMovementCost(const CGHeroInstance * h, const int3 &dst); }; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 008f72496..9032cc181 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1780,7 +1780,7 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo //check if destination tile is available const bool canFly = h->getBonusAtTurn(Bonus::FLYING_MOVEMENT); const bool canWalkOnSea = h->getBonusAtTurn(Bonus::WATER_WALKING); - const int cost = CPathfinderHelper::getCost(h, h->getPosition(), hmpos, h->movement); + const int cost = CPathfinderHelper::getMovementCost(h, h->getPosition(), hmpos, h->movement); //it's a rock or blocked and not visitable tile //OR hero is on land and dest is water and (there is not present only one object - boat) From 9a63735c24d35b509a7ea50889cf11c9df55dcc5 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Tue, 10 Nov 2015 14:26:45 +0300 Subject: [PATCH 39/83] CPathfinderHelper: implement TurnInfo to avoid bonus re-checking TurnInfo contain information about what movement-related bonuses and MP hero going to have on certain turn. This way we can collect this information once for each turn so it's huge performance boost. --- lib/CPathfinder.cpp | 88 +++++++++++++++++++++---------- lib/CPathfinder.h | 24 ++++++++- lib/mapObjects/CGHeroInstance.cpp | 4 +- lib/mapObjects/CGHeroInstance.h | 3 +- 4 files changed, 87 insertions(+), 32 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index e1150e886..3df8c5757 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -48,9 +48,10 @@ CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance throw std::runtime_error("Wrong checksum"); } - if(hero->getBonusAtTurn(Bonus::FLYING_MOVEMENT)) + hlp = new CPathfinderHelper(hero); + if(hlp->ti->bonusFlying) options.useFlying = true; - if(hero->getBonusAtTurn(Bonus::WATER_WALKING)) + if(hlp->ti->bonusWaterWalking) options.useWaterWalking = true; if(CGWhirlpool::isProtected(hero)) options.useTeleportWhirlpool = true; @@ -61,14 +62,6 @@ CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance void CPathfinder::calculatePaths() { - int maxMovePointsLand = hero->maxMovePoints(true); - int maxMovePointsWater = hero->maxMovePoints(false); - - auto maxMovePoints = [&](CGPathNode *cp) -> int - { - return cp->layer == ELayer::SAIL ? maxMovePointsWater : maxMovePointsLand; - }; - auto passOneTurnLimitCheck = [&](bool shouldCheck) -> bool { if(options.oneTurnSpecialLayersLimit && shouldCheck) @@ -109,10 +102,11 @@ void CPathfinder::calculatePaths() cp->locked = true; int movement = cp->moveRemains, turn = cp->turns; + hlp->updateTurnInfo(turn); if(!movement) { - movement = maxMovePoints(cp); - turn++; + hlp->updateTurnInfo(++turn); + movement = hlp->getMaxMovePoints(cp->layer); } //add accessible neighbouring nodes to the queue @@ -143,7 +137,7 @@ void CPathfinder::calculatePaths() if(!isMovementToDestPossible()) continue; - int cost = CPathfinderHelper::getMovementCost(hero, cp->coord, dp->coord, movement); + int cost = CPathfinderHelper::getMovementCost(hero, cp->coord, dp->coord, movement, hlp->ti); int remains = movement - cost; if(destAction == CGPathNode::EMBARK || destAction == CGPathNode::DISEMBARK) { @@ -154,9 +148,9 @@ void CPathfinder::calculatePaths() if(remains < 0) { //occurs rarely, when hero with low movepoints tries to leave the road - turnAtNextTile++; - int moveAtNextTile = maxMovePoints(cp); - cost = CPathfinderHelper::getMovementCost(hero, cp->coord, dp->coord, moveAtNextTile); //cost must be updated, movement points changed :( + hlp->updateTurnInfo(++turnAtNextTile); + int moveAtNextTile = hlp->getMaxMovePoints(i); + cost = CPathfinderHelper::getMovementCost(hero, cp->coord, dp->coord, moveAtNextTile, hlp->ti); //cost must be updated, movement points changed :( remains = moveAtNextTile - cost; } @@ -282,13 +276,13 @@ bool CPathfinder::isLayerAvailable(const ELayer &layer, const int &turn) const switch(layer) { case ELayer::AIR: - if(!hero->getBonusAtTurn(Bonus::FLYING_MOVEMENT, turn)) + if(!hlp->ti->bonusFlying) return false; break; case ELayer::WATER: - if(!hero->getBonusAtTurn(Bonus::WATER_WALKING, turn)) + if(!hlp->ti->bonusWaterWalking) return false; break; } @@ -625,6 +619,43 @@ bool CPathfinder::canVisitObject() const return cp->layer == ELayer::LAND || cp->layer == ELayer::SAIL; } +CPathfinderHelper::CPathfinderHelper(const CGHeroInstance * Hero) + : ti(nullptr), hero(Hero) +{ + turnsInfo.reserve(16); + updateTurnInfo(); +} + +void CPathfinderHelper::updateTurnInfo(const int &turn) +{ + if(!ti || ti->turn != turn) + { + if(turn < turnsInfo.size()) + ti = turnsInfo[turn]; + else + { + ti = getTurnInfo(hero, turn); + turnsInfo.push_back(ti); + } + } +} + +int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer &layer) const +{ + return layer == EPathfindingLayer::SAIL ? ti->maxMovePointsWater : ti->maxMovePointsLand; +} + +TurnInfo * CPathfinderHelper::getTurnInfo(const CGHeroInstance * h, const int &turn) +{ + auto turnInfo = new TurnInfo; + turnInfo->turn = turn; + turnInfo->maxMovePointsLand = h->maxMovePoints(true); + turnInfo->maxMovePointsWater = h->maxMovePoints(false); + turnInfo->bonusFlying = h->getBonusAtTurn(Bonus::FLYING_MOVEMENT, turn); + turnInfo->bonusWaterWalking = h->getBonusAtTurn(Bonus::WATER_WALKING, turn); + return turnInfo; +} + void CPathfinderHelper::getNeighbours(CGameState * gs, const TerrainTile &srct, const int3 &tile, std::vector &vec, const boost::logic::tribool &onLand, const bool &limitCoastSailing) { static const int3 dirs[] = { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0), @@ -664,27 +695,28 @@ void CPathfinderHelper::getNeighbours(CGameState * gs, const TerrainTile &srct, } } -int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 &src, const int3 &dst, const int &remainingMovePoints, const int &turn, const bool &checkLast) +int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 &src, const int3 &dst, const int &remainingMovePoints, const TurnInfo * ti, const bool &checkLast) { if(src == dst) //same tile return 0; - auto s = h->cb->getTile(src), d = h->cb->getTile(dst); - int ret = h->getTileCost(*d, *s, turn); + if(!ti) + ti = getTurnInfo(h); - auto flyBonus = h->getBonusAtTurn(Bonus::FLYING_MOVEMENT, turn); - auto waterWalkingBonus = h->getBonusAtTurn(Bonus::WATER_WALKING, turn); - if(d->blocked && flyBonus) + auto s = h->cb->getTile(src), d = h->cb->getTile(dst); + int ret = h->getTileCost(*d, *s, ti); + + if(d->blocked && ti->bonusFlying) { - ret *= (100.0 + flyBonus->val) / 100.0; + ret *= (100.0 + ti->bonusFlying->val) / 100.0; } else if(d->terType == ETerrainType::WATER) { if(h->boat && s->hasFavourableWinds() && d->hasFavourableWinds()) //Favourable Winds ret *= 0.666; - else if(!h->boat && waterWalkingBonus) + else if(!h->boat && ti->bonusWaterWalking) { - ret *= (100.0 + waterWalkingBonus->val) / 100.0; + ret *= (100.0 + ti->bonusWaterWalking->val) / 100.0; } } @@ -705,7 +737,7 @@ int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 &src getNeighbours(h->cb->gameState(), *d, dst, vec, s->terType != ETerrainType::WATER, true); for(auto & elem : vec) { - int fcost = getMovementCost(h, dst, elem, left, turn, false); + int fcost = getMovementCost(h, dst, elem, left, ti, false); if(fcost <= left) return ret; } diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 60d17132b..b774724b9 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -20,6 +20,7 @@ class CGHeroInstance; class CGObjectInstance; struct TerrainTile; +class CPathfinderHelper; struct DLL_LINKAGE CGPathNode { @@ -128,6 +129,7 @@ private: CPathsInfo &out; const CGHeroInstance *hero; + CPathfinderHelper * hlp; struct NodeComparer { @@ -179,11 +181,31 @@ private: }; +struct TurnInfo +{ + int turn; + int maxMovePointsLand; + int maxMovePointsWater; + const Bonus * bonusFlying; + const Bonus * bonusWaterWalking; +}; + class DLL_LINKAGE CPathfinderHelper { public: + TurnInfo * ti; + const CGHeroInstance * hero; + + CPathfinderHelper(const CGHeroInstance * Hero); + void updateTurnInfo(const int &turn = 0); + int getMaxMovePoints(const EPathfindingLayer &layer) const; + static TurnInfo * getTurnInfo(const CGHeroInstance * h, const int &turn = 0); + static void getNeighbours(CGameState * gs, const TerrainTile &srct, const int3 &tile, std::vector &vec, const boost::logic::tribool &onLand, const bool &limitCoastSailing); - static int getMovementCost(const CGHeroInstance * h, const int3 &src, const int3 &dst, const int &remainingMovePoints =- 1, const int &turn = 0, const bool &checkLast = true); + static int getMovementCost(const CGHeroInstance * h, const int3 &src, const int3 &dst, const int &remainingMovePoints =- 1, const TurnInfo * ti = nullptr, const bool &checkLast = true); static int getMovementCost(const CGHeroInstance * h, const int3 &dst); + +private: + std::vector turnsInfo; }; diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 71bcf089e..a031d080c 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -56,7 +56,7 @@ static int lowestSpeed(const CGHeroInstance * chi) return ret; } -ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &from, const int &turn) const +ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &from, const TurnInfo * ti) const { unsigned ret = GameConstants::BASE_MOVEMENT_COST; @@ -80,7 +80,7 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &fro break; } } - else if(!getBonusAtTurn(Bonus::NO_TERRAIN_PENALTY, turn, from.terType)) + else if(!getBonusAtTurn(Bonus::NO_TERRAIN_PENALTY, ti->turn, from.terType)) { // NOTE: in H3 neutral stacks will ignore terrain penalty only if placed as topmost stack(s) in hero army. // This is clearly bug in H3 however intended behaviour is not clear. diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index d05b035df..0c6056664 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -21,6 +21,7 @@ class CHero; class CGBoat; class CGTownInstance; struct TerrainTile; +struct TurnInfo; class CGHeroPlaceholder : public CGObjectInstance { @@ -129,7 +130,7 @@ public: EAlignment::EAlignment getAlignment() const; const std::string &getBiography() const; bool needsLastStack()const override; - ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from, const int &turn) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling + ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from, const TurnInfo * ti) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling ui32 getLowestCreatureSpeed() const; int3 getPosition(bool h3m = false) const; //h3m=true - returns position of hero object; h3m=false - returns position of hero 'manifestation' si32 manaRegain() const; //how many points of mana can hero regain "naturally" in one day From 2b6851b3d2de2fb79d45b43f120953f5a0858964 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Tue, 10 Nov 2015 19:15:59 +0300 Subject: [PATCH 40/83] Pathfinding: use unique_ptr for hlp and fix few rules --- lib/CPathfinder.cpp | 28 +++++++++++++++++++++++++--- lib/CPathfinder.h | 2 +- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 3df8c5757..f90c0164c 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -48,7 +48,7 @@ CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance throw std::runtime_error("Wrong checksum"); } - hlp = new CPathfinderHelper(hero); + hlp = make_unique(hero); if(hlp->ti->bonusFlying) options.useFlying = true; if(hlp->ti->bonusWaterWalking) @@ -292,11 +292,25 @@ bool CPathfinder::isLayerAvailable(const ELayer &layer, const int &turn) const bool CPathfinder::isLayerTransitionPossible() const { + /// No layer transition allowed when previous node action is BATTLE + if(cp->action == CGPathNode::BATTLE) + return false; + if((cp->layer == ELayer::AIR || cp->layer == ELayer::WATER) && dp->layer != ELayer::LAND) { + /// Hero that fly or walking on water can only go into ground layer return false; } + else if(cp->layer == ELayer::AIR && dp->layer == ELayer::LAND) + { + /// Hero that fly can only land on accessible tiles + if(cp->accessible != CGPathNode::ACCESSIBLE && + dp->accessible != CGPathNode::ACCESSIBLE) + { + return false; + } + } else if(cp->layer == ELayer::LAND && dp->layer == ELayer::AIR) { if(options.lightweightFlyingMode && !isSourceInitialPosition()) @@ -421,6 +435,8 @@ bool CPathfinder::isMovementAfterDestPossible() const /// TODO: Investigate what kind of limitation is possible to apply on movement from visitable tiles /// Likely in many cases we don't need to add visitable tile to queue when hero don't fly case CGPathNode::VISIT: + /// For now we only add visitable tile into queue when it's teleporter that allow transit + /// Movement from visitable tile when hero is standing on it is possible into any layer if(CGTeleport::isTeleport(dt->topVisitableObj())) { /// For now we'll always allow transit over teleporters @@ -430,7 +446,7 @@ bool CPathfinder::isMovementAfterDestPossible() const return true; } else - return true; + return false; case CGPathNode::NORMAL: return true; @@ -441,6 +457,11 @@ bool CPathfinder::isMovementAfterDestPossible() const case CGPathNode::DISEMBARK: if(options.useEmbarkAndDisembark && !isDestinationGuarded()) return true; + + case CGPathNode::BATTLE: + /// Movement after BATTLE action only possible to guardian tile + if(isDestinationGuarded()) + return true; } return false; @@ -498,6 +519,7 @@ void CPathfinder::initializeGraph() node->reset(); auto accessibility = evaluateAccessibility(pos, tinfo); + /// TODO: Probably this shouldn't be handled by initializeGraph if(blockNotAccessible && (accessibility != CGPathNode::ACCESSIBLE || tinfo->terType == ETerrainType::WATER)) accessibility = CGPathNode::BLOCKED; @@ -521,7 +543,7 @@ void CPathfinder::initializeGraph() if(options.useFlying) updateNode(pos, ELayer::AIR, tinfo, true); if(options.useWaterWalking) - updateNode(pos, ELayer::WATER, tinfo, true); + updateNode(pos, ELayer::WATER, tinfo, false); break; default: updateNode(pos, ELayer::LAND, tinfo, false); diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index b774724b9..8b757401c 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -129,7 +129,7 @@ private: CPathsInfo &out; const CGHeroInstance *hero; - CPathfinderHelper * hlp; + unique_ptr hlp; struct NodeComparer { From 4aaf6191a599c81d77ad771c0b2f343c63318a4c Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Tue, 10 Nov 2015 21:07:27 +0300 Subject: [PATCH 41/83] CPathfinder: implemented originalMovementRules option --- lib/CPathfinder.cpp | 34 +++++++++++++++++++++++++++------- lib/CPathfinder.h | 7 +++++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index f90c0164c..e7ee51658 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -32,6 +32,7 @@ CPathfinder::PathfinderOptions::PathfinderOptions() lightweightFlyingMode = false; oneTurnSpecialLayersLimit = true; + originalMovementRules = true; } CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero) @@ -304,10 +305,19 @@ bool CPathfinder::isLayerTransitionPossible() const } else if(cp->layer == ELayer::AIR && dp->layer == ELayer::LAND) { - /// Hero that fly can only land on accessible tiles - if(cp->accessible != CGPathNode::ACCESSIBLE && - dp->accessible != CGPathNode::ACCESSIBLE) + if(options.originalMovementRules) { + if ((cp->accessible != CGPathNode::ACCESSIBLE && + cp->accessible != CGPathNode::VISITABLE) && + (dp->accessible != CGPathNode::VISITABLE && + dp->accessible != CGPathNode::ACCESSIBLE)) + { + return false; + } + } + else if(cp->accessible != CGPathNode::ACCESSIBLE && dp->accessible != CGPathNode::ACCESSIBLE) + { + /// Hero that fly can only land on accessible tiles return false; } } @@ -344,9 +354,14 @@ bool CPathfinder::isMovementToDestPossible() case ELayer::LAND: if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED) return false; - if(isSourceGuarded() && !isDestinationGuardian()) // Can step into tile of guard - return false; - + if(isSourceGuarded()) + { + if(!(options.originalMovementRules && cp->layer == ELayer::AIR) && + !isDestinationGuardian()) // Can step into tile of guard + { + return false; + } + } if(cp->layer == ELayer::SAIL) destAction = CGPathNode::DISEMBARK; @@ -418,7 +433,12 @@ bool CPathfinder::isMovementToDestPossible() if(destAction == CGPathNode::NORMAL) - destAction = CGPathNode::VISIT; + { + if(options.originalMovementRules && isDestinationGuarded()) + destAction = CGPathNode::BATTLE; + else + destAction = CGPathNode::VISIT; + } } else if(isDestinationGuarded()) destAction = CGPathNode::BATTLE; diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 8b757401c..448024974 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -124,6 +124,13 @@ private: /// After all this limit should benefit performance on maps with tons of water or blocked tiles. bool oneTurnSpecialLayersLimit; + /// VCMI have different movement rules to solve flaws original engine has. + /// If this option enabled you'll able to do following things in fly: + /// - Move from blocked tiles to visitable one + /// - Move from guarded tiles to blockvis tiles without being attacked + /// - Move from guarded tiles to guarded visitable tiles with being attacked after + bool originalMovementRules; + PathfinderOptions(); } options; From f1311abd0bd1b9ded15099725c85b2a5f6e0ef3d Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Wed, 11 Nov 2015 19:51:08 +0300 Subject: [PATCH 42/83] CPathfinder: move node action code into getDestAction --- lib/CPathfinder.cpp | 147 +++++++++++++++++++++++--------------------- lib/CPathfinder.h | 5 +- 2 files changed, 79 insertions(+), 73 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index e7ee51658..227f26845 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -101,6 +101,8 @@ void CPathfinder::calculatePaths() cp = pq.top(); pq.pop(); cp->locked = true; + ct = &gs->map->getTile(cp->coord); + cObj = ct->topVisitableObj(cp->coord == out.hpos); int movement = cp->moveRemains, turn = cp->turns; hlp->updateTurnInfo(turn); @@ -115,6 +117,7 @@ void CPathfinder::calculatePaths() for(auto & neighbour : neighbours) { dt = &gs->map->getTile(neighbour); + dObj = dt->topVisitableObj(); for(ELayer i = ELayer::LAND; i <= ELayer::AIR; i.advance(1)) { dp = out.getNode(neighbour, i); @@ -133,11 +136,10 @@ void CPathfinder::calculatePaths() if(cp->layer != i && !isLayerTransitionPossible()) continue; - - destAction = CGPathNode::UNKNOWN; if(!isMovementToDestPossible()) continue; + destAction = getDestAction(); int cost = CPathfinderHelper::getMovementCost(hero, cp->coord, dp->coord, movement, hlp->ti); int remains = movement - cost; if(destAction == CGPathNode::EMBARK || destAction == CGPathNode::DISEMBARK) @@ -171,7 +173,7 @@ void CPathfinder::calculatePaths() } //neighbours loop //just add all passable teleport exits - if(sTileObj && canVisitObject()) + if(cObj && canVisitObject()) { addTeleportExits(); for(auto & neighbour : neighbours) @@ -196,18 +198,15 @@ void CPathfinder::calculatePaths() void CPathfinder::addNeighbours(const int3 &coord) { neighbours.clear(); - ct = &gs->map->getTile(coord); - std::vector tiles; CPathfinderHelper::getNeighbours(gs, *ct, coord, tiles, boost::logic::indeterminate, cp->layer == ELayer::SAIL); // TODO: find out if we still need "limitCoastSailing" option - sTileObj = ct->topVisitableObj(coord == out.hpos); if(canVisitObject()) { - if(sTileObj) + if(cObj) { for(int3 tile: tiles) { - if(canMoveBetween(tile, sTileObj->visitablePos())) + if(canMoveBetween(tile, cObj->visitablePos())) neighbours.push_back(tile); } } @@ -220,7 +219,7 @@ void CPathfinder::addNeighbours(const int3 &coord) void CPathfinder::addTeleportExits(bool noTeleportExcludes) { - assert(sTileObj); + assert(cObj); neighbours.clear(); auto isAllowedTeleportEntrance = [&](const CGTeleport * obj) -> bool @@ -243,7 +242,7 @@ void CPathfinder::addTeleportExits(bool noTeleportExcludes) return false; }; - const CGTeleport *sTileTeleport = dynamic_cast(sTileObj); + const CGTeleport *sTileTeleport = dynamic_cast(cObj); if(isAllowedTeleportEntrance(sTileTeleport)) { for(auto objId : gs->getTeleportChannelExits(sTileTeleport->channel, hero->tempOwner)) @@ -255,15 +254,15 @@ void CPathfinder::addTeleportExits(bool noTeleportExcludes) } if(options.useCastleGate - && (sTileObj->ID == Obj::TOWN && sTileObj->subID == ETownType::INFERNO - && getPlayerRelations(hero->tempOwner, sTileObj->tempOwner) != PlayerRelations::ENEMIES)) + && (cObj->ID == Obj::TOWN && cObj->subID == ETownType::INFERNO + && getPlayerRelations(hero->tempOwner, cObj->tempOwner) != PlayerRelations::ENEMIES)) { /// TODO: Find way to reuse CPlayerSpecificInfoCallback::getTownsInfo /// This may be handy if we allow to use teleportation to friendly towns auto towns = gs->getPlayer(hero->tempOwner)->towns; for(const auto & town : towns) { - if(town->id != sTileObj->id && town->visitingHero == nullptr + if(town->id != cObj->id && town->visitingHero == nullptr && town->hasBuilt(BuildingID::CASTLE_GATE, ETownType::INFERNO)) { neighbours.push_back(town->visitablePos()); @@ -346,7 +345,7 @@ bool CPathfinder::isLayerTransitionPossible() const return true; } -bool CPathfinder::isMovementToDestPossible() +bool CPathfinder::isMovementToDestPossible() const { auto obj = dt->topVisitableObj(); switch(dp->layer) @@ -362,10 +361,9 @@ bool CPathfinder::isMovementToDestPossible() return false; } } - if(cp->layer == ELayer::SAIL) - destAction = CGPathNode::DISEMBARK; break; + case ELayer::SAIL: if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED) return false; @@ -377,19 +375,11 @@ bool CPathfinder::isMovementToDestPossible() if(!obj) return false; - if(obj->ID == Obj::BOAT) - destAction = CGPathNode::EMBARK; - else if(obj->ID != Obj::HERO) + if(obj->ID != Obj::BOAT && obj->ID != Obj::HERO) return false; } break; - case ELayer::AIR: - //if(!canMoveBetween(cp->coord, dp->coord)) - // return false; - - break; - case ELayer::WATER: if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible != CGPathNode::ACCESSIBLE) return false; @@ -399,52 +389,6 @@ bool CPathfinder::isMovementToDestPossible() break; } - if(destAction == CGPathNode::UNKNOWN) - { - destAction = CGPathNode::NORMAL; - if(dp->layer == ELayer::LAND || dp->layer == ELayer::SAIL) - { - if(obj) - { - auto objRel = getPlayerRelations(obj->tempOwner, hero->tempOwner); - if(obj->ID == Obj::HERO) - { - if(objRel == PlayerRelations::ENEMIES) - destAction = CGPathNode::BATTLE; - else - destAction = CGPathNode::BLOCKING_VISIT; - } - else if(obj->ID == Obj::TOWN && objRel == PlayerRelations::ENEMIES) - { - const CGTownInstance * townObj = dynamic_cast(obj); - if (townObj->armedGarrison()) - destAction = CGPathNode::BATTLE; - } - else if(obj->ID == Obj::GARRISON || obj->ID == Obj::GARRISON2) - { - const CGGarrison * garrisonObj = dynamic_cast(obj); - if((garrisonObj->stacksCount() && objRel == PlayerRelations::ENEMIES) || isDestinationGuarded(true)) - destAction = CGPathNode::BATTLE; - } - else if(isDestinationGuardian()) - destAction = CGPathNode::BATTLE; - else if(obj->blockVisit && (!options.useCastleGate || obj->ID != Obj::TOWN)) - destAction = CGPathNode::BLOCKING_VISIT; - - - if(destAction == CGPathNode::NORMAL) - { - if(options.originalMovementRules && isDestinationGuarded()) - destAction = CGPathNode::BATTLE; - else - destAction = CGPathNode::VISIT; - } - } - else if(isDestinationGuarded()) - destAction = CGPathNode::BATTLE; - } - } - return true; } @@ -487,6 +431,67 @@ bool CPathfinder::isMovementAfterDestPossible() const return false; } +CGPathNode::ENodeAction CPathfinder::getDestAction() const +{ + CGPathNode::ENodeAction action = CGPathNode::NORMAL; + switch(dp->layer) + { + case ELayer::LAND: + if(cp->layer == ELayer::SAIL) + { + // TODO: Handle dismebark into guarded areaa + action = CGPathNode::DISEMBARK; + break; + } + + case ELayer::SAIL: + if(dObj) + { + auto objRel = getPlayerRelations(dObj->tempOwner, hero->tempOwner); + + if(dObj->ID == Obj::BOAT) + action = CGPathNode::EMBARK; + else if(dObj->ID == Obj::HERO) + { + if(objRel == PlayerRelations::ENEMIES) + action = CGPathNode::BATTLE; + else + action = CGPathNode::BLOCKING_VISIT; + } + else if(dObj->ID == Obj::TOWN && objRel == PlayerRelations::ENEMIES) + { + const CGTownInstance * townObj = dynamic_cast(dObj); + if (townObj->armedGarrison()) + action = CGPathNode::BATTLE; + } + else if(dObj->ID == Obj::GARRISON || dObj->ID == Obj::GARRISON2) + { + const CGGarrison * garrisonObj = dynamic_cast(dObj); + if((garrisonObj->stacksCount() && objRel == PlayerRelations::ENEMIES) || isDestinationGuarded(true)) + action = CGPathNode::BATTLE; + } + else if(isDestinationGuardian()) + action = CGPathNode::BATTLE; + else if(dObj->blockVisit && (!options.useCastleGate || dObj->ID != Obj::TOWN)) + action = CGPathNode::BLOCKING_VISIT; + + if(action == CGPathNode::NORMAL) + { + if(options.originalMovementRules && isDestinationGuarded()) + action = CGPathNode::BATTLE; + else + action = CGPathNode::VISIT; + } + } + else if(isDestinationGuarded()) + action = CGPathNode::BATTLE; + + break; + } + + return action; +} + bool CPathfinder::isSourceInitialPosition() const { return cp->coord == out.hpos; diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 448024974..e900cc998 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -157,7 +157,7 @@ private: 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 - const CGObjectInstance *sTileObj; + const CGObjectInstance * cObj, * dObj; CGPathNode::ENodeAction destAction; void addNeighbours(const int3 &coord); @@ -165,8 +165,9 @@ private: bool isLayerAvailable(const ELayer &layer, const int &turn) const; bool isLayerTransitionPossible() const; - bool isMovementToDestPossible(); + bool isMovementToDestPossible() const; bool isMovementAfterDestPossible() const; + CGPathNode::ENodeAction getDestAction() const; bool isSourceInitialPosition() const; int3 getSourceGuardPosition() const; From 46b923713b69e4239848a028790f1dcf45654c28 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Wed, 11 Nov 2015 22:08:15 +0300 Subject: [PATCH 43/83] Pathfinding: rework isLayerTransitionPossible and fix formatting --- lib/CPathfinder.cpp | 228 ++++++++++++++++++++++++-------------------- lib/CPathfinder.h | 44 ++++----- 2 files changed, 147 insertions(+), 125 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 227f26845..f4e03bcaf 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -35,7 +35,7 @@ CPathfinder::PathfinderOptions::PathfinderOptions() originalMovementRules = true; } -CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero) +CPathfinder::CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstance * _hero) : CGameInfoCallback(_gs, boost::optional()), out(_out), hero(_hero) { assert(hero); @@ -82,7 +82,7 @@ void CPathfinder::calculatePaths() return true; else if(dp->turns > turn) return true; - else if(dp->turns >= turn && dp->moveRemains < remains) //this route is faster + else if(dp->turns >= turn && dp->moveRemains < remains) //this route is faster return true; return false; @@ -91,7 +91,7 @@ void CPathfinder::calculatePaths() //logGlobal->infoStream() << boost::format("Calculating paths for hero %s (adress %d) of player %d") % hero->name % hero % hero->tempOwner; //initial tile - set cost on 0 and add to the queue - CGPathNode *initialNode = out.getNode(out.hpos, hero->boat ? ELayer::SAIL : ELayer::LAND); + CGPathNode * initialNode = out.getNode(out.hpos, hero->boat ? ELayer::SAIL : ELayer::LAND); initialNode->turns = 0; initialNode->moveRemains = hero->movement; pq.push(initialNode); @@ -195,7 +195,7 @@ void CPathfinder::calculatePaths() } //queue loop } -void CPathfinder::addNeighbours(const int3 &coord) +void CPathfinder::addNeighbours(const int3 & coord) { neighbours.clear(); std::vector tiles; @@ -242,7 +242,7 @@ void CPathfinder::addTeleportExits(bool noTeleportExcludes) return false; }; - const CGTeleport *sTileTeleport = dynamic_cast(cObj); + const CGTeleport * sTileTeleport = dynamic_cast(cObj); if(isAllowedTeleportEntrance(sTileTeleport)) { for(auto objId : gs->getTeleportChannelExits(sTileTeleport->channel, hero->tempOwner)) @@ -271,20 +271,21 @@ void CPathfinder::addTeleportExits(bool noTeleportExcludes) } } -bool CPathfinder::isLayerAvailable(const ELayer &layer, const int &turn) const +bool CPathfinder::isLayerAvailable(const ELayer layer, const int turn) const { switch(layer) { - case ELayer::AIR: - if(!hlp->ti->bonusFlying) - return false; + case ELayer::AIR: + if(!hlp->ti->bonusFlying) + return false; - break; + break; - case ELayer::WATER: - if(!hlp->ti->bonusWaterWalking) - return false; - break; + case ELayer::WATER: + if(!hlp->ti->bonusWaterWalking) + return false; + + break; } return true; @@ -296,17 +297,45 @@ bool CPathfinder::isLayerTransitionPossible() const if(cp->action == CGPathNode::BATTLE) return false; - if((cp->layer == ELayer::AIR || cp->layer == ELayer::WATER) - && dp->layer != ELayer::LAND) - { - /// Hero that fly or walking on water can only go into ground layer - return false; - } - else if(cp->layer == ELayer::AIR && dp->layer == ELayer::LAND) + switch(cp->layer) { + case ELayer::LAND: + if(options.lightweightFlyingMode && dp->layer == ELayer::AIR) + { + if(!isSourceInitialPosition()) + return false; + } + else if(dp->layer == ELayer::SAIL) + { + /// Cannot enter empty water tile from land -> it has to be visitable + if(dp->accessible == CGPathNode::ACCESSIBLE) + return false; + } + + break; + + case ELayer::SAIL: + if(dp->layer != ELayer::LAND) + return false; + + 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)) + || dt->visitable) //TODO: passableness problem -> town says it's passable (thus accessible) but we obviously can't disembark onto town gate + { + return false; + } + break; + + case ELayer::AIR: + if(dp->layer != ELayer::LAND) + return false; + if(options.originalMovementRules) { - if ((cp->accessible != CGPathNode::ACCESSIBLE && + if((cp->accessible != CGPathNode::ACCESSIBLE && cp->accessible != CGPathNode::VISITABLE) && (dp->accessible != CGPathNode::VISITABLE && dp->accessible != CGPathNode::ACCESSIBLE)) @@ -319,74 +348,60 @@ bool CPathfinder::isLayerTransitionPossible() const /// Hero that fly can only land on accessible tiles return false; } - } - else if(cp->layer == ELayer::LAND && dp->layer == ELayer::AIR) - { - if(options.lightweightFlyingMode && !isSourceInitialPosition()) - return false; - } - else if(cp->layer == ELayer::SAIL && dp->layer != ELayer::LAND) - return false; - else if(cp->layer == ELayer::SAIL && dp->layer == ELayer::LAND) - { - if(!dt->isCoastal()) + + break; + + case ELayer::WATER: + if(dp->layer != ELayer::LAND) 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)) - || dt->visitable) //TODO: passableness problem -> town says it's passable (thus accessible) but we obviously can't disembark onto town gate - return false; - } - else if(cp->layer == ELayer::LAND && dp->layer == ELayer::SAIL) - { - if(dp->accessible == CGPathNode::ACCESSIBLE) //cannot enter empty water tile from land -> it has to be visitable - return false; + break; } + return true; } bool CPathfinder::isMovementToDestPossible() const { - auto obj = dt->topVisitableObj(); switch(dp->layer) { - case ELayer::LAND: - if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED) - return false; - if(isSourceGuarded()) + case ELayer::LAND: + if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED) + return false; + if(isSourceGuarded()) + { + if(!(options.originalMovementRules && cp->layer == ELayer::AIR) && + !isDestinationGuardian()) // Can step into tile of guard { - if(!(options.originalMovementRules && cp->layer == ELayer::AIR) && - !isDestinationGuardian()) // Can step into tile of guard - { - return false; - } + return false; } + } - break; + break; - case ELayer::SAIL: - if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED) - return false; - if(isSourceGuarded() && !isDestinationGuardian()) // Can step into tile of guard + case ELayer::SAIL: + if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED) + return false; + if(isSourceGuarded() && !isDestinationGuardian()) // Can step into tile of guard + return false; + + if(cp->layer == ELayer::LAND) + { + if(!dObj) return false; - if(cp->layer == ELayer::LAND) - { - if(!obj) - return false; - - if(obj->ID != Obj::BOAT && obj->ID != Obj::HERO) - return false; - } - break; - - case ELayer::WATER: - if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible != CGPathNode::ACCESSIBLE) - return false; - if(isDestinationGuarded()) + if(dObj->ID != Obj::BOAT && dObj->ID != Obj::HERO) return false; + } + break; - break; + case ELayer::WATER: + if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible != CGPathNode::ACCESSIBLE) + return false; + if(isDestinationGuarded()) + return false; + + break; } return true; @@ -401,16 +416,17 @@ bool CPathfinder::isMovementAfterDestPossible() const case CGPathNode::VISIT: /// For now we only add visitable tile into queue when it's teleporter that allow transit /// Movement from visitable tile when hero is standing on it is possible into any layer - if(CGTeleport::isTeleport(dt->topVisitableObj())) + if(CGTeleport::isTeleport(dObj)) { /// For now we'll always allow transit over teleporters /// Transit over whirlpools only allowed when hero protected - auto whirlpool = dynamic_cast(dt->topVisitableObj()); + auto whirlpool = dynamic_cast(dObj); if(!whirlpool || options.useTeleportWhirlpool) return true; } - else - return false; + + break; + case CGPathNode::NORMAL: return true; @@ -418,14 +434,20 @@ bool CPathfinder::isMovementAfterDestPossible() const if(options.useEmbarkAndDisembark) return true; + break; + case CGPathNode::DISEMBARK: if(options.useEmbarkAndDisembark && !isDestinationGuarded()) return true; + break; + case CGPathNode::BATTLE: /// Movement after BATTLE action only possible to guardian tile - if(isDestinationGuarded()) + if(isDestinationGuardian()) return true; + + break; } return false; @@ -461,7 +483,7 @@ CGPathNode::ENodeAction CPathfinder::getDestAction() const else if(dObj->ID == Obj::TOWN && objRel == PlayerRelations::ENEMIES) { const CGTownInstance * townObj = dynamic_cast(dObj); - if (townObj->armedGarrison()) + if(townObj->armedGarrison()) action = CGPathNode::BATTLE; } else if(dObj->ID == Obj::GARRISON || dObj->ID == Obj::GARRISON2) @@ -509,9 +531,9 @@ bool CPathfinder::isSourceGuarded() const if(getSourceGuardPosition() != int3(-1, -1, -1) && !isSourceInitialPosition()) { //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 - || cp->theNodeBefore->layer == ELayer::LAND - || ct->topVisitableId() != Obj::BOAT) + if(cp->accessible != CGPathNode::VISITABLE || + cp->theNodeBefore->layer == ELayer::LAND || + cObj->ID != Obj::BOAT) { return true; } @@ -538,7 +560,7 @@ bool CPathfinder::isDestinationGuardian() const void CPathfinder::initializeGraph() { - auto updateNode = [&](int3 pos, ELayer layer, const TerrainTile *tinfo, bool blockNotAccessible) + auto updateNode = [&](int3 pos, ELayer layer, const TerrainTile * tinfo, bool blockNotAccessible) { auto node = out.getNode(pos, layer); node->reset(); @@ -558,7 +580,7 @@ void CPathfinder::initializeGraph() { for(pos.z=0; pos.z < out.sizes.z; ++pos.z) { - const TerrainTile *tinfo = &gs->map->getTile(pos); + const TerrainTile * tinfo = &gs->map->getTile(pos); switch(tinfo->terType) { case ETerrainType::ROCK: @@ -581,7 +603,7 @@ void CPathfinder::initializeGraph() } } -CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const int3 &pos, const TerrainTile *tinfo) const +CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo) const { CGPathNode::EAccessibility ret = (tinfo->blocked ? CGPathNode::BLOCKED : CGPathNode::ACCESSIBLE); @@ -596,7 +618,7 @@ CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const int3 &pos, c } else { - for(const CGObjectInstance *obj : tinfo->visitableObjects) + for(const CGObjectInstance * obj : tinfo->visitableObjects) { if(obj->passableFor(hero->tempOwner)) { @@ -608,7 +630,7 @@ CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const int3 &pos, c } else if(obj->ID != Obj::EVENT) //pathfinder should ignore placed events { - ret = CGPathNode::VISITABLE; + ret = CGPathNode::VISITABLE; } } } @@ -623,7 +645,7 @@ CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const int3 &pos, c return ret; } -bool CPathfinder::canMoveBetween(const int3 &a, const int3 &b) const +bool CPathfinder::canMoveBetween(const int3 & a, const int3 & b) const { return gs->checkForVisitableDir(a, b); } @@ -673,7 +695,7 @@ CPathfinderHelper::CPathfinderHelper(const CGHeroInstance * Hero) updateTurnInfo(); } -void CPathfinderHelper::updateTurnInfo(const int &turn) +void CPathfinderHelper::updateTurnInfo(const int turn) { if(!ti || ti->turn != turn) { @@ -687,12 +709,12 @@ void CPathfinderHelper::updateTurnInfo(const int &turn) } } -int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer &layer) const +int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer layer) const { return layer == EPathfindingLayer::SAIL ? ti->maxMovePointsWater : ti->maxMovePointsLand; } -TurnInfo * CPathfinderHelper::getTurnInfo(const CGHeroInstance * h, const int &turn) +TurnInfo * CPathfinderHelper::getTurnInfo(const CGHeroInstance * h, const int turn) { auto turnInfo = new TurnInfo; turnInfo->turn = turn; @@ -703,19 +725,19 @@ TurnInfo * CPathfinderHelper::getTurnInfo(const CGHeroInstance * h, const int &t return turnInfo; } -void CPathfinderHelper::getNeighbours(CGameState * gs, const TerrainTile &srct, const int3 &tile, std::vector &vec, const boost::logic::tribool &onLand, const bool &limitCoastSailing) +void CPathfinderHelper::getNeighbours(CGameState * gs, const TerrainTile & srct, const int3 & tile, std::vector & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing) { 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.reserve(8); //optimization - for (auto & dir : dirs) + for(auto & dir : dirs) { const int3 hlp = tile + dir; if(!gs->isInTheMap(hlp)) continue; - const TerrainTile &hlpt = gs->map->getTile(hlp); + const TerrainTile & hlpt = gs->map->getTile(hlp); // //we cannot visit things from blocked tiles // if(srct.blocked && !srct.visitable && hlpt.visitable && srct.blockingObjects.front()->ID != HEROI_TYPE) @@ -734,7 +756,7 @@ void CPathfinderHelper::getNeighbours(CGameState * gs, const TerrainTile &srct, continue; } - if((indeterminate(onLand) || onLand == (hlpt.terType!=ETerrainType::WATER) ) + if((indeterminate(onLand) || onLand == (hlpt.terType!=ETerrainType::WATER) ) && hlpt.terType != ETerrainType::ROCK) { vec.push_back(hlp); @@ -742,7 +764,7 @@ void CPathfinderHelper::getNeighbours(CGameState * gs, const TerrainTile &srct, } } -int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 &src, const int3 &dst, const int &remainingMovePoints, const TurnInfo * ti, const bool &checkLast) +int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & src, const int3 & dst, const int remainingMovePoints, const TurnInfo * ti, const bool checkLast) { if(src == dst) //same tile return 0; @@ -793,7 +815,7 @@ int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 &src return ret; } -int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 &dst) +int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & dst) { return getMovementCost(h, h->visitablePos(), dst, h->movement); } @@ -840,7 +862,7 @@ void CGPath::convert(ui8 mode) } } -CPathsInfo::CPathsInfo(const int3 &Sizes) +CPathsInfo::CPathsInfo(const int3 & Sizes) : sizes(Sizes) { hero = nullptr; @@ -856,7 +878,7 @@ CPathsInfo::~CPathsInfo() { } -const CGPathNode * CPathsInfo::getPathInfo(const int3 &tile, const ELayer &layer) const +const CGPathNode * CPathsInfo::getPathInfo(const int3 & tile, const ELayer layer) const { boost::unique_lock pathLock(pathMx); @@ -865,25 +887,25 @@ const CGPathNode * CPathsInfo::getPathInfo(const int3 &tile, const ELayer &layer return getNode(tile, layer); } -bool CPathsInfo::getPath(CGPath &out, const int3 &dst, const ELayer &layer) const +bool CPathsInfo::getPath(CGPath & out, const int3 & dst, const ELayer layer) const { boost::unique_lock pathLock(pathMx); out.nodes.clear(); - const CGPathNode *curnode = getNode(dst, layer); + const CGPathNode * curnode = getNode(dst, layer); if(!curnode->theNodeBefore) return false; while(curnode) { - CGPathNode cpn = *curnode; + CGPathNode cpn = * curnode; curnode = curnode->theNodeBefore; out.nodes.push_back(cpn); } return true; } -int CPathsInfo::getDistance(const int3 &tile, const ELayer &layer) const +int CPathsInfo::getDistance(const int3 & tile, const ELayer layer) const { boost::unique_lock pathLock(pathMx); @@ -894,7 +916,7 @@ int CPathsInfo::getDistance(const int3 &tile, const ELayer &layer) const return 255; } -CGPathNode *CPathsInfo::getNode(const int3 &coord, const ELayer &layer) const +CGPathNode * CPathsInfo::getNode(const int3 & coord, const ELayer layer) const { if(layer != ELayer::AUTO) return nodes[coord.x][coord.y][coord.z][layer]; diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index e900cc998..b0cfef049 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -75,23 +75,23 @@ struct DLL_LINKAGE CPathsInfo mutable boost::mutex pathMx; - const CGHeroInstance *hero; + const CGHeroInstance * hero; int3 hpos; int3 sizes; boost::multi_array nodes; //[w][h][level][layer] - CPathsInfo(const int3 &Sizes); + CPathsInfo(const int3 & Sizes); ~CPathsInfo(); - const CGPathNode * getPathInfo(const int3 &tile, const ELayer &layer = ELayer::AUTO) const; - bool getPath(CGPath &out, const int3 &dst, const ELayer &layer = ELayer::AUTO) const; - int getDistance(const int3 &tile, const ELayer &layer = ELayer::AUTO) const; - CGPathNode *getNode(const int3 &coord, const ELayer &layer) const; + const CGPathNode * getPathInfo(const int3 & tile, const ELayer layer = ELayer::AUTO) const; + bool getPath(CGPath & out, const int3 & dst, const ELayer layer = ELayer::AUTO) const; + int getDistance(const int3 & tile, const ELayer layer = ELayer::AUTO) const; + CGPathNode * getNode(const int3 & coord, const ELayer layer) const; }; class CPathfinder : private CGameInfoCallback { public: - CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance *_hero); + CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstance * _hero); void calculatePaths(); //calculates possible paths for hero, uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists private: @@ -134,8 +134,8 @@ private: PathfinderOptions(); } options; - CPathsInfo &out; - const CGHeroInstance *hero; + CPathsInfo & out; + const CGHeroInstance * hero; unique_ptr hlp; struct NodeComparer @@ -154,16 +154,16 @@ private: std::vector neighbours; - 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 + 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 const CGObjectInstance * cObj, * dObj; CGPathNode::ENodeAction destAction; - void addNeighbours(const int3 &coord); + void addNeighbours(const int3 & coord); void addTeleportExits(bool noTeleportExcludes = false); - bool isLayerAvailable(const ELayer &layer, const int &turn) const; + bool isLayerAvailable(const ELayer layer, const int turn) const; bool isLayerTransitionPossible() const; bool isMovementToDestPossible() const; bool isMovementAfterDestPossible() const; @@ -177,8 +177,8 @@ private: void initializeGraph(); - CGPathNode::EAccessibility evaluateAccessibility(const int3 &pos, const TerrainTile *tinfo) const; - bool canMoveBetween(const int3 &a, const int3 &b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility) + CGPathNode::EAccessibility evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo) const; + bool canMoveBetween(const int3 & a, const int3 & b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility) bool addTeleportTwoWay(const CGTeleport * obj) const; bool addTeleportOneWay(const CGTeleport * obj) const; @@ -205,14 +205,14 @@ public: const CGHeroInstance * hero; CPathfinderHelper(const CGHeroInstance * Hero); - void updateTurnInfo(const int &turn = 0); - int getMaxMovePoints(const EPathfindingLayer &layer) const; - static TurnInfo * getTurnInfo(const CGHeroInstance * h, const int &turn = 0); + void updateTurnInfo(const int turn = 0); + int getMaxMovePoints(const EPathfindingLayer layer) const; + static TurnInfo * getTurnInfo(const CGHeroInstance * h, const int turn = 0); - static void getNeighbours(CGameState * gs, const TerrainTile &srct, const int3 &tile, std::vector &vec, const boost::logic::tribool &onLand, const bool &limitCoastSailing); + static void getNeighbours(CGameState * gs, const TerrainTile & srct, const int3 & tile, std::vector & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing); - static int getMovementCost(const CGHeroInstance * h, const int3 &src, const int3 &dst, const int &remainingMovePoints =- 1, const TurnInfo * ti = nullptr, const bool &checkLast = true); - static int getMovementCost(const CGHeroInstance * h, const int3 &dst); + static int getMovementCost(const CGHeroInstance * h, const int3 & src, const int3 & dst, const int remainingMovePoints =- 1, const TurnInfo * ti = nullptr, const bool checkLast = true); + static int getMovementCost(const CGHeroInstance * h, const int3 & dst); private: std::vector turnsInfo; From 09473f6648d4244434129ab73e90b311b5e2a34d Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Wed, 11 Nov 2015 22:29:20 +0300 Subject: [PATCH 44/83] CPathfinder: move embark special case to isMovementToDestPossible --- lib/CPathfinder.cpp | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index f4e03bcaf..758804767 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -327,6 +327,7 @@ bool CPathfinder::isLayerTransitionPossible() const { return false; } + break; case ELayer::AIR: @@ -382,8 +383,12 @@ bool CPathfinder::isMovementToDestPossible() const case ELayer::SAIL: if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED) return false; - if(isSourceGuarded() && !isDestinationGuardian()) // Can step into tile of guard - return false; + if(isSourceGuarded()) + { + // Hero embarked a boat standing on a guarded tile -> we must allow to move away from that tile + if(cp->action != CGPathNode::EMBARK && !isDestinationGuardian()) + return false; + } if(cp->layer == ELayer::LAND) { @@ -393,6 +398,7 @@ bool CPathfinder::isMovementToDestPossible() const if(dObj->ID != Obj::BOAT && dObj->ID != Obj::HERO) return false; } + break; case ELayer::WATER: @@ -526,17 +532,14 @@ int3 CPathfinder::getSourceGuardPosition() const bool CPathfinder::isSourceGuarded() const { - //map can start with hero on guarded tile or teleport there using dimension door - //so threat tile hero standing on like it's not guarded because it's should be possible to move out of here + /// Hero can move from guarded tile if movement started on that tile + /// It's possible at least in these cases: + /// - Map start with hero on guarded tile + /// - Dimention door used + /// TODO: check what happen when there is several guards if(getSourceGuardPosition() != int3(-1, -1, -1) && !isSourceInitialPosition()) { - //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 || - cp->theNodeBefore->layer == ELayer::LAND || - cObj->ID != Obj::BOAT) - { - return true; - } + return true; } return false; From d76b2b7ca886a6fbf3b2b73a4135710cd040aff8 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Thu, 12 Nov 2015 00:05:20 +0300 Subject: [PATCH 45/83] CPathsInfo: rework nodes multi_array allocation --- lib/CPathfinder.cpp | 64 +++++++++++++++++++++++++++------------------ lib/CPathfinder.h | 15 ++++++----- 2 files changed, 47 insertions(+), 32 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 758804767..749c2d008 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -566,14 +566,15 @@ void CPathfinder::initializeGraph() auto updateNode = [&](int3 pos, ELayer layer, const TerrainTile * tinfo, bool blockNotAccessible) { auto node = out.getNode(pos, layer); - node->reset(); auto accessibility = evaluateAccessibility(pos, tinfo); /// TODO: Probably this shouldn't be handled by initializeGraph if(blockNotAccessible && (accessibility != CGPathNode::ACCESSIBLE || tinfo->terType == ETerrainType::WATER)) + { accessibility = CGPathNode::BLOCKED; - node->accessible = accessibility; + } + node->update(pos, layer, accessibility); }; int3 pos; @@ -823,8 +824,8 @@ int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & ds return getMovementCost(h, h->visitablePos(), dst, h->movement); } -CGPathNode::CGPathNode(int3 Coord, ELayer Layer) - : coord(Coord), layer(Layer) +CGPathNode::CGPathNode() + : coord(int3(-1, -1, -1)), layer(ELayer::WRONG) { reset(); } @@ -839,6 +840,19 @@ void CGPathNode::reset() action = UNKNOWN; } +void CGPathNode::update(const int3 & Coord, const ELayer Layer, const EAccessibility Accessible) +{ + if(layer == ELayer::WRONG) + { + coord = Coord; + layer = Layer; + } + else + reset(); + + accessible = Accessible; +} + bool CGPathNode::reachable() const { return turns < 255; @@ -870,63 +884,61 @@ CPathsInfo::CPathsInfo(const int3 & Sizes) { hero = nullptr; nodes.resize(boost::extents[sizes.x][sizes.y][sizes.z][ELayer::NUM_LAYERS]); - for(int i = 0; i < sizes.x; i++) - for(int j = 0; j < sizes.y; j++) - for(int z = 0; z < sizes.z; z++) - for(int l = 0; l < ELayer::NUM_LAYERS; l++) - nodes[i][j][z][l] = new CGPathNode(int3(i, j, z), static_cast(l)); } CPathsInfo::~CPathsInfo() { } -const CGPathNode * CPathsInfo::getPathInfo(const int3 & tile, const ELayer layer) const +const CGPathNode * CPathsInfo::getPathInfo(const int3 & tile) const { - boost::unique_lock pathLock(pathMx); + assert(vstd::iswithin(tile.x, 0, sizes.x)); + assert(vstd::iswithin(tile.y, 0, sizes.y)); + assert(vstd::iswithin(tile.z, 0, sizes.z)); - if(tile.x >= sizes.x || tile.y >= sizes.y || tile.z >= sizes.z || layer >= ELayer::NUM_LAYERS) - return nullptr; - return getNode(tile, layer); + boost::unique_lock pathLock(pathMx); + return getNode(tile); } -bool CPathsInfo::getPath(CGPath & out, const int3 & dst, const ELayer layer) const +bool CPathsInfo::getPath(CGPath & out, const int3 & dst) const { boost::unique_lock pathLock(pathMx); out.nodes.clear(); - const CGPathNode * curnode = getNode(dst, layer); + const CGPathNode * curnode = getNode(dst); if(!curnode->theNodeBefore) return false; while(curnode) { - CGPathNode cpn = * curnode; + const CGPathNode cpn = * curnode; curnode = curnode->theNodeBefore; out.nodes.push_back(cpn); } return true; } -int CPathsInfo::getDistance(const int3 & tile, const ELayer layer) const +int CPathsInfo::getDistance(const int3 & tile) const { boost::unique_lock pathLock(pathMx); CGPath ret; - if(getPath(ret, tile, layer)) + if(getPath(ret, tile)) return ret.nodes.size(); else return 255; } -CGPathNode * CPathsInfo::getNode(const int3 & coord, const ELayer layer) const +const CGPathNode * CPathsInfo::getNode(const int3 & coord) const { - if(layer != ELayer::AUTO) - return nodes[coord.x][coord.y][coord.z][layer]; - - auto landNode = nodes[coord.x][coord.y][coord.z][ELayer::LAND]; - if(landNode->theNodeBefore) + auto landNode = &nodes[coord.x][coord.y][coord.z][ELayer::LAND]; + if(landNode->reachable()) return landNode; else - return nodes[coord.x][coord.y][coord.z][ELayer::SAIL]; + return &nodes[coord.x][coord.y][coord.z][ELayer::SAIL]; +} + +CGPathNode * CPathsInfo::getNode(const int3 & coord, const ELayer layer) +{ + return &nodes[coord.x][coord.y][coord.z][layer]; } diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index b0cfef049..3c386308c 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -55,8 +55,9 @@ struct DLL_LINKAGE CGPathNode ELayer layer; ENodeAction action; - CGPathNode(int3 Coord, ELayer Layer); + CGPathNode(); void reset(); + void update(const int3 & Coord, const ELayer Layer, const EAccessibility Accessible); bool reachable() const; }; @@ -78,14 +79,16 @@ struct DLL_LINKAGE CPathsInfo const CGHeroInstance * hero; int3 hpos; int3 sizes; - boost::multi_array nodes; //[w][h][level][layer] + boost::multi_array nodes; //[w][h][level][layer] CPathsInfo(const int3 & Sizes); ~CPathsInfo(); - const CGPathNode * getPathInfo(const int3 & tile, const ELayer layer = ELayer::AUTO) const; - bool getPath(CGPath & out, const int3 & dst, const ELayer layer = ELayer::AUTO) const; - int getDistance(const int3 & tile, const ELayer layer = ELayer::AUTO) const; - CGPathNode * getNode(const int3 & coord, const ELayer layer) const; + const CGPathNode * getPathInfo(const int3 & tile) const; + bool getPath(CGPath & out, const int3 & dst) const; + int getDistance(const int3 & tile) const; + const CGPathNode * getNode(const int3 & coord) const; + + CGPathNode * getNode(const int3 & coord, const ELayer layer); }; class CPathfinder : private CGameInfoCallback From 942c0cd718a94806cd848b812b6095314cdd1944 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Thu, 12 Nov 2015 01:16:06 +0300 Subject: [PATCH 46/83] CPathfinder: improve support for whirlpools --- lib/CPathfinder.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 749c2d008..574f2bf31 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -248,7 +248,16 @@ void CPathfinder::addTeleportExits(bool noTeleportExcludes) for(auto objId : gs->getTeleportChannelExits(sTileTeleport->channel, hero->tempOwner)) { auto obj = getObj(objId); - if(CGTeleport::isExitPassable(gs, hero, obj)) + if(dynamic_cast(obj)) + { + auto pos = obj->getBlockedPos(); + for(auto p : pos) + { + if(gs->getTile(p)->topVisitableId() == obj->ID) + neighbours.push_back(p); + } + } + else if(CGTeleport::isExitPassable(gs, hero, obj)) neighbours.push_back(obj->visitablePos()); } } From 9ed9d94009930315fcda8c289047ebf080076294 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Thu, 12 Nov 2015 05:20:32 +0300 Subject: [PATCH 47/83] TurnInfo: first step towards better abstraction --- lib/CPathfinder.cpp | 40 ++++++++++++++++++++++++---------------- lib/CPathfinder.h | 7 +++++-- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 574f2bf31..b0fa4f9cb 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -109,7 +109,7 @@ void CPathfinder::calculatePaths() if(!movement) { hlp->updateTurnInfo(++turn); - movement = hlp->getMaxMovePoints(cp->layer); + movement = hlp->getMaxMovePoints(cp->layer, turn); } //add accessible neighbouring nodes to the queue @@ -140,7 +140,7 @@ void CPathfinder::calculatePaths() continue; destAction = getDestAction(); - int cost = CPathfinderHelper::getMovementCost(hero, cp->coord, dp->coord, movement, hlp->ti); + int cost = CPathfinderHelper::getMovementCost(hero, cp->coord, dp->coord, movement, hlp->getTurnInfo(turn)); int remains = movement - cost; if(destAction == CGPathNode::EMBARK || destAction == CGPathNode::DISEMBARK) { @@ -152,8 +152,8 @@ void CPathfinder::calculatePaths() { //occurs rarely, when hero with low movepoints tries to leave the road hlp->updateTurnInfo(++turnAtNextTile); - int moveAtNextTile = hlp->getMaxMovePoints(i); - cost = CPathfinderHelper::getMovementCost(hero, cp->coord, dp->coord, moveAtNextTile, hlp->ti); //cost must be updated, movement points changed :( + int moveAtNextTile = hlp->getMaxMovePoints(i, turnAtNextTile); + cost = CPathfinderHelper::getMovementCost(hero, cp->coord, dp->coord, moveAtNextTile, hlp->getTurnInfo(turnAtNextTile)); //cost must be updated, movement points changed :( remains = moveAtNextTile - cost; } @@ -701,6 +701,20 @@ bool CPathfinder::canVisitObject() const return cp->layer == ELayer::LAND || cp->layer == ELayer::SAIL; } +TurnInfo::TurnInfo(const CGHeroInstance * h, const int Turn) + : turn(Turn) +{ + maxMovePointsLand = h->maxMovePoints(true); + maxMovePointsWater = h->maxMovePoints(false); + bonusFlying = h->getBonusAtTurn(Bonus::FLYING_MOVEMENT, turn); + bonusWaterWalking = h->getBonusAtTurn(Bonus::WATER_WALKING, turn); +} + +int TurnInfo::getMaxMovePoints(const EPathfindingLayer layer) const +{ + return layer == EPathfindingLayer::SAIL ? maxMovePointsWater : maxMovePointsLand; +} + CPathfinderHelper::CPathfinderHelper(const CGHeroInstance * Hero) : ti(nullptr), hero(Hero) { @@ -716,26 +730,20 @@ void CPathfinderHelper::updateTurnInfo(const int turn) ti = turnsInfo[turn]; else { - ti = getTurnInfo(hero, turn); + ti = new TurnInfo(hero, turn); turnsInfo.push_back(ti); } } } -int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer layer) const +const TurnInfo * CPathfinderHelper::getTurnInfo(const int turn) const { - return layer == EPathfindingLayer::SAIL ? ti->maxMovePointsWater : ti->maxMovePointsLand; + return turnsInfo[turn]; } -TurnInfo * CPathfinderHelper::getTurnInfo(const CGHeroInstance * h, const int turn) +int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer layer, const int turn) const { - auto turnInfo = new TurnInfo; - turnInfo->turn = turn; - turnInfo->maxMovePointsLand = h->maxMovePoints(true); - turnInfo->maxMovePointsWater = h->maxMovePoints(false); - turnInfo->bonusFlying = h->getBonusAtTurn(Bonus::FLYING_MOVEMENT, turn); - turnInfo->bonusWaterWalking = h->getBonusAtTurn(Bonus::WATER_WALKING, turn); - return turnInfo; + return turnsInfo[turn]->getMaxMovePoints(layer); } void CPathfinderHelper::getNeighbours(CGameState * gs, const TerrainTile & srct, const int3 & tile, std::vector & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing) @@ -783,7 +791,7 @@ int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & sr return 0; if(!ti) - ti = getTurnInfo(h); + ti = new TurnInfo(h); auto s = h->cb->getTile(src), d = h->cb->getTile(dst); int ret = h->getTileCost(*d, *s, ti); diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 3c386308c..cdd263d28 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -199,6 +199,9 @@ struct TurnInfo int maxMovePointsWater; const Bonus * bonusFlying; const Bonus * bonusWaterWalking; + + TurnInfo(const CGHeroInstance * h, const int Turn = 0); + int getMaxMovePoints(const EPathfindingLayer layer) const; }; class DLL_LINKAGE CPathfinderHelper @@ -209,8 +212,8 @@ public: CPathfinderHelper(const CGHeroInstance * Hero); void updateTurnInfo(const int turn = 0); - int getMaxMovePoints(const EPathfindingLayer layer) const; - static TurnInfo * getTurnInfo(const CGHeroInstance * h, const int turn = 0); + const TurnInfo * getTurnInfo(const int turn) const; + int getMaxMovePoints(const EPathfindingLayer layer, const int turn) const; static void getNeighbours(CGameState * gs, const TerrainTile & srct, const int3 & tile, std::vector & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing); From abc4ea272fc7f75614c96528767a21fe47da892e Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Thu, 12 Nov 2015 14:04:33 +0300 Subject: [PATCH 48/83] TurnInfo: store all bonuses and use TileInfo for everything Currently this going to break ONE_WEEK bonuses because those don't work with CWillLastDays selector. --- lib/CPathfinder.cpp | 74 +++++++++++++++++++------------ lib/CPathfinder.h | 22 ++++----- lib/mapObjects/CGHeroInstance.cpp | 23 +++++----- lib/mapObjects/CGHeroInstance.h | 5 +-- server/CGameHandler.cpp | 11 ++--- 5 files changed, 77 insertions(+), 58 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index b0fa4f9cb..fc85d4118 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -50,11 +50,11 @@ CPathfinder::CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstan } hlp = make_unique(hero); - if(hlp->ti->bonusFlying) + if(hlp->hasBonusOfType(Bonus::FLYING_MOVEMENT)) options.useFlying = true; - if(hlp->ti->bonusWaterWalking) + if(hlp->hasBonusOfType(Bonus::WATER_WALKING)) options.useWaterWalking = true; - if(CGWhirlpool::isProtected(hero)) + if(hlp->hasBonusOfType(Bonus::WHIRLPOOL_PROTECTION)) options.useTeleportWhirlpool = true; initializeGraph(); @@ -109,7 +109,7 @@ void CPathfinder::calculatePaths() if(!movement) { hlp->updateTurnInfo(++turn); - movement = hlp->getMaxMovePoints(cp->layer, turn); + movement = hlp->getMaxMovePoints(cp->layer); } //add accessible neighbouring nodes to the queue @@ -140,11 +140,11 @@ void CPathfinder::calculatePaths() continue; destAction = getDestAction(); - int cost = CPathfinderHelper::getMovementCost(hero, cp->coord, dp->coord, movement, hlp->getTurnInfo(turn)); + int cost = CPathfinderHelper::getMovementCost(hero, cp->coord, dp->coord, movement, hlp->getTurnInfo()); int remains = movement - cost; if(destAction == CGPathNode::EMBARK || destAction == CGPathNode::DISEMBARK) { - remains = hero->movementPointsAfterEmbark(movement, cost, destAction - 1); + remains = hero->movementPointsAfterEmbark(movement, cost, destAction - 1, hlp->getTurnInfo()); cost = movement - remains; } int turnAtNextTile = turn; @@ -152,8 +152,8 @@ void CPathfinder::calculatePaths() { //occurs rarely, when hero with low movepoints tries to leave the road hlp->updateTurnInfo(++turnAtNextTile); - int moveAtNextTile = hlp->getMaxMovePoints(i, turnAtNextTile); - cost = CPathfinderHelper::getMovementCost(hero, cp->coord, dp->coord, moveAtNextTile, hlp->getTurnInfo(turnAtNextTile)); //cost must be updated, movement points changed :( + int moveAtNextTile = hlp->getMaxMovePoints(i); + cost = CPathfinderHelper::getMovementCost(hero, cp->coord, dp->coord, moveAtNextTile, hlp->getTurnInfo()); //cost must be updated, movement points changed :( remains = moveAtNextTile - cost; } @@ -285,13 +285,13 @@ bool CPathfinder::isLayerAvailable(const ELayer layer, const int turn) const switch(layer) { case ELayer::AIR: - if(!hlp->ti->bonusFlying) + if(!hlp->hasBonusOfType(Bonus::FLYING_MOVEMENT)) return false; break; case ELayer::WATER: - if(!hlp->ti->bonusWaterWalking) + if(!hlp->hasBonusOfType(Bonus::WATER_WALKING)) return false; break; @@ -701,47 +701,63 @@ bool CPathfinder::canVisitObject() const return cp->layer == ELayer::LAND || cp->layer == ELayer::SAIL; } -TurnInfo::TurnInfo(const CGHeroInstance * h, const int Turn) - : turn(Turn) +TurnInfo::TurnInfo(const CGHeroInstance * Hero, const int turn) + : hero(Hero), maxMovePointsLand(-1), maxMovePointsWater(-1) { - maxMovePointsLand = h->maxMovePoints(true); - maxMovePointsWater = h->maxMovePoints(false); - bonusFlying = h->getBonusAtTurn(Bonus::FLYING_MOVEMENT, turn); - bonusWaterWalking = h->getBonusAtTurn(Bonus::WATER_WALKING, turn); + bonuses = hero->getAllBonuses(Selector::days(turn), nullptr); +} + +bool TurnInfo::hasBonusOfType(Bonus::BonusType type, int subtype) const +{ + return bonuses->getFirst(Selector::type(type).And(Selector::subtype(subtype))); +} + +int TurnInfo::valOfBonuses(Bonus::BonusType type, int subtype) const +{ + return bonuses->valOfBonuses(Selector::type(type).And(Selector::subtype(subtype))); } int TurnInfo::getMaxMovePoints(const EPathfindingLayer layer) const { + if(maxMovePointsLand == -1) + maxMovePointsLand = hero->maxMovePoints(true, this); + if(maxMovePointsWater == -1) + maxMovePointsWater = hero->maxMovePoints(false, this); + return layer == EPathfindingLayer::SAIL ? maxMovePointsWater : maxMovePointsLand; } CPathfinderHelper::CPathfinderHelper(const CGHeroInstance * Hero) - : ti(nullptr), hero(Hero) + : turn(0), hero(Hero) { turnsInfo.reserve(16); updateTurnInfo(); } -void CPathfinderHelper::updateTurnInfo(const int turn) +void CPathfinderHelper::updateTurnInfo(const int Turn) { - if(!ti || ti->turn != turn) + if(turn != Turn) { - if(turn < turnsInfo.size()) - ti = turnsInfo[turn]; - else + turn = Turn; + if(turn >= turnsInfo.size()) { - ti = new TurnInfo(hero, turn); + auto ti = new TurnInfo(hero, turn); turnsInfo.push_back(ti); } } } -const TurnInfo * CPathfinderHelper::getTurnInfo(const int turn) const +const TurnInfo * CPathfinderHelper::getTurnInfo() const { return turnsInfo[turn]; } -int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer layer, const int turn) const +bool CPathfinderHelper::hasBonusOfType(const Bonus::BonusType type, const int subtype) const +{ + return turnsInfo[turn]->hasBonusOfType(type, subtype); +} + +int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer layer) const { return turnsInfo[turn]->getMaxMovePoints(layer); } @@ -796,17 +812,17 @@ int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & sr auto s = h->cb->getTile(src), d = h->cb->getTile(dst); int ret = h->getTileCost(*d, *s, ti); - if(d->blocked && ti->bonusFlying) + if(d->blocked && ti->hasBonusOfType(Bonus::FLYING_MOVEMENT)) { - ret *= (100.0 + ti->bonusFlying->val) / 100.0; + ret *= (100.0 + ti->valOfBonuses(Bonus::FLYING_MOVEMENT)) / 100.0; } else if(d->terType == ETerrainType::WATER) { if(h->boat && s->hasFavourableWinds() && d->hasFavourableWinds()) //Favourable Winds ret *= 0.666; - else if(!h->boat && ti->bonusWaterWalking) + else if(!h->boat && ti->hasBonusOfType(Bonus::WATER_WALKING)) { - ret *= (100.0 + ti->bonusWaterWalking->val) / 100.0; + ret *= (100.0 + ti->valOfBonuses(Bonus::WATER_WALKING)) / 100.0; } } diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index cdd263d28..0a7083dc8 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -192,28 +192,30 @@ private: }; -struct TurnInfo +struct DLL_LINKAGE TurnInfo { - int turn; - int maxMovePointsLand; - int maxMovePointsWater; - const Bonus * bonusFlying; - const Bonus * bonusWaterWalking; + const CGHeroInstance * hero; + TBonusListPtr bonuses; + mutable int maxMovePointsLand; + mutable int maxMovePointsWater; - TurnInfo(const CGHeroInstance * h, const int Turn = 0); + TurnInfo(const CGHeroInstance * Hero, const int Turn = 0); + bool hasBonusOfType(const Bonus::BonusType type, const int subtype = -1) const; + int valOfBonuses(const Bonus::BonusType type, const int subtype = -1) const; int getMaxMovePoints(const EPathfindingLayer layer) const; }; class DLL_LINKAGE CPathfinderHelper { public: - TurnInfo * ti; + int turn; const CGHeroInstance * hero; CPathfinderHelper(const CGHeroInstance * Hero); void updateTurnInfo(const int turn = 0); - const TurnInfo * getTurnInfo(const int turn) const; - int getMaxMovePoints(const EPathfindingLayer layer, const int turn) const; + const TurnInfo * getTurnInfo() const; + bool hasBonusOfType(const Bonus::BonusType type, const int subtype = -1) const; + int getMaxMovePoints(const EPathfindingLayer layer) const; static void getNeighbours(CGameState * gs, const TerrainTile & srct, const int3 & tile, std::vector & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing); diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index a031d080c..e6156b9bd 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -80,7 +80,7 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &fro break; } } - else if(!getBonusAtTurn(Bonus::NO_TERRAIN_PENALTY, ti->turn, from.terType)) + else if(!ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType)) { // NOTE: in H3 neutral stacks will ignore terrain penalty only if placed as topmost stack(s) in hero army. // This is clearly bug in H3 however intended behaviour is not clear. @@ -129,11 +129,6 @@ int3 CGHeroInstance::getPosition(bool h3m) const //h3m=true - returns position o } } -const Bonus * CGHeroInstance::getBonusAtTurn(const Bonus::BonusType &type, const int &turn, const TBonusSubtype &subType) const -{ - return getBonus(Selector::type(type).And(Selector::days(turn)).And(Selector::subtype(subType))); -} - ui8 CGHeroInstance::getSecSkillLevel(SecondarySkill skill) const { for(auto & elem : secSkills) @@ -176,8 +171,11 @@ bool CGHeroInstance::canLearnSkill() const return secSkills.size() < GameConstants::SKILL_PER_HERO; } -int CGHeroInstance::maxMovePoints(bool onLand) const +int CGHeroInstance::maxMovePoints(bool onLand, const TurnInfo * ti) const { + if(!ti) + ti = new TurnInfo(this); + int base; if(onLand) @@ -196,10 +194,10 @@ int CGHeroInstance::maxMovePoints(bool onLand) const } const Bonus::BonusType bt = onLand ? Bonus::LAND_MOVEMENT : Bonus::SEA_MOVEMENT; - const int bonus = valOfBonuses(Bonus::MOVEMENT) + valOfBonuses(bt); + const int bonus = ti->valOfBonuses(Bonus::MOVEMENT) + ti->valOfBonuses(bt); const int subtype = onLand ? SecondarySkill::LOGISTICS : SecondarySkill::NAVIGATION; - const double modifier = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, subtype) / 100.0; + const double modifier = ti->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, subtype) / 100.0; return int(base* (1+modifier)) + bonus; } @@ -1166,9 +1164,12 @@ CBonusSystemNode * CGHeroInstance::whereShouldBeAttached(CGameState *gs) return CArmedInstance::whereShouldBeAttached(gs); } -int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark /*= false*/) const +int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark /*= false*/, const TurnInfo * ti) const { - if(hasBonusOfType(Bonus::FREE_SHIP_BOARDING)) + if(!ti) + ti = new TurnInfo(this); + + if(ti->hasBonusOfType(Bonus::FREE_SHIP_BOARDING)) return (MPsBefore - basicCost) * static_cast(maxMovePoints(disembark)) / maxMovePoints(!disembark); return 0; //take all MPs otherwise diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 0c6056664..9b9b59d23 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -134,7 +134,6 @@ public: ui32 getLowestCreatureSpeed() const; int3 getPosition(bool h3m = false) const; //h3m=true - returns position of hero object; h3m=false - returns position of hero 'manifestation' si32 manaRegain() const; //how many points of mana can hero regain "naturally" in one day - const Bonus * getBonusAtTurn(const Bonus::BonusType &type, const int &turn = 0, const TBonusSubtype &subType = -1) const; int getCurrentLuck(int stack=-1, bool town=false) const; int getSpellCost(const CSpell *sp) const; //do not use during battles -> bonuses from army would be ignored @@ -161,8 +160,8 @@ public: void setSecSkillLevel(SecondarySkill which, int val, bool abs);// abs == 0 - changes by value; 1 - sets to value void levelUp(std::vector skills); - int maxMovePoints(bool onLand) const; - int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false) const; + int maxMovePoints(bool onLand, const TurnInfo * ti = nullptr) const; + int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false, const TurnInfo * ti = nullptr) const; static int3 convertPosition(int3 src, bool toh3m); //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest double getFightingStrength() const; // takes attack / defense skill into account diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 9032cc181..4fa958575 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1778,9 +1778,10 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo tmh.movePoints = h->movement; //check if destination tile is available - const bool canFly = h->getBonusAtTurn(Bonus::FLYING_MOVEMENT); - const bool canWalkOnSea = h->getBonusAtTurn(Bonus::WATER_WALKING); - const int cost = CPathfinderHelper::getMovementCost(h, h->getPosition(), hmpos, h->movement); + auto ti = new TurnInfo(h); + const bool canFly = ti->hasBonusOfType(Bonus::FLYING_MOVEMENT); + const bool canWalkOnSea = ti->hasBonusOfType(Bonus::WATER_WALKING); + const int cost = CPathfinderHelper::getMovementCost(h, h->getPosition(), hmpos, h->movement, ti); //it's a rock or blocked and not visitable tile //OR hero is on land and dest is water and (there is not present only one object - boat) @@ -1872,14 +1873,14 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo if(!transit && embarking) { - tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, false); + tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, false, ti); return doMove(TryMoveHero::EMBARK, IGNORE_GUARDS, DONT_VISIT_DEST, LEAVING_TILE); //attack guards on embarking? In H3 creatures on water had no zone of control at all } if(disembarking) { - tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, true); + tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, true, ti); return doMove(TryMoveHero::DISEMBARK, CHECK_FOR_GUARDS, VISIT_DEST, LEAVING_TILE); } From d2baa5b0d0d057f1a0eec8ea2eeb2283582865e0 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Thu, 12 Nov 2015 14:39:22 +0300 Subject: [PATCH 49/83] Pathfinding: move isLayerAvailable into TurnInfo --- lib/CPathfinder.cpp | 49 +++++++++++++++++++++++++-------------------- lib/CPathfinder.h | 8 ++++---- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index fc85d4118..014066bad 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -130,7 +130,7 @@ void CPathfinder::calculatePaths() if(!passOneTurnLimitCheck(cp->turns != turn)) continue; - if(!isLayerAvailable(i, turn)) + if(!hlp->isLayerAvailable(i)) continue; if(cp->layer != i && !isLayerTransitionPossible()) @@ -280,26 +280,6 @@ void CPathfinder::addTeleportExits(bool noTeleportExcludes) } } -bool CPathfinder::isLayerAvailable(const ELayer layer, const int turn) const -{ - switch(layer) - { - case ELayer::AIR: - if(!hlp->hasBonusOfType(Bonus::FLYING_MOVEMENT)) - return false; - - break; - - case ELayer::WATER: - if(!hlp->hasBonusOfType(Bonus::WATER_WALKING)) - return false; - - break; - } - - return true; -} - bool CPathfinder::isLayerTransitionPossible() const { /// No layer transition allowed when previous node action is BATTLE @@ -707,6 +687,26 @@ TurnInfo::TurnInfo(const CGHeroInstance * Hero, const int turn) bonuses = hero->getAllBonuses(Selector::days(turn), nullptr); } +bool TurnInfo::isLayerAvailable(const EPathfindingLayer layer) const +{ + switch(layer) + { + case EPathfindingLayer::AIR: + if(!hasBonusOfType(Bonus::FLYING_MOVEMENT)) + return false; + + break; + + case EPathfindingLayer::WATER: + if(!hasBonusOfType(Bonus::WATER_WALKING)) + return false; + + break; + } + + return true; +} + bool TurnInfo::hasBonusOfType(Bonus::BonusType type, int subtype) const { return bonuses->getFirst(Selector::type(type).And(Selector::subtype(subtype))); @@ -728,7 +728,7 @@ int TurnInfo::getMaxMovePoints(const EPathfindingLayer layer) const } CPathfinderHelper::CPathfinderHelper(const CGHeroInstance * Hero) - : turn(0), hero(Hero) + : turn(-1), hero(Hero) { turnsInfo.reserve(16); updateTurnInfo(); @@ -747,6 +747,11 @@ void CPathfinderHelper::updateTurnInfo(const int Turn) } } +bool CPathfinderHelper::isLayerAvailable(const EPathfindingLayer layer) const +{ + return turnsInfo[turn]->isLayerAvailable(layer); +} + const TurnInfo * CPathfinderHelper::getTurnInfo() const { return turnsInfo[turn]; diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 0a7083dc8..5c2377e48 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -166,7 +166,6 @@ private: void addNeighbours(const int3 & coord); void addTeleportExits(bool noTeleportExcludes = false); - bool isLayerAvailable(const ELayer layer, const int turn) const; bool isLayerTransitionPossible() const; bool isMovementToDestPossible() const; bool isMovementAfterDestPossible() const; @@ -200,6 +199,7 @@ struct DLL_LINKAGE TurnInfo mutable int maxMovePointsWater; TurnInfo(const CGHeroInstance * Hero, const int Turn = 0); + bool isLayerAvailable(const EPathfindingLayer layer) const; bool hasBonusOfType(const Bonus::BonusType type, const int subtype = -1) const; int valOfBonuses(const Bonus::BonusType type, const int subtype = -1) const; int getMaxMovePoints(const EPathfindingLayer layer) const; @@ -208,11 +208,9 @@ struct DLL_LINKAGE TurnInfo class DLL_LINKAGE CPathfinderHelper { public: - int turn; - const CGHeroInstance * hero; - CPathfinderHelper(const CGHeroInstance * Hero); void updateTurnInfo(const int turn = 0); + bool isLayerAvailable(const EPathfindingLayer layer) const; const TurnInfo * getTurnInfo() const; bool hasBonusOfType(const Bonus::BonusType type, const int subtype = -1) const; int getMaxMovePoints(const EPathfindingLayer layer) const; @@ -223,5 +221,7 @@ public: static int getMovementCost(const CGHeroInstance * h, const int3 & dst); private: + int turn; + const CGHeroInstance * hero; std::vector turnsInfo; }; From 0be9d21132a85a3ef12a88e4c26f01f30b7eeecc Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Fri, 13 Nov 2015 10:28:44 +0300 Subject: [PATCH 50/83] CPathfinder: add rule that hero in boat can't visit empty boats --- lib/CPathfinder.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 014066bad..9c5b0ed8a 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -387,6 +387,11 @@ bool CPathfinder::isMovementToDestPossible() const if(dObj->ID != Obj::BOAT && dObj->ID != Obj::HERO) return false; } + else if(dObj && dObj->ID == Obj::BOAT) + { + /// Hero in boat can't visit empty boats + return false; + } break; From 324cf5490c7d745d89952db4667cc70f464720bc Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Fri, 13 Nov 2015 23:07:56 +0300 Subject: [PATCH 51/83] CPathfinder: fix movement into guardian tile --- lib/CPathfinder.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 9c5b0ed8a..906281556 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -443,8 +443,8 @@ bool CPathfinder::isMovementAfterDestPossible() const break; case CGPathNode::BATTLE: - /// Movement after BATTLE action only possible to guardian tile - if(isDestinationGuardian()) + /// Movement after BATTLE action only possible from guarded tile to guardian tile + if(isDestinationGuarded()) return true; break; From aa59ad05eda62062aa971071dfe3b98e06cd6220 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Fri, 13 Nov 2015 23:32:52 +0300 Subject: [PATCH 52/83] CPathfinder: don't access map->guardingCreaturePositions directly --- lib/CPathfinder.cpp | 14 ++++---------- lib/CPathfinder.h | 1 - 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 906281556..042d7d79b 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -519,11 +519,6 @@ bool CPathfinder::isSourceInitialPosition() const return cp->coord == out.hpos; } -int3 CPathfinder::getSourceGuardPosition() const -{ - return gs->map->guardingCreaturePositions[cp->coord.x][cp->coord.y][cp->coord.z]; -} - bool CPathfinder::isSourceGuarded() const { /// Hero can move from guarded tile if movement started on that tile @@ -531,7 +526,7 @@ bool CPathfinder::isSourceGuarded() const /// - Map start with hero on guarded tile /// - Dimention door used /// TODO: check what happen when there is several guards - if(getSourceGuardPosition() != int3(-1, -1, -1) && !isSourceInitialPosition()) + if(gs->guardingCreaturePosition(cp->coord) != int3(-1, -1, -1) && !isSourceInitialPosition()) { return true; } @@ -541,7 +536,7 @@ bool CPathfinder::isSourceGuarded() const bool CPathfinder::isDestinationGuarded(const bool ignoreAccessibility) const { - if(gs->map->guardingCreaturePositions[dp->coord.x][dp->coord.y][dp->coord.z].valid() + if(gs->guardingCreaturePosition(dp->coord).valid() && (ignoreAccessibility || dp->accessible == CGPathNode::BLOCKVIS)) { return true; @@ -552,7 +547,7 @@ bool CPathfinder::isDestinationGuarded(const bool ignoreAccessibility) const bool CPathfinder::isDestinationGuardian() const { - return getSourceGuardPosition() == dp->coord; + return gs->guardingCreaturePosition(cp->coord) == dp->coord; } void CPathfinder::initializeGraph() @@ -633,8 +628,7 @@ CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const int3 & pos, } } } - else if(gs->map->guardingCreaturePositions[pos.x][pos.y][pos.z].valid() - && !tinfo->blocked) + else if(gs->guardingCreaturePosition(pos).valid() && !tinfo->blocked) { // Monster close by; blocked visit for battle. return CGPathNode::BLOCKVIS; diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 5c2377e48..97164cee8 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -172,7 +172,6 @@ private: CGPathNode::ENodeAction getDestAction() const; bool isSourceInitialPosition() const; - int3 getSourceGuardPosition() const; bool isSourceGuarded() const; bool isDestinationGuarded(const bool ignoreAccessibility = true) const; bool isDestinationGuardian() const; From 6dd957264409e1f71122a899eca7780965f09c57 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Mon, 16 Nov 2015 17:36:58 +0300 Subject: [PATCH 53/83] CPathfinder: cleanup checks for source node visitable object --- lib/CPathfinder.cpp | 59 ++++++++++++++++++++------------------------- lib/CPathfinder.h | 3 +-- 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 042d7d79b..1c4640d05 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -102,7 +102,7 @@ void CPathfinder::calculatePaths() pq.pop(); cp->locked = true; ct = &gs->map->getTile(cp->coord); - cObj = ct->topVisitableObj(cp->coord == out.hpos); + cObj = ct->topVisitableObj(isSourceInitialPosition()); int movement = cp->moveRemains, turn = cp->turns; hlp->updateTurnInfo(turn); @@ -173,23 +173,20 @@ void CPathfinder::calculatePaths() } //neighbours loop //just add all passable teleport exits - if(cObj && canVisitObject()) + addTeleportExits(); + for(auto & neighbour : neighbours) { - addTeleportExits(); - for(auto & neighbour : neighbours) - { - dp = out.getNode(neighbour, cp->layer); - if(dp->locked) - continue; + dp = out.getNode(neighbour, cp->layer); + if(dp->locked) + continue; - if(isBetterWay(movement, turn)) - { - dp->moveRemains = movement; - dp->turns = turn; - dp->theNodeBefore = cp; - dp->action = CGPathNode::NORMAL; - pq.push(dp); - } + if(isBetterWay(movement, turn)) + { + dp->moveRemains = movement; + dp->turns = turn; + dp->theNodeBefore = cp; + dp->action = CGPathNode::NORMAL; + pq.push(dp); } } } //queue loop @@ -200,18 +197,13 @@ void CPathfinder::addNeighbours(const int3 & coord) neighbours.clear(); std::vector tiles; CPathfinderHelper::getNeighbours(gs, *ct, coord, tiles, boost::logic::indeterminate, cp->layer == ELayer::SAIL); // TODO: find out if we still need "limitCoastSailing" option - if(canVisitObject()) + if(isSourceVisitableObj()) { - if(cObj) + for(int3 tile: tiles) { - for(int3 tile: tiles) - { - if(canMoveBetween(tile, cObj->visitablePos())) - neighbours.push_back(tile); - } + if(canMoveBetween(tile, cObj->visitablePos())) + neighbours.push_back(tile); } - else - vstd::concatenate(neighbours, tiles); } else vstd::concatenate(neighbours, tiles); @@ -219,9 +211,10 @@ void CPathfinder::addNeighbours(const int3 & coord) void CPathfinder::addTeleportExits(bool noTeleportExcludes) { - assert(cObj); - neighbours.clear(); + if(!isSourceVisitableObj()) + return; + auto isAllowedTeleportEntrance = [&](const CGTeleport * obj) -> bool { if(!gs->isTeleportEntrancePassable(obj, hero->tempOwner)) @@ -519,6 +512,12 @@ bool CPathfinder::isSourceInitialPosition() const return cp->coord == out.hpos; } +bool CPathfinder::isSourceVisitableObj() const +{ + /// Hero can't visit objects while walking on water or flying + return cObj != nullptr && (cp->layer == ELayer::LAND || cp->layer == ELayer::SAIL); +} + bool CPathfinder::isSourceGuarded() const { /// Hero can move from guarded tile if movement started on that tile @@ -674,12 +673,6 @@ bool CPathfinder::addTeleportWhirlpool(const CGWhirlpool * obj) const return options.useTeleportWhirlpool && obj; } -bool CPathfinder::canVisitObject() const -{ - //hero can't visit objects while walking on water or flying - return cp->layer == ELayer::LAND || cp->layer == ELayer::SAIL; -} - TurnInfo::TurnInfo(const CGHeroInstance * Hero, const int turn) : hero(Hero), maxMovePointsLand(-1), maxMovePointsWater(-1) { diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 97164cee8..c29223e6a 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -172,6 +172,7 @@ private: CGPathNode::ENodeAction getDestAction() const; bool isSourceInitialPosition() const; + bool isSourceVisitableObj() const; bool isSourceGuarded() const; bool isDestinationGuarded(const bool ignoreAccessibility = true) const; bool isDestinationGuardian() const; @@ -186,8 +187,6 @@ private: bool addTeleportOneWayRandom(const CGTeleport * obj) const; bool addTeleportWhirlpool(const CGWhirlpool * obj) const; - bool canVisitObject() const; - }; struct DLL_LINKAGE TurnInfo From 3185b64fb006424b911a044d9a0ca9b37fdfb1e4 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Mon, 16 Nov 2015 17:41:23 +0300 Subject: [PATCH 54/83] CPathfinder: rename cObj and dObjt to ctObj and dtObj This way it's more clear that it's pointer to object on tile and not path node. It's important because air layer nodes don't have visitable objects while tile under them can have one. --- lib/CPathfinder.cpp | 44 ++++++++++++++++++++++---------------------- lib/CPathfinder.h | 2 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 1c4640d05..b31b40601 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -102,7 +102,7 @@ void CPathfinder::calculatePaths() pq.pop(); cp->locked = true; ct = &gs->map->getTile(cp->coord); - cObj = ct->topVisitableObj(isSourceInitialPosition()); + ctObj = ct->topVisitableObj(isSourceInitialPosition()); int movement = cp->moveRemains, turn = cp->turns; hlp->updateTurnInfo(turn); @@ -117,7 +117,7 @@ void CPathfinder::calculatePaths() for(auto & neighbour : neighbours) { dt = &gs->map->getTile(neighbour); - dObj = dt->topVisitableObj(); + dtObj = dt->topVisitableObj(); for(ELayer i = ELayer::LAND; i <= ELayer::AIR; i.advance(1)) { dp = out.getNode(neighbour, i); @@ -201,7 +201,7 @@ void CPathfinder::addNeighbours(const int3 & coord) { for(int3 tile: tiles) { - if(canMoveBetween(tile, cObj->visitablePos())) + if(canMoveBetween(tile, ctObj->visitablePos())) neighbours.push_back(tile); } } @@ -235,7 +235,7 @@ void CPathfinder::addTeleportExits(bool noTeleportExcludes) return false; }; - const CGTeleport * sTileTeleport = dynamic_cast(cObj); + const CGTeleport * sTileTeleport = dynamic_cast(ctObj); if(isAllowedTeleportEntrance(sTileTeleport)) { for(auto objId : gs->getTeleportChannelExits(sTileTeleport->channel, hero->tempOwner)) @@ -256,15 +256,15 @@ void CPathfinder::addTeleportExits(bool noTeleportExcludes) } if(options.useCastleGate - && (cObj->ID == Obj::TOWN && cObj->subID == ETownType::INFERNO - && getPlayerRelations(hero->tempOwner, cObj->tempOwner) != PlayerRelations::ENEMIES)) + && (ctObj->ID == Obj::TOWN && ctObj->subID == ETownType::INFERNO + && getPlayerRelations(hero->tempOwner, ctObj->tempOwner) != PlayerRelations::ENEMIES)) { /// TODO: Find way to reuse CPlayerSpecificInfoCallback::getTownsInfo /// This may be handy if we allow to use teleportation to friendly towns auto towns = gs->getPlayer(hero->tempOwner)->towns; for(const auto & town : towns) { - if(town->id != cObj->id && town->visitingHero == nullptr + if(town->id != ctObj->id && town->visitingHero == nullptr && town->hasBuilt(BuildingID::CASTLE_GATE, ETownType::INFERNO)) { neighbours.push_back(town->visitablePos()); @@ -374,13 +374,13 @@ bool CPathfinder::isMovementToDestPossible() const if(cp->layer == ELayer::LAND) { - if(!dObj) + if(!dtObj) return false; - if(dObj->ID != Obj::BOAT && dObj->ID != Obj::HERO) + if(dtObj->ID != Obj::BOAT && dtObj->ID != Obj::HERO) return false; } - else if(dObj && dObj->ID == Obj::BOAT) + else if(dtObj && dtObj->ID == Obj::BOAT) { /// Hero in boat can't visit empty boats return false; @@ -409,11 +409,11 @@ bool CPathfinder::isMovementAfterDestPossible() const case CGPathNode::VISIT: /// For now we only add visitable tile into queue when it's teleporter that allow transit /// Movement from visitable tile when hero is standing on it is possible into any layer - if(CGTeleport::isTeleport(dObj)) + if(CGTeleport::isTeleport(dtObj)) { /// For now we'll always allow transit over teleporters /// Transit over whirlpools only allowed when hero protected - auto whirlpool = dynamic_cast(dObj); + auto whirlpool = dynamic_cast(dtObj); if(!whirlpool || options.useTeleportWhirlpool) return true; } @@ -460,34 +460,34 @@ CGPathNode::ENodeAction CPathfinder::getDestAction() const } case ELayer::SAIL: - if(dObj) + if(dtObj) { - auto objRel = getPlayerRelations(dObj->tempOwner, hero->tempOwner); + auto objRel = getPlayerRelations(dtObj->tempOwner, hero->tempOwner); - if(dObj->ID == Obj::BOAT) + if(dtObj->ID == Obj::BOAT) action = CGPathNode::EMBARK; - else if(dObj->ID == Obj::HERO) + else if(dtObj->ID == Obj::HERO) { if(objRel == PlayerRelations::ENEMIES) action = CGPathNode::BATTLE; else action = CGPathNode::BLOCKING_VISIT; } - else if(dObj->ID == Obj::TOWN && objRel == PlayerRelations::ENEMIES) + else if(dtObj->ID == Obj::TOWN && objRel == PlayerRelations::ENEMIES) { - const CGTownInstance * townObj = dynamic_cast(dObj); + const CGTownInstance * townObj = dynamic_cast(dtObj); if(townObj->armedGarrison()) action = CGPathNode::BATTLE; } - else if(dObj->ID == Obj::GARRISON || dObj->ID == Obj::GARRISON2) + else if(dtObj->ID == Obj::GARRISON || dtObj->ID == Obj::GARRISON2) { - const CGGarrison * garrisonObj = dynamic_cast(dObj); + const CGGarrison * garrisonObj = dynamic_cast(dtObj); if((garrisonObj->stacksCount() && objRel == PlayerRelations::ENEMIES) || isDestinationGuarded(true)) action = CGPathNode::BATTLE; } else if(isDestinationGuardian()) action = CGPathNode::BATTLE; - else if(dObj->blockVisit && (!options.useCastleGate || dObj->ID != Obj::TOWN)) + else if(dtObj->blockVisit && (!options.useCastleGate || dtObj->ID != Obj::TOWN)) action = CGPathNode::BLOCKING_VISIT; if(action == CGPathNode::NORMAL) @@ -515,7 +515,7 @@ bool CPathfinder::isSourceInitialPosition() const bool CPathfinder::isSourceVisitableObj() const { /// Hero can't visit objects while walking on water or flying - return cObj != nullptr && (cp->layer == ELayer::LAND || cp->layer == ELayer::SAIL); + return ctObj != nullptr && (cp->layer == ELayer::LAND || cp->layer == ELayer::SAIL); } bool CPathfinder::isSourceGuarded() const diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index c29223e6a..cf9c0e42b 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -160,7 +160,7 @@ private: 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 - const CGObjectInstance * cObj, * dObj; + const CGObjectInstance * ctObj, * dtObj; CGPathNode::ENodeAction destAction; void addNeighbours(const int3 & coord); From 0949283cb9f66ea3cfcf6ba908079c0deffa9f17 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Mon, 16 Nov 2015 18:43:02 +0300 Subject: [PATCH 55/83] Pathfinding: pass PathfinderOptions to helper and avoid changing them Original idea behind options is that options should only be set on pathfinder object creation (or via some special method in case pathfinder become persistent). --- lib/CPathfinder.cpp | 33 +++++++++++++++++++++------------ lib/CPathfinder.h | 5 ++++- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index b31b40601..be96bbeb6 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -20,13 +20,13 @@ CPathfinder::PathfinderOptions::PathfinderOptions() { - useFlying = false; - useWaterWalking = false; + useFlying = true; + useWaterWalking = true; useEmbarkAndDisembark = true; useTeleportTwoWay = true; useTeleportOneWay = true; useTeleportOneWayRandom = false; - useTeleportWhirlpool = false; + useTeleportWhirlpool = true; useCastleGate = false; @@ -49,13 +49,7 @@ CPathfinder::CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstan throw std::runtime_error("Wrong checksum"); } - hlp = make_unique(hero); - if(hlp->hasBonusOfType(Bonus::FLYING_MOVEMENT)) - options.useFlying = true; - if(hlp->hasBonusOfType(Bonus::WATER_WALKING)) - options.useWaterWalking = true; - if(hlp->hasBonusOfType(Bonus::WHIRLPOOL_PROTECTION)) - options.useTeleportWhirlpool = true; + hlp = make_unique(hero, options); initializeGraph(); neighbours.reserve(16); @@ -719,8 +713,8 @@ int TurnInfo::getMaxMovePoints(const EPathfindingLayer layer) const return layer == EPathfindingLayer::SAIL ? maxMovePointsWater : maxMovePointsLand; } -CPathfinderHelper::CPathfinderHelper(const CGHeroInstance * Hero) - : turn(-1), hero(Hero) +CPathfinderHelper::CPathfinderHelper(const CGHeroInstance * Hero, const CPathfinder::PathfinderOptions & Options) + : turn(-1), hero(Hero), options(Options) { turnsInfo.reserve(16); updateTurnInfo(); @@ -741,6 +735,21 @@ void CPathfinderHelper::updateTurnInfo(const int Turn) bool CPathfinderHelper::isLayerAvailable(const EPathfindingLayer layer) const { + switch(layer) + { + case EPathfindingLayer::AIR: + if(!options.useFlying) + return false; + + break; + + case EPathfindingLayer::WATER: + if(!options.useWaterWalking) + return false; + + break; + } + return turnsInfo[turn]->isLayerAvailable(layer); } diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index cf9c0e42b..6f325d204 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -94,6 +94,8 @@ struct DLL_LINKAGE CPathsInfo class CPathfinder : private CGameInfoCallback { public: + friend class CPathfinderHelper; + CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstance * _hero); void calculatePaths(); //calculates possible paths for hero, uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists @@ -206,7 +208,7 @@ struct DLL_LINKAGE TurnInfo class DLL_LINKAGE CPathfinderHelper { public: - CPathfinderHelper(const CGHeroInstance * Hero); + CPathfinderHelper(const CGHeroInstance * Hero, const CPathfinder::PathfinderOptions & Options); void updateTurnInfo(const int turn = 0); bool isLayerAvailable(const EPathfindingLayer layer) const; const TurnInfo * getTurnInfo() const; @@ -222,4 +224,5 @@ private: int turn; const CGHeroInstance * hero; std::vector turnsInfo; + const CPathfinder::PathfinderOptions & options; }; From 8f72d7324132c12639190d68af79903132f842f7 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Mon, 16 Nov 2015 19:14:18 +0300 Subject: [PATCH 56/83] CPathfinder: update teleport code and use TurnInfo for whirlpools --- lib/CPathfinder.cpp | 56 ++++++++++++++++++++++----------------------- lib/CPathfinder.h | 3 ++- 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index be96bbeb6..e94479fbc 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -203,36 +203,16 @@ void CPathfinder::addNeighbours(const int3 & coord) vstd::concatenate(neighbours, tiles); } -void CPathfinder::addTeleportExits(bool noTeleportExcludes) +void CPathfinder::addTeleportExits() { neighbours.clear(); if(!isSourceVisitableObj()) return; - auto isAllowedTeleportEntrance = [&](const CGTeleport * obj) -> bool + const CGTeleport * objTeleport = dynamic_cast(ctObj); + if(isAllowedTeleportEntrance(objTeleport)) { - if(!gs->isTeleportEntrancePassable(obj, hero->tempOwner)) - return false; - - if(noTeleportExcludes) - return true; - - auto whirlpool = dynamic_cast(obj); - if(whirlpool) - { - if(addTeleportWhirlpool(whirlpool)) - return true; - } - else if(addTeleportTwoWay(obj) || addTeleportOneWay(obj) || addTeleportOneWayRandom(obj)) - return true; - - return false; - }; - - const CGTeleport * sTileTeleport = dynamic_cast(ctObj); - if(isAllowedTeleportEntrance(sTileTeleport)) - { - for(auto objId : gs->getTeleportChannelExits(sTileTeleport->channel, hero->tempOwner)) + for(auto objId : gs->getTeleportChannelExits(objTeleport->channel, hero->tempOwner)) { auto obj = getObj(objId); if(dynamic_cast(obj)) @@ -401,18 +381,19 @@ bool CPathfinder::isMovementAfterDestPossible() const /// TODO: Investigate what kind of limitation is possible to apply on movement from visitable tiles /// Likely in many cases we don't need to add visitable tile to queue when hero don't fly case CGPathNode::VISIT: + { /// For now we only add visitable tile into queue when it's teleporter that allow transit /// Movement from visitable tile when hero is standing on it is possible into any layer - if(CGTeleport::isTeleport(dtObj)) + const CGTeleport * objTeleport = dynamic_cast(dtObj); + if(isAllowedTeleportEntrance(objTeleport)) { /// For now we'll always allow transit over teleporters /// Transit over whirlpools only allowed when hero protected - auto whirlpool = dynamic_cast(dtObj); - if(!whirlpool || options.useTeleportWhirlpool) - return true; + return true; } break; + } case CGPathNode::NORMAL: return true; @@ -635,6 +616,23 @@ bool CPathfinder::canMoveBetween(const int3 & a, const int3 & b) const return gs->checkForVisitableDir(a, b); } +bool CPathfinder::isAllowedTeleportEntrance(const CGTeleport * obj) const +{ + if(!obj || !gs->isTeleportEntrancePassable(obj, hero->tempOwner)) + return false; + + auto whirlpool = dynamic_cast(obj); + if(whirlpool) + { + if(addTeleportWhirlpool(whirlpool)) + return true; + } + else if(addTeleportTwoWay(obj) || addTeleportOneWay(obj) || addTeleportOneWayRandom(obj)) + return true; + + return false; +} + bool CPathfinder::addTeleportTwoWay(const CGTeleport * obj) const { return options.useTeleportTwoWay && gs->isTeleportChannelBidirectional(obj->channel, hero->tempOwner); @@ -664,7 +662,7 @@ bool CPathfinder::addTeleportOneWayRandom(const CGTeleport * obj) const bool CPathfinder::addTeleportWhirlpool(const CGWhirlpool * obj) const { - return options.useTeleportWhirlpool && obj; + return options.useTeleportWhirlpool && hlp->hasBonusOfType(Bonus::WHIRLPOOL_PROTECTION) && obj; } TurnInfo::TurnInfo(const CGHeroInstance * Hero, const int turn) diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 6f325d204..f3cd2f8c0 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -166,7 +166,7 @@ private: CGPathNode::ENodeAction destAction; void addNeighbours(const int3 & coord); - void addTeleportExits(bool noTeleportExcludes = false); + void addTeleportExits(); bool isLayerTransitionPossible() const; bool isMovementToDestPossible() const; @@ -184,6 +184,7 @@ private: CGPathNode::EAccessibility evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo) const; bool canMoveBetween(const int3 & a, const int3 & b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility) + bool isAllowedTeleportEntrance(const CGTeleport * obj) const; bool addTeleportTwoWay(const CGTeleport * obj) const; bool addTeleportOneWay(const CGTeleport * obj) const; bool addTeleportOneWayRandom(const CGTeleport * obj) const; From d524b4eabe9d60ac2685a144d080966e5655734b Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Mon, 16 Nov 2015 19:36:15 +0300 Subject: [PATCH 57/83] CPathfinder: get rid of addNeighbours coord argument --- lib/CPathfinder.cpp | 6 +++--- lib/CPathfinder.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index e94479fbc..13a33ee21 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -107,7 +107,7 @@ void CPathfinder::calculatePaths() } //add accessible neighbouring nodes to the queue - addNeighbours(cp->coord); + addNeighbours(); for(auto & neighbour : neighbours) { dt = &gs->map->getTile(neighbour); @@ -186,11 +186,11 @@ void CPathfinder::calculatePaths() } //queue loop } -void CPathfinder::addNeighbours(const int3 & coord) +void CPathfinder::addNeighbours() { neighbours.clear(); std::vector tiles; - CPathfinderHelper::getNeighbours(gs, *ct, coord, tiles, boost::logic::indeterminate, cp->layer == ELayer::SAIL); // TODO: find out if we still need "limitCoastSailing" option + CPathfinderHelper::getNeighbours(gs, *ct, cp->coord, tiles, boost::logic::indeterminate, cp->layer == ELayer::SAIL); // TODO: find out if we still need "limitCoastSailing" option if(isSourceVisitableObj()) { for(int3 tile: tiles) diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index f3cd2f8c0..d3a0c777b 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -165,7 +165,7 @@ private: const CGObjectInstance * ctObj, * dtObj; CGPathNode::ENodeAction destAction; - void addNeighbours(const int3 & coord); + void addNeighbours(); void addTeleportExits(); bool isLayerTransitionPossible() const; From 578aa2acd46676b5c12de66df55bc829af63dcdc Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Mon, 16 Nov 2015 21:22:11 +0300 Subject: [PATCH 58/83] Pathfinding: don't use gamestate directly as it's not needed --- lib/CPathfinder.cpp | 42 +++++++++++++++++++++--------------------- lib/CPathfinder.h | 2 +- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 13a33ee21..6204fcad5 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -43,7 +43,7 @@ CPathfinder::CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstan out.hero = hero; out.hpos = hero->getPosition(false); - if(!gs->map->isInTheMap(out.hpos)/* || !gs->map->isInTheMap(dest)*/) //check input + if(!isInTheMap(out.hpos)/* || !gs->map->isInTheMap(dest)*/) //check input { logGlobal->errorStream() << "CGameState::calculatePaths: Hero outside the gs->map? How dare you..."; throw std::runtime_error("Wrong checksum"); @@ -95,7 +95,7 @@ void CPathfinder::calculatePaths() cp = pq.top(); pq.pop(); cp->locked = true; - ct = &gs->map->getTile(cp->coord); + ct = getTile(cp->coord); ctObj = ct->topVisitableObj(isSourceInitialPosition()); int movement = cp->moveRemains, turn = cp->turns; @@ -110,7 +110,7 @@ void CPathfinder::calculatePaths() addNeighbours(); for(auto & neighbour : neighbours) { - dt = &gs->map->getTile(neighbour); + dt = getTile(neighbour); dtObj = dt->topVisitableObj(); for(ELayer i = ELayer::LAND; i <= ELayer::AIR; i.advance(1)) { @@ -190,7 +190,7 @@ void CPathfinder::addNeighbours() { neighbours.clear(); std::vector tiles; - CPathfinderHelper::getNeighbours(gs, *ct, cp->coord, tiles, boost::logic::indeterminate, cp->layer == ELayer::SAIL); // TODO: find out if we still need "limitCoastSailing" option + CPathfinderHelper::getNeighbours(gs->map, *ct, cp->coord, tiles, boost::logic::indeterminate, cp->layer == ELayer::SAIL); // TODO: find out if we still need "limitCoastSailing" option if(isSourceVisitableObj()) { for(int3 tile: tiles) @@ -212,7 +212,7 @@ void CPathfinder::addTeleportExits() const CGTeleport * objTeleport = dynamic_cast(ctObj); if(isAllowedTeleportEntrance(objTeleport)) { - for(auto objId : gs->getTeleportChannelExits(objTeleport->channel, hero->tempOwner)) + for(auto objId : getTeleportChannelExits(objTeleport->channel, hero->tempOwner)) { auto obj = getObj(objId); if(dynamic_cast(obj)) @@ -220,7 +220,7 @@ void CPathfinder::addTeleportExits() auto pos = obj->getBlockedPos(); for(auto p : pos) { - if(gs->getTile(p)->topVisitableId() == obj->ID) + if(getTile(p)->topVisitableId() == obj->ID) neighbours.push_back(p); } } @@ -235,7 +235,7 @@ void CPathfinder::addTeleportExits() { /// TODO: Find way to reuse CPlayerSpecificInfoCallback::getTownsInfo /// This may be handy if we allow to use teleportation to friendly towns - auto towns = gs->getPlayer(hero->tempOwner)->towns; + auto towns = getPlayer(hero->tempOwner)->towns; for(const auto & town : towns) { if(town->id != ctObj->id && town->visitingHero == nullptr @@ -500,7 +500,7 @@ bool CPathfinder::isSourceGuarded() const /// - Map start with hero on guarded tile /// - Dimention door used /// TODO: check what happen when there is several guards - if(gs->guardingCreaturePosition(cp->coord) != int3(-1, -1, -1) && !isSourceInitialPosition()) + if(guardingCreaturePosition(cp->coord) != int3(-1, -1, -1) && !isSourceInitialPosition()) { return true; } @@ -510,7 +510,7 @@ bool CPathfinder::isSourceGuarded() const bool CPathfinder::isDestinationGuarded(const bool ignoreAccessibility) const { - if(gs->guardingCreaturePosition(dp->coord).valid() + if(guardingCreaturePosition(dp->coord).valid() && (ignoreAccessibility || dp->accessible == CGPathNode::BLOCKVIS)) { return true; @@ -521,7 +521,7 @@ bool CPathfinder::isDestinationGuarded(const bool ignoreAccessibility) const bool CPathfinder::isDestinationGuardian() const { - return gs->guardingCreaturePosition(cp->coord) == dp->coord; + return guardingCreaturePosition(cp->coord) == dp->coord; } void CPathfinder::initializeGraph() @@ -547,7 +547,7 @@ void CPathfinder::initializeGraph() { for(pos.z=0; pos.z < out.sizes.z; ++pos.z) { - const TerrainTile * tinfo = &gs->map->getTile(pos); + const TerrainTile * tinfo = getTile(pos); switch(tinfo->terType) { case ETerrainType::ROCK: @@ -602,7 +602,7 @@ CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const int3 & pos, } } } - else if(gs->guardingCreaturePosition(pos).valid() && !tinfo->blocked) + else if(guardingCreaturePosition(pos).valid() && !tinfo->blocked) { // Monster close by; blocked visit for battle. return CGPathNode::BLOCKVIS; @@ -618,7 +618,7 @@ bool CPathfinder::canMoveBetween(const int3 & a, const int3 & b) const bool CPathfinder::isAllowedTeleportEntrance(const CGTeleport * obj) const { - if(!obj || !gs->isTeleportEntrancePassable(obj, hero->tempOwner)) + if(!obj || !isTeleportEntrancePassable(obj, hero->tempOwner)) return false; auto whirlpool = dynamic_cast(obj); @@ -635,14 +635,14 @@ bool CPathfinder::isAllowedTeleportEntrance(const CGTeleport * obj) const bool CPathfinder::addTeleportTwoWay(const CGTeleport * obj) const { - return options.useTeleportTwoWay && gs->isTeleportChannelBidirectional(obj->channel, hero->tempOwner); + return options.useTeleportTwoWay && isTeleportChannelBidirectional(obj->channel, hero->tempOwner); } bool CPathfinder::addTeleportOneWay(const CGTeleport * obj) const { if(options.useTeleportOneWay && isTeleportChannelUnidirectional(obj->channel, hero->tempOwner)) { - auto passableExits = CGTeleport::getPassableExits(gs, hero, gs->getTeleportChannelExits(obj->channel, hero->tempOwner)); + auto passableExits = CGTeleport::getPassableExits(gs, hero, getTeleportChannelExits(obj->channel, hero->tempOwner)); if(passableExits.size() == 1) return true; } @@ -653,7 +653,7 @@ bool CPathfinder::addTeleportOneWayRandom(const CGTeleport * obj) const { if(options.useTeleportOneWayRandom && isTeleportChannelUnidirectional(obj->channel, hero->tempOwner)) { - auto passableExits = CGTeleport::getPassableExits(gs, hero, gs->getTeleportChannelExits(obj->channel, hero->tempOwner)); + auto passableExits = CGTeleport::getPassableExits(gs, hero, getTeleportChannelExits(obj->channel, hero->tempOwner)); if(passableExits.size() > 1) return true; } @@ -766,7 +766,7 @@ int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer layer) const return turnsInfo[turn]->getMaxMovePoints(layer); } -void CPathfinderHelper::getNeighbours(CGameState * gs, const TerrainTile & srct, const int3 & tile, std::vector & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing) +void CPathfinderHelper::getNeighbours(const CMap * map, const TerrainTile & srct, const int3 & tile, std::vector & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing) { 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) }; @@ -775,10 +775,10 @@ void CPathfinderHelper::getNeighbours(CGameState * gs, const TerrainTile & srct, for(auto & dir : dirs) { const int3 hlp = tile + dir; - if(!gs->isInTheMap(hlp)) + if(!map->isInTheMap(hlp)) continue; - const TerrainTile & hlpt = gs->map->getTile(hlp); + 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) @@ -793,7 +793,7 @@ void CPathfinderHelper::getNeighbours(CGameState * gs, const TerrainTile & srct, hlp1.x += dir.x; hlp2.y += dir.y; - if(gs->map->getTile(hlp1).terType != ETerrainType::WATER || gs->map->getTile(hlp2).terType != ETerrainType::WATER) + if(map->getTile(hlp1).terType != ETerrainType::WATER || map->getTile(hlp2).terType != ETerrainType::WATER) continue; } @@ -844,7 +844,7 @@ int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & sr { std::vector vec; vec.reserve(8); //optimization - getNeighbours(h->cb->gameState(), *d, dst, vec, s->terType != ETerrainType::WATER, true); + getNeighbours(h->cb->gameState()->map, *d, dst, vec, s->terType != ETerrainType::WATER, true); for(auto & elem : vec) { int fcost = getMovementCost(h, dst, elem, left, ti, false); diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index d3a0c777b..64c80dee5 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -216,7 +216,7 @@ public: bool hasBonusOfType(const Bonus::BonusType type, const int subtype = -1) const; int getMaxMovePoints(const EPathfindingLayer layer) const; - static void getNeighbours(CGameState * gs, const TerrainTile & srct, const int3 & tile, std::vector & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing); + static void getNeighbours(const CMap * map, const TerrainTile & srct, const int3 & tile, std::vector & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing); static int getMovementCost(const CGHeroInstance * h, const int3 & src, const int3 & dst, const int remainingMovePoints =- 1, const TurnInfo * ti = nullptr, const bool checkLast = true); static int getMovementCost(const CGHeroInstance * h, const int3 & dst); From 73d86877854b673bd779090f071ddb312237e3bb Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Mon, 16 Nov 2015 23:07:36 +0300 Subject: [PATCH 59/83] CPathfinder: add some comments and TODO for future changes --- lib/CPathfinder.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 6204fcad5..b37da32ce 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -434,6 +434,8 @@ CGPathNode::ENodeAction CPathfinder::getDestAction() const break; } + /// don't break - next case shared for both land and sail layers + case ELayer::SAIL: if(dtObj) { @@ -510,6 +512,8 @@ bool CPathfinder::isSourceGuarded() const bool CPathfinder::isDestinationGuarded(const bool ignoreAccessibility) const { + /// isDestinationGuarded is exception needed for garrisons. + /// When monster standing behind garrison it's visitable and guarded at the same time. if(guardingCreaturePosition(dp->coord).valid() && (ignoreAccessibility || dp->accessible == CGPathNode::BLOCKVIS)) { @@ -814,7 +818,14 @@ int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & sr ti = new TurnInfo(h); auto s = h->cb->getTile(src), d = h->cb->getTile(dst); + + /// TODO: by the original game rules hero shouldn't be affected by terrain penalty while flying. + /// Also flying movement only has penalty when player moving over blocked tiles. + /// So if you only have base flying with 40% penalty you can still ignore terrain penalty while having zero flying penalty. int ret = h->getTileCost(*d, *s, ti); + /// Unfortunately this can't be implemented yet as server don't know when player flying and when he's not. + /// Difference in cost calculation on client and server is much worse than incorrect cost. + /// So this one is waiting till server going to use pathfinder rules for path validation. if(d->blocked && ti->hasBonusOfType(Bonus::FLYING_MOVEMENT)) { @@ -839,6 +850,8 @@ int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & sr return remainingMovePoints; } + /// TODO: This part need rework in order to work properly with flying and water walking + /// Currently it's only work properly for normal movement or sailing int left = remainingMovePoints-ret; if(checkLast && left > 0 && remainingMovePoints-ret < 250) //it might be the last tile - if no further move possible we take all move points { From c2ba3e3faf63f9ca7a427c819a218ebdeb757b68 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Tue, 17 Nov 2015 02:41:31 +0300 Subject: [PATCH 60/83] CPathfinderHelper: very firts pass over getNeighbours --- lib/CPathfinder.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index b37da32ce..f57a08af2 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -190,7 +190,8 @@ void CPathfinder::addNeighbours() { neighbours.clear(); std::vector tiles; - CPathfinderHelper::getNeighbours(gs->map, *ct, cp->coord, tiles, boost::logic::indeterminate, cp->layer == ELayer::SAIL); // TODO: find out if we still need "limitCoastSailing" option + tiles.reserve(8); + CPathfinderHelper::getNeighbours(gs->map, *ct, cp->coord, tiles, boost::logic::indeterminate, cp->layer == ELayer::SAIL); if(isSourceVisitableObj()) { for(int3 tile: tiles) @@ -464,7 +465,7 @@ CGPathNode::ENodeAction CPathfinder::getDestAction() const } else if(isDestinationGuardian()) action = CGPathNode::BATTLE; - else if(dtObj->blockVisit && (!options.useCastleGate || dtObj->ID != Obj::TOWN)) + else if(dtObj->blockVisit && !(options.useCastleGate && dtObj->ID == Obj::TOWN)) action = CGPathNode::BLOCKING_VISIT; if(action == CGPathNode::NORMAL) @@ -772,10 +773,12 @@ int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer layer) const void CPathfinderHelper::getNeighbours(const CMap * map, const TerrainTile & srct, const int3 & tile, std::vector & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing) { - 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) }; + static const int3 dirs[] = { + int3(-1, +1, +0), int3(0, +1, +0), int3(+1, +1, +0), + int3(-1, +0, +0), /* source pos */ int3(+1, +0, +0), + int3(-1, -1, +0), int3(0, -1, +0), int3(+1, -1, +0) + }; - //vec.reserve(8); //optimization for(auto & dir : dirs) { const int3 hlp = tile + dir; @@ -783,6 +786,8 @@ void CPathfinderHelper::getNeighbours(const CMap * map, const TerrainTile & srct continue; const TerrainTile & hlpt = map->getTile(hlp); + if(hlpt.terType == ETerrainType::ROCK) + continue; // //we cannot visit things from blocked tiles // if(srct.blocked && !srct.visitable && hlpt.visitable && srct.blockingObjects.front()->ID != HEROI_TYPE) @@ -790,6 +795,7 @@ void CPathfinderHelper::getNeighbours(const CMap * map, const TerrainTile & srct // continue; // } + /// Following condition let us avoid diagonal movement over coast when sailing if(srct.terType == ETerrainType::WATER && limitCoastSailing && hlpt.terType == ETerrainType::WATER && dir.x && dir.y) //diagonal move through water { int3 hlp1 = tile, @@ -801,8 +807,7 @@ void CPathfinderHelper::getNeighbours(const CMap * map, const TerrainTile & srct continue; } - if((indeterminate(onLand) || onLand == (hlpt.terType!=ETerrainType::WATER) ) - && hlpt.terType != ETerrainType::ROCK) + if(indeterminate(onLand) || onLand == (hlpt.terType != ETerrainType::WATER)) { vec.push_back(hlp); } From fe12b8f664a6f38b71cf914a6326d6cb81dc1253 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Tue, 17 Nov 2015 03:59:02 +0300 Subject: [PATCH 61/83] Pathfinding: re-introduce EAccessibility::FLYABLE That let us get rid of really hacky code in initializeGraph and also fix flying over tiles that aren't visible. --- lib/CPathfinder.cpp | 119 +++++++++++++++++++++++++------------------- lib/CPathfinder.h | 3 +- 2 files changed, 70 insertions(+), 52 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index f57a08af2..ab2146796 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -321,10 +321,13 @@ bool CPathfinder::isLayerTransitionPossible() const bool CPathfinder::isMovementToDestPossible() const { + if(dp->accessible == CGPathNode::BLOCKED) + return false; + switch(dp->layer) { case ELayer::LAND: - if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED) + if(!canMoveBetween(cp->coord, dp->coord)) return false; if(isSourceGuarded()) { @@ -338,7 +341,7 @@ bool CPathfinder::isMovementToDestPossible() const break; case ELayer::SAIL: - if(!canMoveBetween(cp->coord, dp->coord) || dp->accessible == CGPathNode::BLOCKED) + if(!canMoveBetween(cp->coord, dp->coord)) return false; if(isSourceGuarded()) { @@ -531,17 +534,10 @@ bool CPathfinder::isDestinationGuardian() const void CPathfinder::initializeGraph() { - auto updateNode = [&](int3 pos, ELayer layer, const TerrainTile * tinfo, bool blockNotAccessible) + auto updateNode = [&](int3 pos, ELayer layer, const TerrainTile * tinfo) { auto node = out.getNode(pos, layer); - - auto accessibility = evaluateAccessibility(pos, tinfo); - /// TODO: Probably this shouldn't be handled by initializeGraph - if(blockNotAccessible - && (accessibility != CGPathNode::ACCESSIBLE || tinfo->terType == ETerrainType::WATER)) - { - accessibility = CGPathNode::BLOCKED; - } + auto accessibility = evaluateAccessibility(pos, tinfo, layer); node->update(pos, layer, accessibility); }; @@ -555,65 +551,86 @@ void CPathfinder::initializeGraph() const TerrainTile * tinfo = getTile(pos); switch(tinfo->terType) { - case ETerrainType::ROCK: - break; - case ETerrainType::WATER: - updateNode(pos, ELayer::SAIL, tinfo, false); - if(options.useFlying) - updateNode(pos, ELayer::AIR, tinfo, true); - if(options.useWaterWalking) - updateNode(pos, ELayer::WATER, tinfo, false); - break; - default: - updateNode(pos, ELayer::LAND, tinfo, false); - if(options.useFlying) - updateNode(pos, ELayer::AIR, tinfo, true); - break; + case ETerrainType::ROCK: + break; + + case ETerrainType::WATER: + updateNode(pos, ELayer::SAIL, tinfo); + if(options.useFlying) + updateNode(pos, ELayer::AIR, tinfo); + if(options.useWaterWalking) + updateNode(pos, ELayer::WATER, tinfo); + break; + + default: + updateNode(pos, ELayer::LAND, tinfo); + if(options.useFlying) + updateNode(pos, ELayer::AIR, tinfo); + break; } } } } } -CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo) const +CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo, const ELayer layer) const { - CGPathNode::EAccessibility ret = (tinfo->blocked ? CGPathNode::BLOCKED : CGPathNode::ACCESSIBLE); - if(tinfo->terType == ETerrainType::ROCK || !isVisible(pos, hero->tempOwner)) return CGPathNode::BLOCKED; - if(tinfo->visitable) + switch(layer) { - if(tinfo->visitableObjects.front()->ID == Obj::SANCTUARY && tinfo->visitableObjects.back()->ID == Obj::HERO && tinfo->visitableObjects.back()->tempOwner != hero->tempOwner) //non-owned hero stands on Sanctuary + case ELayer::LAND: + case ELayer::SAIL: + if(tinfo->visitable) { - return CGPathNode::BLOCKED; - } - else - { - for(const CGObjectInstance * obj : tinfo->visitableObjects) + if(tinfo->visitableObjects.front()->ID == Obj::SANCTUARY && tinfo->visitableObjects.back()->ID == Obj::HERO && tinfo->visitableObjects.back()->tempOwner != hero->tempOwner) //non-owned hero stands on Sanctuary { - if(obj->passableFor(hero->tempOwner)) + return CGPathNode::BLOCKED; + } + else + { + for(const CGObjectInstance * obj : tinfo->visitableObjects) { - ret = CGPathNode::ACCESSIBLE; - } - else if(obj->blockVisit) - { - return CGPathNode::BLOCKVIS; - } - else if(obj->ID != Obj::EVENT) //pathfinder should ignore placed events - { - ret = CGPathNode::VISITABLE; + if(obj->passableFor(hero->tempOwner)) + { + return CGPathNode::ACCESSIBLE; + } + else if(obj->blockVisit) + { + return CGPathNode::BLOCKVIS; + } + else if(obj->ID != Obj::EVENT) //pathfinder should ignore placed events + { + return CGPathNode::VISITABLE; + } } } } - } - else if(guardingCreaturePosition(pos).valid() && !tinfo->blocked) - { - // Monster close by; blocked visit for battle. - return CGPathNode::BLOCKVIS; + else if(guardingCreaturePosition(pos).valid() && !tinfo->blocked) + { + // Monster close by; blocked visit for battle + return CGPathNode::BLOCKVIS; + } + else if(tinfo->blocked) + return CGPathNode::BLOCKED; + + break; + + case ELayer::WATER: + if(tinfo->blocked || tinfo->terType != ETerrainType::WATER) + return CGPathNode::BLOCKED; + + break; + + case ELayer::AIR: + if(tinfo->blocked || tinfo->terType == ETerrainType::WATER) + return CGPathNode::FLYABLE; + + break; } - return ret; + return CGPathNode::ACCESSIBLE; } bool CPathfinder::canMoveBetween(const int3 & a, const int3 & b) const diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 64c80dee5..51a47af41 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -43,6 +43,7 @@ struct DLL_LINKAGE CGPathNode 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 + FLYABLE, //can only be accessed in air layer BLOCKED //tile can't be entered nor visited }; @@ -181,7 +182,7 @@ private: void initializeGraph(); - CGPathNode::EAccessibility evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo) const; + CGPathNode::EAccessibility evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo, const ELayer layer) const; bool canMoveBetween(const int3 & a, const int3 & b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility) bool isAllowedTeleportEntrance(const CGTeleport * obj) const; From e9636a8d37e6a9e5675ddc82739acb9f56c2f066 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Tue, 17 Nov 2015 04:33:21 +0300 Subject: [PATCH 62/83] CPathfinder: add TODO and ideas for available options --- lib/CPathfinder.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 51a47af41..f035383af 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -121,6 +121,13 @@ private: /// If true transition into air layer only possible from initial node. /// This is drastically decrease path calculation complexity (and time). /// Downside is less MP effective paths calculation. + /// + /// TODO: If this option end up useful for slow devices it's can be improved: + /// - Allow transition into air layer not only from initial position, but also from teleporters. + /// Movement into air can be also allowed when hero disembarked. + /// - Other idea is to allow transition into air within certain radius of N tiles around hero. + /// Patrol support need similar functionality so it's won't be ton of useless code. + /// Such limitation could be useful as it's can be scaled depend on device performance. bool lightweightFlyingMode; /// This option enable one turn limitation for flying and water walking. @@ -135,6 +142,10 @@ private: /// - Move from blocked tiles to visitable one /// - Move from guarded tiles to blockvis tiles without being attacked /// - Move from guarded tiles to guarded visitable tiles with being attacked after + /// TODO: + /// - Option should also allow same tile land <-> air layer transitions. + /// Current implementation only allow go into (from) air layer only to neighbour tiles. + /// I find it's reasonable limitation, but it's will make some movements more expensive than in H3. bool originalMovementRules; PathfinderOptions(); From ab9680a7d9610e048b1c5b1aebb70a45372288e0 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Tue, 17 Nov 2015 07:09:01 +0300 Subject: [PATCH 63/83] CPathfinder: handle event object properly everywhere Also add forgoted check for AdvmapInterface to avoid possible crash. --- client/windows/CAdvmapInterface.cpp | 2 +- lib/CPathfinder.cpp | 30 ++++++++++++++++++++++------- lib/CPathfinder.h | 3 +++ 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/client/windows/CAdvmapInterface.cpp b/client/windows/CAdvmapInterface.cpp index c0a208631..e48daf709 100644 --- a/client/windows/CAdvmapInterface.cpp +++ b/client/windows/CAdvmapInterface.cpp @@ -1545,7 +1545,7 @@ void CAdvMapInt::tileHovered(const int3 &mapPos) case CGPathNode::VISIT: case CGPathNode::BLOCKING_VISIT: - if(objAtTile->ID == Obj::HERO) + if(objAtTile && objAtTile->ID == Obj::HERO) { if(selection == objAtTile) CCS->curh->changeGraphic(ECursor::ADVENTURE, 2); diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index ab2146796..49cb006f8 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -352,13 +352,13 @@ bool CPathfinder::isMovementToDestPossible() const if(cp->layer == ELayer::LAND) { - if(!dtObj) + if(!isDestVisitableObj()) return false; if(dtObj->ID != Obj::BOAT && dtObj->ID != Obj::HERO) return false; } - else if(dtObj && dtObj->ID == Obj::BOAT) + else if(isDestVisitableObj() && dtObj->ID == Obj::BOAT) { /// Hero in boat can't visit empty boats return false; @@ -441,7 +441,7 @@ CGPathNode::ENodeAction CPathfinder::getDestAction() const /// don't break - next case shared for both land and sail layers case ELayer::SAIL: - if(dtObj) + if(isDestVisitableObj()) { auto objRel = getPlayerRelations(dtObj->tempOwner, hero->tempOwner); @@ -495,8 +495,7 @@ bool CPathfinder::isSourceInitialPosition() const bool CPathfinder::isSourceVisitableObj() const { - /// Hero can't visit objects while walking on water or flying - return ctObj != nullptr && (cp->layer == ELayer::LAND || cp->layer == ELayer::SAIL); + return isVisitableObj(ctObj, cp->layer); } bool CPathfinder::isSourceGuarded() const @@ -505,7 +504,7 @@ bool CPathfinder::isSourceGuarded() const /// It's possible at least in these cases: /// - Map start with hero on guarded tile /// - Dimention door used - /// TODO: check what happen when there is several guards + /// TODO: check what happen when there is several guards if(guardingCreaturePosition(cp->coord) != int3(-1, -1, -1) && !isSourceInitialPosition()) { return true; @@ -514,6 +513,11 @@ bool CPathfinder::isSourceGuarded() const return false; } +bool CPathfinder::isDestVisitableObj() const +{ + return isVisitableObj(dtObj, dp->layer); +} + bool CPathfinder::isDestinationGuarded(const bool ignoreAccessibility) const { /// isDestinationGuarded is exception needed for garrisons. @@ -600,7 +604,7 @@ CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const int3 & pos, { return CGPathNode::BLOCKVIS; } - else if(obj->ID != Obj::EVENT) //pathfinder should ignore placed events + else if(canSeeObj(obj)) { return CGPathNode::VISITABLE; } @@ -633,6 +637,18 @@ CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const int3 & pos, return CGPathNode::ACCESSIBLE; } +bool CPathfinder::isVisitableObj(const CGObjectInstance * obj, const ELayer layer) const +{ + /// Hero can't visit objects while walking on water or flying + return canSeeObj(obj) && (layer == ELayer::LAND || layer == ELayer::SAIL); +} + +bool CPathfinder::canSeeObj(const CGObjectInstance * obj) const +{ + /// Pathfinder should ignore placed events + return obj != nullptr && obj->ID != Obj::EVENT; +} + bool CPathfinder::canMoveBetween(const int3 & a, const int3 & b) const { return gs->checkForVisitableDir(a, b); diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index f035383af..143502eab 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -188,12 +188,15 @@ private: bool isSourceInitialPosition() const; bool isSourceVisitableObj() const; bool isSourceGuarded() const; + bool isDestVisitableObj() const; bool isDestinationGuarded(const bool ignoreAccessibility = true) const; bool isDestinationGuardian() const; void initializeGraph(); CGPathNode::EAccessibility evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo, const ELayer layer) const; + bool isVisitableObj(const CGObjectInstance * obj, const ELayer layer) const; + bool canSeeObj(const CGObjectInstance * obj) const; bool canMoveBetween(const int3 & a, const int3 & b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility) bool isAllowedTeleportEntrance(const CGTeleport * obj) const; From 511bb5464447ad2c7ce535fe98fa69290ccee910 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Tue, 17 Nov 2015 17:46:26 +0300 Subject: [PATCH 64/83] doMoveHero: only allow to stop at accessible or land/sail nodes --- client/CPlayerInterface.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 7e3b5a243..3b5bdf7f2 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -2642,7 +2642,18 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) ETerrainType newTerrain; int sh = -1; - for(i=path.nodes.size()-1; i>0 && (stillMoveHero.data == CONTINUE_MOVE); i--) + auto canStop = [&](CGPathNode * node) -> bool + { + if(node->layer == EPathfindingLayer::LAND || node->layer == EPathfindingLayer::SAIL) + return true; + + if(node->accessible == CGPathNode::ACCESSIBLE) + return true; + + return false; + }; + + for(i=path.nodes.size()-1; i>0 && (stillMoveHero.data == CONTINUE_MOVE || !canStop(&path.nodes[i])); i--) { int3 currentCoord = path.nodes[i].coord; int3 nextCoord = path.nodes[i-1].coord; From 7101083a24a66e8a55b961056d599b4129bad969 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Wed, 18 Nov 2015 00:07:25 +0300 Subject: [PATCH 65/83] CPathfinder: restore transit via garrisons --- lib/CPathfinder.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 49cb006f8..547422cd8 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -395,6 +395,11 @@ bool CPathfinder::isMovementAfterDestPossible() const /// Transit over whirlpools only allowed when hero protected return true; } + else if(dtObj->ID == Obj::GARRISON || dtObj->ID == Obj::GARRISON2) + { + /// Transit via unguarded garrisons is always possible + return true; + } break; } From def0f0ef0a506e428a7e2c761ddc4fbc350f6ba1 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Wed, 18 Nov 2015 03:14:58 +0300 Subject: [PATCH 66/83] CTerrainRect::mouseMoved: don't pass curHoveredTile by reference This does cause problems because curHoveredTile can change while reference still being used by CAdvMapInt::tileHovered. --- client/windows/CAdvmapInterface.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/windows/CAdvmapInterface.cpp b/client/windows/CAdvmapInterface.cpp index e48daf709..940a1a655 100644 --- a/client/windows/CAdvmapInterface.cpp +++ b/client/windows/CAdvmapInterface.cpp @@ -114,7 +114,7 @@ void CTerrainRect::clickRight(tribool down, bool previousState) adventureInt->tileRClicked(mp); } -void CTerrainRect::mouseMoved (const SDL_MouseMotionEvent & sEvent) +void CTerrainRect::mouseMoved(const SDL_MouseMotionEvent & sEvent) { int3 tHovered = whichTileIsIt(sEvent.x,sEvent.y); int3 pom = adventureInt->verifyPos(tHovered); @@ -126,11 +126,11 @@ void CTerrainRect::mouseMoved (const SDL_MouseMotionEvent & sEvent) } if (pom != curHoveredTile) - curHoveredTile=pom; + curHoveredTile = pom; else return; - adventureInt->tileHovered(curHoveredTile); + adventureInt->tileHovered(pom); } void CTerrainRect::hover(bool on) { From adeefe903a2353a22f2e3b76e86047af47f42bec Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Thu, 19 Nov 2015 03:08:57 +0300 Subject: [PATCH 67/83] CGPathNode: apply suggested optimizations Also make EPathfindingLayers ui8 too --- lib/CPathfinder.h | 24 ++++++++++++------------ lib/GameConstants.h | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 143502eab..a1c546e13 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -26,18 +26,18 @@ struct DLL_LINKAGE CGPathNode { typedef EPathfindingLayer ELayer; - enum ENodeAction + enum ENodeAction : ui8 { - UNKNOWN = -1, - NORMAL = 0, + UNKNOWN = 0, EMBARK = 1, - DISEMBARK, //2 - BATTLE,//3 - VISIT,//4 - BLOCKING_VISIT//5 + DISEMBARK, + NORMAL, + BATTLE, + VISIT, + BLOCKING_VISIT }; - enum EAccessibility + enum EAccessibility : ui8 { NOT_SET = 0, ACCESSIBLE = 1, //tile can be entered and passed @@ -47,14 +47,14 @@ struct DLL_LINKAGE CGPathNode BLOCKED //tile can't be entered nor visited }; - bool locked; - EAccessibility accessible; - ui8 turns; //how many turns we have to wait before reachng the tile - 0 means current turn - ui32 moveRemains; //remaining tiles after hero reaches the tile CGPathNode * theNodeBefore; int3 coord; //coordinates + ui32 moveRemains; //remaining tiles after hero reaches the tile + ui8 turns; //how many turns we have to wait before reachng the tile - 0 means current turn ELayer layer; + EAccessibility accessible; ENodeAction action; + bool locked; CGPathNode(); void reset(); diff --git a/lib/GameConstants.h b/lib/GameConstants.h index f695be746..d2cd05280 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -748,9 +748,9 @@ ID_LIKE_OPERATORS_DECLS(ETerrainType, ETerrainType::EETerrainType) class DLL_LINKAGE EPathfindingLayer { public: - enum EEPathfindingLayer + enum EEPathfindingLayer : ui8 { - WRONG = -2, AUTO = -1, LAND = 0, SAIL = 1, WATER, AIR, NUM_LAYERS + WRONG = 0, AUTO = 1, LAND = 2, SAIL = 3, WATER, AIR, NUM_LAYERS }; EPathfindingLayer(EEPathfindingLayer _num = WRONG) : num(_num) From d19cb5ce895071da856c7b408c5f8d0d8b5a08b1 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Thu, 19 Nov 2015 04:01:56 +0300 Subject: [PATCH 68/83] EPathfindingLayer: fix obvious layer over-allocation in CPathsInfo --- lib/GameConstants.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/GameConstants.h b/lib/GameConstants.h index d2cd05280..3607ae5bf 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -750,7 +750,7 @@ class DLL_LINKAGE EPathfindingLayer public: enum EEPathfindingLayer : ui8 { - WRONG = 0, AUTO = 1, LAND = 2, SAIL = 3, WATER, AIR, NUM_LAYERS + LAND = 0, SAIL = 1, WATER, AIR, NUM_LAYERS, WRONG, AUTO }; EPathfindingLayer(EEPathfindingLayer _num = WRONG) : num(_num) From cb61572878ff1460d0b7d59575715dd47b778570 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Fri, 20 Nov 2015 12:28:35 +0300 Subject: [PATCH 69/83] TurnInfo: add cache for bonuses selection --- lib/CPathfinder.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 547422cd8..ec9d0d425 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -711,7 +711,10 @@ bool CPathfinder::addTeleportWhirlpool(const CGWhirlpool * obj) const TurnInfo::TurnInfo(const CGHeroInstance * Hero, const int turn) : hero(Hero), maxMovePointsLand(-1), maxMovePointsWater(-1) { - bonuses = hero->getAllBonuses(Selector::days(turn), nullptr); + std::stringstream cachingStr; + cachingStr << "days_" << turn; + + bonuses = hero->getAllBonuses(Selector::days(turn), nullptr, nullptr, cachingStr.str()); } bool TurnInfo::isLayerAvailable(const EPathfindingLayer layer) const From e91d79414bd35d797824b3e1ea07e645d0161709 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sat, 21 Nov 2015 00:32:23 +0300 Subject: [PATCH 70/83] PathfinderOptions: use settings and move all defaults into schema --- config/schemas/settings.json | 70 +++++++++++++++++++++++++++++++++++- lib/CPathfinder.cpp | 23 ++++++------ 2 files changed, 81 insertions(+), 12 deletions(-) diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 5b78fab89..fccc17f74 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -3,7 +3,7 @@ { "type" : "object", "$schema": "http://json-schema.org/draft-04/schema", - "required" : [ "general", "video", "adventure", "battle", "server", "logging", "launcher" ], + "required" : [ "general", "video", "adventure", "pathfinder", "battle", "server", "logging", "launcher" ], "definitions" : { "logLevelEnum" : { "type" : "string", @@ -108,6 +108,74 @@ } } }, + "pathfinder" : { + "type" : "object", + "additionalProperties" : false, + "default": {}, + "required" : [ "teleports", "layers", "oneTurnSpecialLayersLimit", "originalMovementRules", "lightweightFlyingMode" ], + "properties" : { + "layers" : { + "type" : "object", + "additionalProperties" : false, + "default": {}, + "required" : [ "sailing", "waterWalking", "flying" ], + "properties" : { + "sailing" : { + "type" : "boolean", + "default" : true + }, + "waterWalking" : { + "type" : "boolean", + "default" : true + }, + "flying" : { + "type" : "boolean", + "default" : true + } + } + }, + "teleports" : { + "type" : "object", + "additionalProperties" : false, + "default": {}, + "required" : [ "twoWay", "oneWay", "oneWayRandom", "whirlpool", "castleGate" ], + "properties" : { + "twoWay" : { + "type" : "boolean", + "default" : true + }, + "oneWay" : { + "type" : "boolean", + "default" : true + }, + "oneWayRandom" : { + "type" : "boolean", + "default" : false + }, + "whirlpool" : { + "type" : "boolean", + "default" : true + }, + "castleGate" : { + "type" : "boolean", + "default" : false + } + } + }, + "oneTurnSpecialLayersLimit" : { + "type" : "boolean", + "default" : true + }, + "originalMovementRules" : { + "type" : "boolean", + "default" : false + }, + "lightweightFlyingMode" : { + "type" : "boolean", + "default" : false + } + } + }, "battle" : { "type" : "object", "additionalProperties" : false, diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index ec9d0d425..d1297693a 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -7,6 +7,7 @@ #include "mapObjects/CGHeroInstance.h" #include "GameConstants.h" #include "CStopWatch.h" +#include "CConfigHandler.h" /* * CPathfinder.cpp, part of VCMI engine @@ -20,19 +21,19 @@ CPathfinder::PathfinderOptions::PathfinderOptions() { - useFlying = true; - useWaterWalking = true; - useEmbarkAndDisembark = true; - useTeleportTwoWay = true; - useTeleportOneWay = true; - useTeleportOneWayRandom = false; - useTeleportWhirlpool = true; + useFlying = settings["pathfinder"]["layers"]["flying"].Bool(); + useWaterWalking = settings["pathfinder"]["layers"]["waterWalking"].Bool(); + useEmbarkAndDisembark = settings["pathfinder"]["layers"]["sailing"].Bool(); + useTeleportTwoWay = settings["pathfinder"]["teleports"]["twoWay"].Bool(); + useTeleportOneWay = settings["pathfinder"]["teleports"]["oneWay"].Bool(); + useTeleportOneWayRandom = settings["pathfinder"]["teleports"]["oneWayRandom"].Bool(); + useTeleportWhirlpool = settings["pathfinder"]["teleports"]["whirlpool"].Bool(); - useCastleGate = false; + useCastleGate = settings["pathfinder"]["teleports"]["castleGate"].Bool(); - lightweightFlyingMode = false; - oneTurnSpecialLayersLimit = true; - originalMovementRules = true; + lightweightFlyingMode = settings["pathfinder"]["lightweightFlyingMode"].Bool(); + oneTurnSpecialLayersLimit = settings["pathfinder"]["oneTurnSpecialLayersLimit"].Bool(); + originalMovementRules = settings["pathfinder"]["originalMovementRules"].Bool(); } CPathfinder::CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstance * _hero) From 5ae6225ebcb38f693dab8824d8983730d82d20ab Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sat, 21 Nov 2015 10:00:09 +0300 Subject: [PATCH 71/83] TurnInfo: implement internal bonus cache for most used bonuses Bonus system even with caching add too big overhead so we'll only use it once for these bonuses. Still I'm like it to be transparent for external code so it's a bit hacky code. --- lib/CPathfinder.cpp | 36 ++++++++++++++++++++++++++++++++++++ lib/CPathfinder.h | 14 ++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index d1297693a..28d165538 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -709,6 +709,21 @@ bool CPathfinder::addTeleportWhirlpool(const CGWhirlpool * obj) const return options.useTeleportWhirlpool && hlp->hasBonusOfType(Bonus::WHIRLPOOL_PROTECTION) && obj; } +TurnInfo::BonusCache::BonusCache(TBonusListPtr bl) +{ + noTerrainPenalty.reserve(ETerrainType::ROCK); + for(int i = 0; i < ETerrainType::ROCK; i++) + { + noTerrainPenalty.push_back(bl->getFirst(Selector::type(Bonus::NO_TERRAIN_PENALTY).And(Selector::subtype(i)))); + } + + freeShipBoarding = bl->getFirst(Selector::type(Bonus::FREE_SHIP_BOARDING)); + flyingMovement = bl->getFirst(Selector::type(Bonus::FLYING_MOVEMENT)); + flyingMovementVal = bl->valOfBonuses(Selector::type(Bonus::FLYING_MOVEMENT)); + waterWalking = bl->getFirst(Selector::type(Bonus::WATER_WALKING)); + waterWalkingVal = bl->valOfBonuses(Selector::type(Bonus::WATER_WALKING)); +} + TurnInfo::TurnInfo(const CGHeroInstance * Hero, const int turn) : hero(Hero), maxMovePointsLand(-1), maxMovePointsWater(-1) { @@ -716,6 +731,7 @@ TurnInfo::TurnInfo(const CGHeroInstance * Hero, const int turn) cachingStr << "days_" << turn; bonuses = hero->getAllBonuses(Selector::days(turn), nullptr, nullptr, cachingStr.str()); + bonusCache = make_unique(bonuses); } bool TurnInfo::isLayerAvailable(const EPathfindingLayer layer) const @@ -740,11 +756,31 @@ bool TurnInfo::isLayerAvailable(const EPathfindingLayer layer) const bool TurnInfo::hasBonusOfType(Bonus::BonusType type, int subtype) const { + switch(type) + { + case Bonus::FREE_SHIP_BOARDING: + return bonusCache->freeShipBoarding; + case Bonus::FLYING_MOVEMENT: + return bonusCache->flyingMovement; + case Bonus::WATER_WALKING: + return bonusCache->waterWalking; + case Bonus::NO_TERRAIN_PENALTY: + return bonusCache->noTerrainPenalty[subtype]; + } + return bonuses->getFirst(Selector::type(type).And(Selector::subtype(subtype))); } int TurnInfo::valOfBonuses(Bonus::BonusType type, int subtype) const { + switch(type) + { + case Bonus::FLYING_MOVEMENT: + return bonusCache->flyingMovementVal; + case Bonus::WATER_WALKING: + return bonusCache->waterWalkingVal; + } + return bonuses->valOfBonuses(Selector::type(type).And(Selector::subtype(subtype))); } diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index a1c546e13..52409ce08 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -209,6 +209,20 @@ private: struct DLL_LINKAGE TurnInfo { + /// This is certainly not the best design ever and certainly can be improved + /// Unfortunately for pathfinder that do hundreds of thousands calls onus system add too big overhead + struct BonusCache { + std::vector noTerrainPenalty; + bool freeShipBoarding; + bool flyingMovement; + int flyingMovementVal; + bool waterWalking; + int waterWalkingVal; + + BonusCache(TBonusListPtr bonusList); + }; + unique_ptr bonusCache; + const CGHeroInstance * hero; TBonusListPtr bonuses; mutable int maxMovePointsLand; From a375c86ea2985086533e7f8597a2202bc82609eb Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sat, 21 Nov 2015 12:46:59 +0300 Subject: [PATCH 72/83] movementPointsAfterEmbark: get max move points from TurnInfo Missed that when originally implemented TurnInfo. Not pretties code, but boost performance a lot. --- lib/mapObjects/CGHeroInstance.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index e6156b9bd..4e1e003d0 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1169,8 +1169,10 @@ int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool if(!ti) ti = new TurnInfo(this); + int mp1 = ti->getMaxMovePoints(disembark ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL); + int mp2 = ti->getMaxMovePoints(disembark ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND); if(ti->hasBonusOfType(Bonus::FREE_SHIP_BOARDING)) - return (MPsBefore - basicCost) * static_cast(maxMovePoints(disembark)) / maxMovePoints(!disembark); + return (MPsBefore - basicCost) * static_cast(mp1) / mp2; return 0; //take all MPs otherwise } From 438a444443bc77c15b805490e1f987772b6f7135 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sat, 21 Nov 2015 13:30:39 +0300 Subject: [PATCH 73/83] CGHeroInstance: move native terrain check into getNativeTerrain That make it easier to use that code independently in TurnInfo --- lib/CPathfinder.cpp | 1 + lib/CPathfinder.h | 1 + lib/mapObjects/CGHeroInstance.cpp | 48 +++++++++++++++++++------------ lib/mapObjects/CGHeroInstance.h | 1 + 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 28d165538..24a4a023b 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -732,6 +732,7 @@ TurnInfo::TurnInfo(const CGHeroInstance * Hero, const int turn) bonuses = hero->getAllBonuses(Selector::days(turn), nullptr, nullptr, cachingStr.str()); bonusCache = make_unique(bonuses); + nativeTerrain = hero->getNativeTerrain(); } bool TurnInfo::isLayerAvailable(const EPathfindingLayer layer) const diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 52409ce08..a5beef562 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -227,6 +227,7 @@ struct DLL_LINKAGE TurnInfo TBonusListPtr bonuses; mutable int maxMovePointsLand; mutable int maxMovePointsWater; + int nativeTerrain; TurnInfo(const CGHeroInstance * Hero, const int Turn = 0); bool isLayerAvailable(const EPathfindingLayer layer) const; diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 4e1e003d0..a959334c5 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -80,30 +80,40 @@ ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &fro break; } } - else if(!ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType)) + else if(ti->nativeTerrain != from.terType && !ti->hasBonusOfType(Bonus::NO_TERRAIN_PENALTY, from.terType)) { - // NOTE: in H3 neutral stacks will ignore terrain penalty only if placed as topmost stack(s) in hero army. - // This is clearly bug in H3 however intended behaviour is not clear. - // Current VCMI behaviour will ignore neutrals in calculations so army in VCMI - // will always have best penalty without any influence from player-defined stacks order - - for(auto stack : stacks) - { - int nativeTerrain = VLC->townh->factions[stack.second->type->faction]->nativeTerrain; - if(nativeTerrain != -1 && nativeTerrain != from.terType) - { - ret = VLC->heroh->terrCosts[from.terType]; - ret -= getSecSkillLevel(SecondarySkill::PATHFINDING) * 25; - if(ret < GameConstants::BASE_MOVEMENT_COST) - ret = GameConstants::BASE_MOVEMENT_COST; - - break; - } - } + ret = VLC->heroh->terrCosts[from.terType]; + ret -= getSecSkillLevel(SecondarySkill::PATHFINDING) * 25; + if(ret < GameConstants::BASE_MOVEMENT_COST) + ret = GameConstants::BASE_MOVEMENT_COST; } return ret; } +int CGHeroInstance::getNativeTerrain() const +{ + // NOTE: in H3 neutral stacks will ignore terrain penalty only if placed as topmost stack(s) in hero army. + // This is clearly bug in H3 however intended behaviour is not clear. + // Current VCMI behaviour will ignore neutrals in calculations so army in VCMI + // will always have best penalty without any influence from player-defined stacks order + + // TODO: What should we do if all hero stacks are neutral creatures? + int nativeTerrain = -1; + for(auto stack : stacks) + { + int stackNativeTerrain = VLC->townh->factions[stack.second->type->faction]->nativeTerrain; + if(stackNativeTerrain == -1) + continue; + + if(nativeTerrain == -1) + nativeTerrain = stackNativeTerrain; + else if(nativeTerrain != stackNativeTerrain) + return -1; + } + + return nativeTerrain; +} + int3 CGHeroInstance::convertPosition(int3 src, bool toh3m) //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest { if (toh3m) diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 9b9b59d23..748f84685 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -131,6 +131,7 @@ public: const std::string &getBiography() const; bool needsLastStack()const override; ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from, const TurnInfo * ti) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling + int getNativeTerrain() const; ui32 getLowestCreatureSpeed() const; int3 getPosition(bool h3m = false) const; //h3m=true - returns position of hero object; h3m=false - returns position of hero 'manifestation' si32 manaRegain() const; //how many points of mana can hero regain "naturally" in one day From a1fe2ebc447fbebecd67f7bb0372f9df9e01753d Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sat, 21 Nov 2015 14:31:30 +0300 Subject: [PATCH 74/83] Pathfinding: restore gamestate usage to avoid overhead Also when possible pass TerrainTile pointers to getMovementCost instead of using getTile. --- lib/CPathfinder.cpp | 40 ++++++++++++++++++++++------------------ lib/CPathfinder.h | 2 +- server/CGameHandler.cpp | 2 +- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 24a4a023b..88a2877cb 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -96,7 +96,7 @@ void CPathfinder::calculatePaths() cp = pq.top(); pq.pop(); cp->locked = true; - ct = getTile(cp->coord); + ct = &gs->map->getTile(cp->coord); ctObj = ct->topVisitableObj(isSourceInitialPosition()); int movement = cp->moveRemains, turn = cp->turns; @@ -111,7 +111,7 @@ void CPathfinder::calculatePaths() addNeighbours(); for(auto & neighbour : neighbours) { - dt = getTile(neighbour); + dt = &gs->map->getTile(neighbour); dtObj = dt->topVisitableObj(); for(ELayer i = ELayer::LAND; i <= ELayer::AIR; i.advance(1)) { @@ -135,7 +135,7 @@ void CPathfinder::calculatePaths() continue; destAction = getDestAction(); - int cost = CPathfinderHelper::getMovementCost(hero, cp->coord, dp->coord, movement, hlp->getTurnInfo()); + int cost = CPathfinderHelper::getMovementCost(hero, cp->coord, dp->coord, ct, dt, movement, hlp->getTurnInfo()); int remains = movement - cost; if(destAction == CGPathNode::EMBARK || destAction == CGPathNode::DISEMBARK) { @@ -148,7 +148,7 @@ void CPathfinder::calculatePaths() //occurs rarely, when hero with low movepoints tries to leave the road hlp->updateTurnInfo(++turnAtNextTile); int moveAtNextTile = hlp->getMaxMovePoints(i); - cost = CPathfinderHelper::getMovementCost(hero, cp->coord, dp->coord, moveAtNextTile, hlp->getTurnInfo()); //cost must be updated, movement points changed :( + cost = CPathfinderHelper::getMovementCost(hero, cp->coord, dp->coord, ct, dt, moveAtNextTile, hlp->getTurnInfo()); //cost must be updated, movement points changed :( remains = moveAtNextTile - cost; } @@ -222,7 +222,7 @@ void CPathfinder::addTeleportExits() auto pos = obj->getBlockedPos(); for(auto p : pos) { - if(getTile(p)->topVisitableId() == obj->ID) + if(gs->map->getTile(p).topVisitableId() == obj->ID) neighbours.push_back(p); } } @@ -511,7 +511,7 @@ bool CPathfinder::isSourceGuarded() const /// - Map start with hero on guarded tile /// - Dimention door used /// TODO: check what happen when there is several guards - if(guardingCreaturePosition(cp->coord) != int3(-1, -1, -1) && !isSourceInitialPosition()) + if(gs->guardingCreaturePosition(cp->coord).valid() && !isSourceInitialPosition()) { return true; } @@ -528,7 +528,7 @@ bool CPathfinder::isDestinationGuarded(const bool ignoreAccessibility) const { /// isDestinationGuarded is exception needed for garrisons. /// When monster standing behind garrison it's visitable and guarded at the same time. - if(guardingCreaturePosition(dp->coord).valid() + if(gs->guardingCreaturePosition(dp->coord).valid() && (ignoreAccessibility || dp->accessible == CGPathNode::BLOCKVIS)) { return true; @@ -539,7 +539,7 @@ bool CPathfinder::isDestinationGuarded(const bool ignoreAccessibility) const bool CPathfinder::isDestinationGuardian() const { - return guardingCreaturePosition(cp->coord) == dp->coord; + return gs->guardingCreaturePosition(cp->coord) == dp->coord; } void CPathfinder::initializeGraph() @@ -558,7 +558,7 @@ void CPathfinder::initializeGraph() { for(pos.z=0; pos.z < out.sizes.z; ++pos.z) { - const TerrainTile * tinfo = getTile(pos); + const TerrainTile * tinfo = &gs->map->getTile(pos); switch(tinfo->terType) { case ETerrainType::ROCK: @@ -893,7 +893,7 @@ void CPathfinderHelper::getNeighbours(const CMap * map, const TerrainTile & srct } } -int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & src, const int3 & dst, const int remainingMovePoints, const TurnInfo * ti, const bool checkLast) +int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & src, const int3 & dst, const TerrainTile * ct, const TerrainTile * dt, const int remainingMovePoints, const TurnInfo * ti, const bool checkLast) { if(src == dst) //same tile return 0; @@ -901,23 +901,27 @@ int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & sr if(!ti) ti = new TurnInfo(h); - auto s = h->cb->getTile(src), d = h->cb->getTile(dst); + if(ct == nullptr || dt == nullptr) + { + ct = h->cb->getTile(src); + dt = h->cb->getTile(dst); + } /// TODO: by the original game rules hero shouldn't be affected by terrain penalty while flying. /// Also flying movement only has penalty when player moving over blocked tiles. /// So if you only have base flying with 40% penalty you can still ignore terrain penalty while having zero flying penalty. - int ret = h->getTileCost(*d, *s, ti); + int ret = h->getTileCost(*dt, *ct, ti); /// Unfortunately this can't be implemented yet as server don't know when player flying and when he's not. /// Difference in cost calculation on client and server is much worse than incorrect cost. /// So this one is waiting till server going to use pathfinder rules for path validation. - if(d->blocked && ti->hasBonusOfType(Bonus::FLYING_MOVEMENT)) + if(dt->blocked && ti->hasBonusOfType(Bonus::FLYING_MOVEMENT)) { ret *= (100.0 + ti->valOfBonuses(Bonus::FLYING_MOVEMENT)) / 100.0; } - else if(d->terType == ETerrainType::WATER) + else if(dt->terType == ETerrainType::WATER) { - if(h->boat && s->hasFavourableWinds() && d->hasFavourableWinds()) //Favourable Winds + if(h->boat && ct->hasFavourableWinds() && dt->hasFavourableWinds()) //Favourable Winds ret *= 0.666; else if(!h->boat && ti->hasBonusOfType(Bonus::WATER_WALKING)) { @@ -941,10 +945,10 @@ int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & sr { std::vector vec; vec.reserve(8); //optimization - getNeighbours(h->cb->gameState()->map, *d, dst, vec, s->terType != ETerrainType::WATER, true); + getNeighbours(h->cb->gameState()->map, *dt, dst, vec, ct->terType != ETerrainType::WATER, true); for(auto & elem : vec) { - int fcost = getMovementCost(h, dst, elem, left, ti, false); + int fcost = getMovementCost(h, dst, elem, nullptr, nullptr, left, ti, false); if(fcost <= left) return ret; } @@ -955,7 +959,7 @@ int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & sr int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & dst) { - return getMovementCost(h, h->visitablePos(), dst, h->movement); + return getMovementCost(h, h->visitablePos(), dst, nullptr, nullptr, h->movement); } CGPathNode::CGPathNode() diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index a5beef562..d94ccb6e8 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -248,7 +248,7 @@ public: static void getNeighbours(const CMap * map, const TerrainTile & srct, const int3 & tile, std::vector & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing); - static int getMovementCost(const CGHeroInstance * h, const int3 & src, const int3 & dst, const int remainingMovePoints =- 1, const TurnInfo * ti = nullptr, const bool checkLast = true); + static int getMovementCost(const CGHeroInstance * h, const int3 & src, const int3 & dst, const TerrainTile * ct, const TerrainTile * dt, const int remainingMovePoints =- 1, const TurnInfo * ti = nullptr, const bool checkLast = true); static int getMovementCost(const CGHeroInstance * h, const int3 & dst); private: diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 4fa958575..7be5428e6 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1781,7 +1781,7 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 teleporting, bo auto ti = new TurnInfo(h); const bool canFly = ti->hasBonusOfType(Bonus::FLYING_MOVEMENT); const bool canWalkOnSea = ti->hasBonusOfType(Bonus::WATER_WALKING); - const int cost = CPathfinderHelper::getMovementCost(h, h->getPosition(), hmpos, h->movement, ti); + const int cost = CPathfinderHelper::getMovementCost(h, h->getPosition(), hmpos, nullptr, nullptr, h->movement, ti); //it's a rock or blocked and not visitable tile //OR hero is on land and dest is water and (there is not present only one object - boat) From 649ebfad2f413728d79403ab37100ea0f30ef177 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sun, 22 Nov 2015 05:34:37 +0300 Subject: [PATCH 75/83] TerrainTile::topVisitableObj: avoid costly vector copying --- lib/mapping/CMap.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 644589df1..e4b7b11f1 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -132,11 +132,13 @@ Obj TerrainTile::topVisitableId(bool excludeTop) const CGObjectInstance * TerrainTile::topVisitableObj(bool excludeTop) const { - auto visitableObj = visitableObjects; - if(excludeTop && visitableObj.size()) - visitableObj.pop_back(); + if(visitableObjects.empty() || (excludeTop && visitableObjects.size() == 1)) + return nullptr; - return visitableObj.size() ? visitableObj.back() : nullptr; + if(excludeTop) + return visitableObjects[visitableObjects.size()-2]; + + return visitableObjects.back(); } bool TerrainTile::isCoastal() const From 8217eb3a82594f895456f56161eb9ce3d6c1e193 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sun, 22 Nov 2015 06:16:16 +0300 Subject: [PATCH 76/83] CPathfinder: split isLayerTransitionPossible into two functions This way we can avoid usage of getNode for layers that clearly can't be used. --- lib/CPathfinder.cpp | 68 ++++++++++++++++++++++++++++----------------- lib/CPathfinder.h | 1 + 2 files changed, 44 insertions(+), 25 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 88a2877cb..ecfd292b4 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -115,6 +115,12 @@ void CPathfinder::calculatePaths() dtObj = dt->topVisitableObj(); for(ELayer i = ELayer::LAND; i <= ELayer::AIR; i.advance(1)) { + if(!hlp->isLayerAvailable(i)) + continue; + + if(cp->layer != i && !isLayerTransitionPossible(i)) + continue; + dp = out.getNode(neighbour, i); if(dp->accessible == CGPathNode::NOT_SET) continue; @@ -125,9 +131,6 @@ void CPathfinder::calculatePaths() if(!passOneTurnLimitCheck(cp->turns != turn)) continue; - if(!hlp->isLayerAvailable(i)) - continue; - if(cp->layer != i && !isLayerTransitionPossible()) continue; @@ -249,7 +252,7 @@ void CPathfinder::addTeleportExits() } } -bool CPathfinder::isLayerTransitionPossible() const +bool CPathfinder::isLayerTransitionPossible(const ELayer destLayer) const { /// No layer transition allowed when previous node action is BATTLE if(cp->action == CGPathNode::BATTLE) @@ -258,12 +261,42 @@ bool CPathfinder::isLayerTransitionPossible() const switch(cp->layer) { case ELayer::LAND: - if(options.lightweightFlyingMode && dp->layer == ELayer::AIR) - { - if(!isSourceInitialPosition()) - return false; - } - else if(dp->layer == ELayer::SAIL) + if(destLayer != ELayer::AIR) + return true; + + if(!options.lightweightFlyingMode || isSourceInitialPosition()) + return true; + + break; + + case ELayer::SAIL: + if(destLayer == ELayer::LAND && dt->isCoastal()) + return true; + + break; + + case ELayer::AIR: + if(destLayer == ELayer::LAND) + return true; + + break; + + case ELayer::WATER: + if(destLayer == ELayer::LAND) + return true; + + break; + } + + return false; +} + +bool CPathfinder::isLayerTransitionPossible() const +{ + switch(cp->layer) + { + case ELayer::LAND: + if(dp->layer == ELayer::SAIL) { /// Cannot enter empty water tile from land -> it has to be visitable if(dp->accessible == CGPathNode::ACCESSIBLE) @@ -273,12 +306,6 @@ bool CPathfinder::isLayerTransitionPossible() const break; case ELayer::SAIL: - if(dp->layer != ELayer::LAND) - return false; - - 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)) || dt->visitable) //TODO: passableness problem -> town says it's passable (thus accessible) but we obviously can't disembark onto town gate @@ -289,9 +316,6 @@ bool CPathfinder::isLayerTransitionPossible() const break; case ELayer::AIR: - if(dp->layer != ELayer::LAND) - return false; - if(options.originalMovementRules) { if((cp->accessible != CGPathNode::ACCESSIBLE && @@ -309,12 +333,6 @@ bool CPathfinder::isLayerTransitionPossible() const } break; - - case ELayer::WATER: - if(dp->layer != ELayer::LAND) - return false; - - break; } return true; diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index d94ccb6e8..2e942935c 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -180,6 +180,7 @@ private: void addNeighbours(); void addTeleportExits(); + bool isLayerTransitionPossible(const ELayer dstLayer) const; bool isLayerTransitionPossible() const; bool isMovementToDestPossible() const; bool isMovementAfterDestPossible() const; From 5a87f58e09b0acfd5aa296e668fd712094b89321 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sun, 22 Nov 2015 06:23:54 +0300 Subject: [PATCH 77/83] CPathfinder: optimize checks order in calculatePaths --- lib/CPathfinder.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index ecfd292b4..4fa2412b0 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -96,8 +96,6 @@ void CPathfinder::calculatePaths() cp = pq.top(); pq.pop(); cp->locked = true; - ct = &gs->map->getTile(cp->coord); - ctObj = ct->topVisitableObj(isSourceInitialPosition()); int movement = cp->moveRemains, turn = cp->turns; hlp->updateTurnInfo(turn); @@ -105,7 +103,11 @@ void CPathfinder::calculatePaths() { hlp->updateTurnInfo(++turn); movement = hlp->getMaxMovePoints(cp->layer); + if(!passOneTurnLimitCheck(true)) + continue; } + ct = &gs->map->getTile(cp->coord); + ctObj = ct->topVisitableObj(isSourceInitialPosition()); //add accessible neighbouring nodes to the queue addNeighbours(); @@ -122,13 +124,10 @@ void CPathfinder::calculatePaths() continue; dp = out.getNode(neighbour, i); - if(dp->accessible == CGPathNode::NOT_SET) - continue; - if(dp->locked) continue; - if(!passOneTurnLimitCheck(cp->turns != turn)) + if(dp->accessible == CGPathNode::NOT_SET) continue; if(cp->layer != i && !isLayerTransitionPossible()) From fc06db4c4f6a7311c132b1621f4c727b0d590c6a Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sun, 22 Nov 2015 07:14:52 +0300 Subject: [PATCH 78/83] Move STRONG_INLINE define into Global.h --- Global.h | 12 ++++++++++++ client/gui/SDL_Extensions.h | 9 --------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Global.h b/Global.h index 297d0ac76..56a35a9f4 100644 --- a/Global.h +++ b/Global.h @@ -95,6 +95,18 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size."); # define NOMINMAX // Exclude min/max macros from . Use std::[min/max] from instead. #endif +/* ---------------------------------------------------------------------------- */ +/* A macro to force inlining some of our functions */ +/* ---------------------------------------------------------------------------- */ +// Compiler (at least MSVC) is not so smart here-> without that displaying is MUCH slower +#ifdef _MSC_VER +# define STRONG_INLINE __forceinline +#elif __GNUC__ +# define STRONG_INLINE inline __attribute__((always_inline)) +#else +# define STRONG_INLINE inline +#endif + #define _USE_MATH_DEFINES #include diff --git a/client/gui/SDL_Extensions.h b/client/gui/SDL_Extensions.h index 0aa691015..0f9a5e7d4 100644 --- a/client/gui/SDL_Extensions.h +++ b/client/gui/SDL_Extensions.h @@ -18,15 +18,6 @@ #include "../../lib/GameConstants.h" -//A macro to force inlining some of our functions. Compiler (at least MSVC) is not so smart here-> without that displaying is MUCH slower -#ifdef _MSC_VER - #define STRONG_INLINE __forceinline -#elif __GNUC__ - #define STRONG_INLINE inline __attribute__((always_inline)) -#else - #define STRONG_INLINE inline -#endif - extern SDL_Window * mainWindow; extern SDL_Renderer * mainRenderer; extern SDL_Texture * screenTexture; From e1a360408df66bda1f9e6d95e26b230321d26d21 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sun, 22 Nov 2015 07:16:20 +0300 Subject: [PATCH 79/83] GameConstants: move operators into header and always inline them --- lib/GameConstants.cpp | 52 --------------------------------- lib/GameConstants.h | 67 +++++++++++++++++++++++++++---------------- 2 files changed, 42 insertions(+), 77 deletions(-) diff --git a/lib/GameConstants.cpp b/lib/GameConstants.cpp index 2b9556091..864b1aba2 100644 --- a/lib/GameConstants.cpp +++ b/lib/GameConstants.cpp @@ -25,58 +25,6 @@ const PlayerColor PlayerColor::NEUTRAL = PlayerColor(255); const PlayerColor PlayerColor::PLAYER_LIMIT = PlayerColor(PLAYER_LIMIT_I); const TeamID TeamID::NO_TEAM = TeamID(255); -#define ID_LIKE_OPERATORS_INTERNAL(A, B, AN, BN) \ -bool operator==(const A & a, const B & b) \ -{ \ - return AN == BN ; \ -} \ -bool operator!=(const A & a, const B & b) \ -{ \ - return AN != BN ; \ -} \ -bool operator<(const A & a, const B & b) \ -{ \ - return AN < BN ; \ -} \ -bool operator<=(const A & a, const B & b) \ -{ \ - return AN <= BN ; \ -} \ -bool operator>(const A & a, const B & b) \ -{ \ - return AN > BN ; \ -} \ -bool operator>=(const A & a, const B & b) \ -{ \ - return AN >= BN ; \ -} - -#define ID_LIKE_OPERATORS(CLASS_NAME, ENUM_NAME) \ - ID_LIKE_OPERATORS_INTERNAL(CLASS_NAME, CLASS_NAME, a.num, b.num) \ - ID_LIKE_OPERATORS_INTERNAL(CLASS_NAME, ENUM_NAME, a.num, b) \ - ID_LIKE_OPERATORS_INTERNAL(ENUM_NAME, CLASS_NAME, a, b.num) - - -ID_LIKE_OPERATORS(SecondarySkill, SecondarySkill::ESecondarySkill) - -ID_LIKE_OPERATORS(Obj, Obj::EObj) - -ID_LIKE_OPERATORS(ETerrainType, ETerrainType::EETerrainType) - -ID_LIKE_OPERATORS(EPathfindingLayer, EPathfindingLayer::EEPathfindingLayer) - -ID_LIKE_OPERATORS(ArtifactID, ArtifactID::EArtifactID) - -ID_LIKE_OPERATORS(ArtifactPosition, ArtifactPosition::EArtifactPosition) - -ID_LIKE_OPERATORS(CreatureID, CreatureID::ECreatureID) - -ID_LIKE_OPERATORS(SpellID, SpellID::ESpellID) - -ID_LIKE_OPERATORS(BuildingID, BuildingID::EBuildingID) - -ID_LIKE_OPERATORS(BFieldType, BFieldType::EBFieldType) - CArtifact * ArtifactID::toArtifact() const { return VLC->arth->artifacts[*this]; diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 3607ae5bf..20262fee0 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -93,18 +93,37 @@ CLASS_NAME & advance(int i) \ } -#define ID_LIKE_OPERATORS_INTERNAL_DECLS(A, B) \ -bool DLL_LINKAGE operator==(const A & a, const B & b); \ -bool DLL_LINKAGE operator!=(const A & a, const B & b); \ -bool DLL_LINKAGE operator<(const A & a, const B & b); \ -bool DLL_LINKAGE operator<=(const A & a, const B & b); \ -bool DLL_LINKAGE operator>(const A & a, const B & b); \ -bool DLL_LINKAGE operator>=(const A & a, const B & b); +// Operators are performance-critical and to be inlined they must be in header +#define ID_LIKE_OPERATORS_INTERNAL(A, B, AN, BN) \ +STRONG_INLINE bool operator==(const A & a, const B & b) \ +{ \ + return AN == BN ; \ +} \ +STRONG_INLINE bool operator!=(const A & a, const B & b) \ +{ \ + return AN != BN ; \ +} \ +STRONG_INLINE bool operator<(const A & a, const B & b) \ +{ \ + return AN < BN ; \ +} \ +STRONG_INLINE bool operator<=(const A & a, const B & b) \ +{ \ + return AN <= BN ; \ +} \ +STRONG_INLINE bool operator>(const A & a, const B & b) \ +{ \ + return AN > BN ; \ +} \ +STRONG_INLINE bool operator>=(const A & a, const B & b) \ +{ \ + return AN >= BN ; \ +} -#define ID_LIKE_OPERATORS_DECLS(CLASS_NAME, ENUM_NAME) \ - ID_LIKE_OPERATORS_INTERNAL_DECLS(CLASS_NAME, CLASS_NAME) \ - ID_LIKE_OPERATORS_INTERNAL_DECLS(CLASS_NAME, ENUM_NAME) \ - ID_LIKE_OPERATORS_INTERNAL_DECLS(ENUM_NAME, CLASS_NAME) +#define ID_LIKE_OPERATORS(CLASS_NAME, ENUM_NAME) \ + ID_LIKE_OPERATORS_INTERNAL(CLASS_NAME, CLASS_NAME, a.num, b.num) \ + ID_LIKE_OPERATORS_INTERNAL(CLASS_NAME, ENUM_NAME, a.num, b) \ + ID_LIKE_OPERATORS_INTERNAL(ENUM_NAME, CLASS_NAME, a, b.num) #define OP_DECL_INT(CLASS_NAME, OP) \ @@ -296,7 +315,7 @@ public: ESecondarySkill num; }; -ID_LIKE_OPERATORS_DECLS(SecondarySkill, SecondarySkill::ESecondarySkill) +ID_LIKE_OPERATORS(SecondarySkill, SecondarySkill::ESecondarySkill) namespace EAlignment { @@ -384,7 +403,7 @@ public: EBuildingID num; }; -ID_LIKE_OPERATORS_DECLS(BuildingID, BuildingID::EBuildingID) +ID_LIKE_OPERATORS(BuildingID, BuildingID::EBuildingID) namespace EBuildingState { @@ -651,7 +670,7 @@ public: EObj num; }; -ID_LIKE_OPERATORS_DECLS(Obj, Obj::EObj) +ID_LIKE_OPERATORS(Obj, Obj::EObj) namespace SecSkillLevel { @@ -743,7 +762,7 @@ public: DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const ETerrainType terrainType); -ID_LIKE_OPERATORS_DECLS(ETerrainType, ETerrainType::EETerrainType) +ID_LIKE_OPERATORS(ETerrainType, ETerrainType::EETerrainType) class DLL_LINKAGE EPathfindingLayer { @@ -763,7 +782,7 @@ public: DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EPathfindingLayer pathfindingLayer); -ID_LIKE_OPERATORS_DECLS(EPathfindingLayer, EPathfindingLayer::EEPathfindingLayer) +ID_LIKE_OPERATORS(EPathfindingLayer, EPathfindingLayer::EEPathfindingLayer) class BFieldType { @@ -786,7 +805,7 @@ public: EBFieldType num; }; -ID_LIKE_OPERATORS_DECLS(BFieldType, BFieldType::EBFieldType) +ID_LIKE_OPERATORS(BFieldType, BFieldType::EBFieldType) namespace EPlayerStatus { @@ -826,7 +845,7 @@ public: EArtifactPosition num; }; -ID_LIKE_OPERATORS_DECLS(ArtifactPosition, ArtifactPosition::EArtifactPosition) +ID_LIKE_OPERATORS(ArtifactPosition, ArtifactPosition::EArtifactPosition) class ArtifactID { @@ -871,7 +890,7 @@ public: EArtifactID num; }; -ID_LIKE_OPERATORS_DECLS(ArtifactID, ArtifactID::EArtifactID) +ID_LIKE_OPERATORS(ArtifactID, ArtifactID::EArtifactID) class CreatureID { @@ -915,7 +934,7 @@ public: ECreatureID num; }; -ID_LIKE_OPERATORS_DECLS(CreatureID, CreatureID::ECreatureID) +ID_LIKE_OPERATORS(CreatureID, CreatureID::ECreatureID) class SpellID { @@ -959,7 +978,7 @@ public: ESpellID num; }; -ID_LIKE_OPERATORS_DECLS(SpellID, SpellID::ESpellID) +ID_LIKE_OPERATORS(SpellID, SpellID::ESpellID) enum class ESpellSchool: ui8 { @@ -969,8 +988,6 @@ enum class ESpellSchool: ui8 EARTH = 3 }; -ID_LIKE_OPERATORS_DECLS(SpellID, SpellID::ESpellID) - // Typedef declarations typedef ui8 TFaction; typedef si64 TExpType; @@ -981,8 +998,8 @@ typedef si32 TQuantity; typedef int TRmgTemplateZoneId; #undef ID_LIKE_CLASS_COMMON -#undef ID_LIKE_OPERATORS_DECLS -#undef ID_LIKE_OPERATORS_INTERNAL_DECLS +#undef ID_LIKE_OPERATORS +#undef ID_LIKE_OPERATORS_INTERNAL #undef INSTID_LIKE_CLASS_COMMON #undef OP_DECL_INT From 6dacb84404c25b7a5baadf793eceb3f0197359e8 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sun, 22 Nov 2015 07:32:33 +0300 Subject: [PATCH 80/83] CPathfinder::addNeighbours: avoid allocating new vector each time --- lib/CPathfinder.cpp | 10 +++++----- lib/CPathfinder.h | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 4fa2412b0..dd68fa838 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -53,6 +53,7 @@ CPathfinder::CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstan hlp = make_unique(hero, options); initializeGraph(); + neighbourTiles.reserve(8); neighbours.reserve(16); } @@ -192,19 +193,18 @@ void CPathfinder::calculatePaths() void CPathfinder::addNeighbours() { neighbours.clear(); - std::vector tiles; - tiles.reserve(8); - CPathfinderHelper::getNeighbours(gs->map, *ct, cp->coord, tiles, boost::logic::indeterminate, cp->layer == ELayer::SAIL); + neighbourTiles.clear(); + CPathfinderHelper::getNeighbours(gs->map, *ct, cp->coord, neighbourTiles, boost::logic::indeterminate, cp->layer == ELayer::SAIL); if(isSourceVisitableObj()) { - for(int3 tile: tiles) + for(int3 tile: neighbourTiles) { if(canMoveBetween(tile, ctObj->visitablePos())) neighbours.push_back(tile); } } else - vstd::concatenate(neighbours, tiles); + vstd::concatenate(neighbours, neighbourTiles); } void CPathfinder::addTeleportExits() diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 2e942935c..a6015d974 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -169,6 +169,7 @@ private: }; boost::heap::priority_queue > pq; + std::vector neighbourTiles; std::vector neighbours; CGPathNode * cp; //current (source) path node -> we took it from the queue From 2632389aaf7104ac08fa270123f82da72c66f3a1 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sun, 22 Nov 2015 21:06:37 +0300 Subject: [PATCH 81/83] CPathfinder: bring FoW reference back to avoid performance loss This one is revert 148355908d968e4e87375a3b727fc8c3a03d8451 --- lib/CPathfinder.cpp | 4 ++-- lib/CPathfinder.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index dd68fa838..fa2f30145 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -37,7 +37,7 @@ CPathfinder::PathfinderOptions::PathfinderOptions() } CPathfinder::CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstance * _hero) - : CGameInfoCallback(_gs, boost::optional()), out(_out), hero(_hero) + : CGameInfoCallback(_gs, boost::optional()), out(_out), hero(_hero), FoW(getPlayerTeam(hero->tempOwner)->fogOfWarMap) { assert(hero); assert(hero == getHero(hero->id)); @@ -602,7 +602,7 @@ void CPathfinder::initializeGraph() CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo, const ELayer layer) const { - if(tinfo->terType == ETerrainType::ROCK || !isVisible(pos, hero->tempOwner)) + if(tinfo->terType == ETerrainType::ROCK || !FoW[pos.x][pos.y][pos.z]) return CGPathNode::BLOCKED; switch(layer) diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index a6015d974..af5cea733 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -153,6 +153,7 @@ private: CPathsInfo & out; const CGHeroInstance * hero; + const std::vector > > &FoW; unique_ptr hlp; struct NodeComparer From f4c06660e413dd8da0f06344c53ed01349d1237e Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Sun, 22 Nov 2015 21:31:47 +0300 Subject: [PATCH 82/83] CPathfinder::evaluateAccessibility: apply more optimizations --- lib/CPathfinder.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index fa2f30145..4cd40b404 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -619,14 +619,14 @@ CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const int3 & pos, { for(const CGObjectInstance * obj : tinfo->visitableObjects) { - if(obj->passableFor(hero->tempOwner)) - { - return CGPathNode::ACCESSIBLE; - } - else if(obj->blockVisit) + if(obj->blockVisit) { return CGPathNode::BLOCKVIS; } + else if(obj->passableFor(hero->tempOwner)) + { + return CGPathNode::ACCESSIBLE; + } else if(canSeeObj(obj)) { return CGPathNode::VISITABLE; @@ -634,13 +634,15 @@ CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const int3 & pos, } } } - else if(guardingCreaturePosition(pos).valid() && !tinfo->blocked) + else if(tinfo->blocked) + { + return CGPathNode::BLOCKED; + } + else if(gs->guardingCreaturePosition(pos).valid()) { // Monster close by; blocked visit for battle return CGPathNode::BLOCKVIS; } - else if(tinfo->blocked) - return CGPathNode::BLOCKED; break; From df4515901d6da7c16a6404f43977df32c304d705 Mon Sep 17 00:00:00 2001 From: ArseniyShestakov Date: Mon, 23 Nov 2015 14:11:08 +0300 Subject: [PATCH 83/83] CPathfinder: fix special movement boundary check Forgot to fix it for water walking after fe12b8f664a6f38b71cf914a6326d6cb81dc1253 --- lib/CPathfinder.cpp | 23 ++++++++++++++--------- lib/CPathfinder.h | 3 +++ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 4cd40b404..8be71f212 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -59,16 +59,21 @@ CPathfinder::CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstan void CPathfinder::calculatePaths() { - auto passOneTurnLimitCheck = [&](bool shouldCheck) -> bool + auto passOneTurnLimitCheck = [&]() -> bool { - if(options.oneTurnSpecialLayersLimit && shouldCheck) + if(!options.oneTurnSpecialLayersLimit) + return true; + + if(cp->layer == ELayer::WATER) + return false; + if(cp->layer == ELayer::AIR) { - if((cp->layer == ELayer::AIR || cp->layer == ELayer::WATER) - && cp->accessible != CGPathNode::ACCESSIBLE) - { + if(options.originalMovementRules && cp->accessible == CGPathNode::ACCESSIBLE) + return true; + else return false; - } } + return true; }; @@ -104,7 +109,7 @@ void CPathfinder::calculatePaths() { hlp->updateTurnInfo(++turn); movement = hlp->getMaxMovePoints(cp->layer); - if(!passOneTurnLimitCheck(true)) + if(!passOneTurnLimitCheck()) continue; } ct = &gs->map->getTile(cp->coord); @@ -155,8 +160,8 @@ void CPathfinder::calculatePaths() remains = moveAtNextTile - cost; } - if(isBetterWay(remains, turnAtNextTile) - && passOneTurnLimitCheck(cp->turns != turnAtNextTile || !remains)) + if(isBetterWay(remains, turnAtNextTile) && + ((cp->turns == turnAtNextTile && remains) || passOneTurnLimitCheck())) { assert(dp != cp->theNodeBefore); //two tiles can't point to each other dp->moveRemains = remains; diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index af5cea733..29c5bd35e 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -135,6 +135,9 @@ private: /// /// Following imitation is default H3 mechanics, but someone may want to disable it in mods. /// After all this limit should benefit performance on maps with tons of water or blocked tiles. + /// + /// TODO: + /// - Behavior when option is disabled not implemented and will lead to crashes. bool oneTurnSpecialLayersLimit; /// VCMI have different movement rules to solve flaws original engine has.