diff --git a/AI/VCAI/AIUtility.cpp b/AI/VCAI/AIUtility.cpp index 854196ad8..b273f5cbb 100644 --- a/AI/VCAI/AIUtility.cpp +++ b/AI/VCAI/AIUtility.cpp @@ -597,3 +597,14 @@ bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2 else return false; } + +uint32_t distanceToTile(const CGHeroInstance * hero, int3 pos) +{ + auto pathInfo = cb->getPathsInfo(hero)->getPathInfo(pos); + uint32_t totalMovementPoints = pathInfo->turns * hero->maxMovePoints(true) + hero->movement; + + if(totalMovementPoints < pathInfo->moveRemains) // should not be but who knows + return 0; + + return totalMovementPoints - pathInfo->moveRemains; +} \ No newline at end of file diff --git a/AI/VCAI/AIUtility.h b/AI/VCAI/AIUtility.h index 6e260f56e..3a5fc1f17 100644 --- a/AI/VCAI/AIUtility.h +++ b/AI/VCAI/AIUtility.h @@ -176,6 +176,7 @@ bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2 ui64 howManyReinforcementsCanBuy(HeroPtr h, const CGTownInstance * t); ui64 howManyReinforcementsCanGet(HeroPtr h, const CGTownInstance * t); int3 whereToExplore(HeroPtr h); +uint32_t distanceToTile(const CGHeroInstance * hero, int3 pos); class CDistanceSorter { diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 3acad303f..dc643505d 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -25,20 +25,20 @@ bool canSeeObj(const CGObjectInstance * obj) return obj != nullptr && obj->ID != Obj::EVENT; } -CNeighbourFinder::CNeighbourFinder(CPathfinder * pathfinder) - :pathfinder(pathfinder), neighbours(), neighbourTiles(), accessibleNeighbourTiles() +CNeighbourFinder::CNeighbourFinder() + :neighbours(), neighbourTiles(), accessibleNeighbourTiles() { } -std::vector & CNeighbourFinder::calculateNeighbours() +std::vector & CNeighbourFinder::calculateNeighbours(CPathNodeInfo & source, CPathfinderHelper * pathfinderHelper, CNodeHelper * nodeHelper) { - addNeighbourTiles(); + addNeighbourTiles(source, pathfinderHelper); for(auto & neighbour : accessibleNeighbourTiles) { for(EPathfindingLayer i = EPathfindingLayer::LAND; i <= EPathfindingLayer::AIR; i.advance(1)) { - auto node = pathfinder->nodeHelper->getNode(neighbour, i); + auto node = nodeHelper->getNode(neighbour, i); if(node->accessible == CGPathNode::NOT_SET) continue; @@ -50,19 +50,24 @@ std::vector & CNeighbourFinder::calculateNeighbours() return neighbours; } -void CNeighbourFinder::addNeighbourTiles() +void CNeighbourFinder::addNeighbourTiles(CPathNodeInfo & source, CPathfinderHelper * pathfinderHelper) { neighbourTiles.clear(); accessibleNeighbourTiles.clear(); neighbours.clear(); - pathfinder->populateNeighbourTiles(neighbourTiles); + pathfinderHelper->getNeighbours( + *source.tile, + source.node->coord, + neighbourTiles, + boost::logic::indeterminate, + source.node->layer == EPathfindingLayer::SAIL); - if(pathfinder->source.isNodeObjectVisitable()) + if(source.isNodeObjectVisitable()) { for(int3 tile : neighbourTiles) { - if(pathfinder->canMoveBetween(tile, pathfinder->source.nodeObject->visitablePos())) + if(pathfinderHelper->canMoveBetween(tile, source.nodeObject->visitablePos())) accessibleNeighbourTiles.push_back(tile); } } @@ -99,7 +104,7 @@ public: } }; -CPathfinder::PathfinderOptions::PathfinderOptions() +PathfinderOptions::PathfinderOptions() { useFlying = settings["pathfinder"]["layers"]["flying"].Bool(); useWaterWalking = settings["pathfinder"]["layers"]["waterWalking"].Bool(); @@ -125,7 +130,7 @@ CPathfinder::CPathfinder( _gs, _hero, std::make_shared(_out, _hero), - std::make_shared(this)) + std::make_shared()) { } @@ -145,9 +150,7 @@ CPathfinder::CPathfinder( assert(hero); assert(hero == getHero(hero->id)); - destAction = CGPathNode::UNKNOWN; - - hlp = make_unique(hero, options); + hlp = make_unique(_gs, hero, options); initializePatrol(); initializeGraph(); @@ -222,7 +225,7 @@ void CPathfinder::calculatePaths() } //add accessible neighbouring nodes to the queue - auto neighbourNodes = neighbourFinder->calculateNeighbours(); + auto neighbourNodes = neighbourFinder->calculateNeighbours(source, hlp.get(), nodeHelper.get()); for(auto & neighbour : neighbourNodes) { destination.setNode(gs, neighbour); @@ -244,26 +247,29 @@ void CPathfinder::calculatePaths() if(source.node->layer != destination.node->layer && !isLayerTransitionPossible()) continue; + destination.guarded = isDestinationGuarded(); + if(!isMovementToDestPossible()) continue; - destAction = getDestAction(); + destination.action = getDestAction(); + int turnAtNextTile = turn, moveAtNextTile = movement; - int cost = CPathfinderHelper::getMovementCost(hero, source, destination, moveAtNextTile, hlp->getTurnInfo()); + int cost = hlp->getMovementCost(source, destination, moveAtNextTile); int remains = moveAtNextTile - cost; if(remains < 0) { //occurs rarely, when hero with low movepoints tries to leave the road hlp->updateTurnInfo(++turnAtNextTile); moveAtNextTile = hlp->getMaxMovePoints(destination.node->layer); - cost = CPathfinderHelper::getMovementCost(hero, source, destination, moveAtNextTile, hlp->getTurnInfo()); //cost must be updated, movement points changed :( + cost = hlp->getMovementCost(source, destination, moveAtNextTile); //cost must be updated, movement points changed :( remains = moveAtNextTile - cost; } - if(destAction == CGPathNode::EMBARK || destAction == CGPathNode::DISEMBARK) + if(destination.action == CGPathNode::EMBARK || destination.action == CGPathNode::DISEMBARK) { /// FREE_SHIP_BOARDING bonus only remove additional penalty /// land <-> sail transition still cost movement points as normal movement - remains = hero->movementPointsAfterEmbark(moveAtNextTile, cost, destAction - 1, hlp->getTurnInfo()); + remains = hero->movementPointsAfterEmbark(moveAtNextTile, cost, destination.action - 1, hlp->getTurnInfo()); cost = moveAtNextTile - remains; } @@ -274,10 +280,11 @@ void CPathfinder::calculatePaths() destination.node->moveRemains = remains; destination.node->turns = turnAtNextTile; destination.node->theNodeBefore = source.node; - destination.node->action = destAction; + destination.node->action = destination.action; + + CMovementAfterDestinationRule rl = CMovementAfterDestinationRule(); + rl.process(hlp.get(), source, destination); - // TODO: move this method to a separete module kind of IPathfinderRule.process(). And all above. - checkMovementAfterDestPossible(); if(!destination.furtherProcessingImpossible) pq.push(destination.node); } @@ -313,17 +320,6 @@ void CPathfinder::calculatePaths() } //queue loop } -void CPathfinder::populateNeighbourTiles(std::vector & neighbourTiles) -{ - CPathfinderHelper::getNeighbours( - gs->map, - *source.tile, - source.node->coord, - neighbourTiles, - boost::logic::indeterminate, - source.node->layer == EPathfindingLayer::SAIL); -} - void CPathfinder::addTeleportExits() { neighbours.clear(); @@ -333,7 +329,7 @@ void CPathfinder::addTeleportExits() return; const CGTeleport * objTeleport = dynamic_cast(source.nodeObject); - if(isAllowedTeleportEntrance(objTeleport)) + if(hlp->isAllowedTeleportEntrance(objTeleport)) { for(auto objId : getTeleportChannelExits(objTeleport->channel, hero->tempOwner)) { @@ -497,7 +493,7 @@ bool CPathfinder::isMovementToDestPossible() const switch(destination.node->layer) { case ELayer::LAND: - if(!canMoveBetween(source.node->coord, destination.node->coord)) + if(!hlp->canMoveBetween(source.node->coord, destination.node->coord)) return false; if(isSourceGuarded()) { @@ -511,7 +507,7 @@ bool CPathfinder::isMovementToDestPossible() const break; case ELayer::SAIL: - if(!canMoveBetween(source.node->coord, destination.node->coord)) + if(!hlp->canMoveBetween(source.node->coord, destination.node->coord)) return false; if(isSourceGuarded()) { @@ -537,7 +533,7 @@ bool CPathfinder::isMovementToDestPossible() const break; case ELayer::WATER: - if(!canMoveBetween(source.node->coord, destination.node->coord) || destination.node->accessible != CGPathNode::ACCESSIBLE) + if(!hlp->canMoveBetween(source.node->coord, destination.node->coord) || destination.node->accessible != CGPathNode::ACCESSIBLE) return false; if(isDestinationGuarded()) return false; @@ -548,9 +544,9 @@ bool CPathfinder::isMovementToDestPossible() const return true; } -void CPathfinder::checkMovementAfterDestPossible() +void CMovementAfterDestinationRule::process(CPathfinderHelper * pathfinderHelper, CPathNodeInfo & source, CDestinationNodeInfo & destination) { - switch(destAction) + switch(destination.action) { /// 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 @@ -559,14 +555,14 @@ void CPathfinder::checkMovementAfterDestPossible() /// 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 const CGTeleport * objTeleport = dynamic_cast(destination.nodeObject); - if(isAllowedTeleportEntrance(objTeleport)) + if(pathfinderHelper->isAllowedTeleportEntrance(objTeleport)) { /// For now we'll always allow transit over teleporters /// Transit over whirlpools only allowed when hero protected return; } - else if(destination.nodeObject->ID == Obj::GARRISON - || destination.nodeObject->ID == Obj::GARRISON2 + else if(destination.nodeObject->ID == Obj::GARRISON + || destination.nodeObject->ID == Obj::GARRISON2 || destination.nodeObject->ID == Obj::BORDER_GATE) { /// Transit via unguarded garrisons is always possible @@ -580,20 +576,20 @@ void CPathfinder::checkMovementAfterDestPossible() return; case CGPathNode::EMBARK: - if(options.useEmbarkAndDisembark) + if(pathfinderHelper->options.useEmbarkAndDisembark) return; break; case CGPathNode::DISEMBARK: - if(options.useEmbarkAndDisembark && !isDestinationGuarded()) + if(pathfinderHelper->options.useEmbarkAndDisembark && !destination.guarded) return; break; case CGPathNode::BATTLE: /// Movement after BATTLE action only possible from guarded tile to guardian tile - if(isDestinationGuarded()) + if(destination.guarded) return; break; @@ -643,7 +639,7 @@ CGPathNode::ENodeAction CPathfinder::getDestAction() const { if(destination.nodeObject->passableFor(hero->tempOwner)) { - if(isDestinationGuarded(true)) + if(isDestinationGuarded()) action = CGPathNode::BATTLE; } else if(objRel == PlayerRelations::ENEMIES) @@ -653,7 +649,7 @@ CGPathNode::ENodeAction CPathfinder::getDestAction() const { if(destination.nodeObject->passableFor(hero->tempOwner)) { - if(isDestinationGuarded(true)) + if(isDestinationGuarded()) action = CGPathNode::BATTLE; } else @@ -716,17 +712,11 @@ bool CPathfinder::isSourceGuarded() const return false; } -bool CPathfinder::isDestinationGuarded(const bool ignoreAccessibility) const +bool CPathfinder::isDestinationGuarded() const { /// isDestinationGuarded is exception needed for garrisons. /// When monster standing behind garrison it's visitable and guarded at the same time. - if(gs->guardingCreaturePosition(destination.node->coord).valid() - && (ignoreAccessibility || destination.node->accessible == CGPathNode::BLOCKVIS)) - { - return true; - } - - return false; + return gs->guardingCreaturePosition(destination.node->coord).valid(); } bool CPathfinder::isDestinationGuardian() const @@ -855,12 +845,12 @@ CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const int3 & pos, return CGPathNode::ACCESSIBLE; } -bool CPathfinder::canMoveBetween(const int3 & a, const int3 & b) const +bool CPathfinderHelper::canMoveBetween(const int3 & a, const int3 & b) const { return gs->checkForVisitableDir(a, b); } -bool CPathfinder::isAllowedTeleportEntrance(const CGTeleport * obj) const +bool CPathfinderHelper::isAllowedTeleportEntrance(const CGTeleport * obj) const { if(!obj || !isTeleportEntrancePassable(obj, hero->tempOwner)) return false; @@ -877,12 +867,12 @@ bool CPathfinder::isAllowedTeleportEntrance(const CGTeleport * obj) const return false; } -bool CPathfinder::addTeleportTwoWay(const CGTeleport * obj) const +bool CPathfinderHelper::addTeleportTwoWay(const CGTeleport * obj) const { return options.useTeleportTwoWay && isTeleportChannelBidirectional(obj->channel, hero->tempOwner); } -bool CPathfinder::addTeleportOneWay(const CGTeleport * obj) const +bool CPathfinderHelper::addTeleportOneWay(const CGTeleport * obj) const { if(options.useTeleportOneWay && isTeleportChannelUnidirectional(obj->channel, hero->tempOwner)) { @@ -893,7 +883,7 @@ bool CPathfinder::addTeleportOneWay(const CGTeleport * obj) const return false; } -bool CPathfinder::addTeleportOneWayRandom(const CGTeleport * obj) const +bool CPathfinderHelper::addTeleportOneWayRandom(const CGTeleport * obj) const { if(options.useTeleportOneWayRandom && isTeleportChannelUnidirectional(obj->channel, hero->tempOwner)) { @@ -904,9 +894,9 @@ bool CPathfinder::addTeleportOneWayRandom(const CGTeleport * obj) const return false; } -bool CPathfinder::addTeleportWhirlpool(const CGWhirlpool * obj) const +bool CPathfinderHelper::addTeleportWhirlpool(const CGWhirlpool * obj) const { - return options.useTeleportWhirlpool && hlp->hasBonusOfType(Bonus::WHIRLPOOL_PROTECTION) && obj; + return options.useTeleportWhirlpool && hasBonusOfType(Bonus::WHIRLPOOL_PROTECTION) && obj; } TurnInfo::BonusCache::BonusCache(TBonusListPtr bl) @@ -997,8 +987,8 @@ int TurnInfo::getMaxMovePoints(const EPathfindingLayer layer) const return layer == EPathfindingLayer::SAIL ? maxMovePointsWater : maxMovePointsLand; } -CPathfinderHelper::CPathfinderHelper(const CGHeroInstance * Hero, const CPathfinder::PathfinderOptions & Options) - : turn(-1), hero(Hero), options(Options) +CPathfinderHelper::CPathfinderHelper(CGameState * gs, const CGHeroInstance * Hero, const PathfinderOptions & Options) + : CGameInfoCallback(gs, boost::optional()), turn(-1), hero(Hero), options(Options) { turnsInfo.reserve(16); updateTurnInfo(); @@ -1058,8 +1048,10 @@ int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer layer) const return turnsInfo[turn]->getMaxMovePoints(layer); } -void CPathfinderHelper::getNeighbours(const CMap * map, const TerrainTile & srct, const int3 & tile, std::vector & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing) +void CPathfinderHelper::getNeighbours(const TerrainTile & srct, const int3 & tile, std::vector & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing) { + CMap * map = gs->map; + 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), @@ -1101,28 +1093,23 @@ void CPathfinderHelper::getNeighbours(const CMap * map, const TerrainTile & srct } } -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) +int CPathfinderHelper::getMovementCost(const int3 & src, const int3 & dst, const TerrainTile * ct, const TerrainTile * dt, const int remainingMovePoints, const bool checkLast) { if(src == dst) //same tile return 0; - bool localTi = false; - if(!ti) - { - localTi = true; - ti = new TurnInfo(h); - } + auto ti = getTurnInfo(); if(ct == nullptr || dt == nullptr) { - ct = h->cb->getTile(src); - dt = h->cb->getTile(dst); + ct = hero->cb->getTile(src); + dt = hero->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(*dt, *ct, ti); + int ret = hero->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. @@ -1133,9 +1120,9 @@ int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & sr } else if(dt->terType == ETerrainType::WATER) { - if(h->boat && ct->hasFavorableWinds() && dt->hasFavorableWinds()) + if(hero->boat && ct->hasFavorableWinds() && dt->hasFavorableWinds()) ret *= 0.666; - else if(!h->boat && ti->hasBonusOfType(Bonus::WATER_WALKING)) + else if(!hero->boat && ti->hasBonusOfType(Bonus::WATER_WALKING)) { ret *= (100.0 + ti->valOfBonuses(Bonus::WATER_WALKING)) / 100.0; } @@ -1148,9 +1135,6 @@ int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & sr //diagonal move costs too much but normal move is possible - allow diagonal move for remaining move points if(ret > remainingMovePoints && remainingMovePoints >= old) { - if(localTi) - delete ti; - return remainingMovePoints; } } @@ -1162,32 +1146,21 @@ int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & sr { std::vector vec; vec.reserve(8); //optimization - getNeighbours(h->cb->gameState()->map, *dt, dst, vec, ct->terType != ETerrainType::WATER, true); + getNeighbours(*dt, dst, vec, ct->terType != ETerrainType::WATER, true); for(auto & elem : vec) { - int fcost = getMovementCost(h, dst, elem, nullptr, nullptr, left, ti, false); + int fcost = getMovementCost(dst, elem, nullptr, nullptr, left, false); if(fcost <= left) { - if(localTi) - delete ti; - return ret; } } ret = remainingMovePoints; } - if(localTi) - delete ti; - return ret; } -int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & dst) -{ - return getMovementCost(h, h->visitablePos(), dst, nullptr, nullptr, h->movement); -} - CGPathNode::CGPathNode() : coord(int3(-1, -1, -1)), layer(ELayer::WRONG) { @@ -1308,7 +1281,7 @@ CGPathNode * CPathsInfo::getNode(const int3 & coord, const ELayer layer) } CPathNodeInfo::CPathNodeInfo() - : node(nullptr), nodeObject(nullptr), tile(nullptr), coord(-1, -1, -1), blocked(false), furtherProcessingImpossible(false) + : node(nullptr), nodeObject(nullptr), tile(nullptr), coord(-1, -1, -1), guarded(false) { } @@ -1325,8 +1298,21 @@ void CPathNodeInfo::setNode(CGameState * gs, CGPathNode * n, bool excludeTopObje nodeObject = tile->topVisitableObj(excludeTopObject); } + guarded = false; +} + +CDestinationNodeInfo::CDestinationNodeInfo() + : CPathNodeInfo(), blocked(false), furtherProcessingImpossible(false), action(CGPathNode::ENodeAction::UNKNOWN) +{ +} + +void CDestinationNodeInfo::setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject) +{ + CPathNodeInfo::setNode(gs, n, excludeTopObject); + blocked = false; furtherProcessingImpossible = false; + action = CGPathNode::ENodeAction::UNKNOWN; } bool CPathNodeInfo::isNodeObjectVisitable() const diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 96233c4fa..9a7eae125 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -102,16 +102,26 @@ struct DLL_LINKAGE CPathNodeInfo const CGObjectInstance * nodeObject; const TerrainTile * tile; int3 coord; - bool blocked; - bool furtherProcessingImpossible; + bool guarded; CPathNodeInfo(); - void setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject = false); + virtual void setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject = false); bool isNodeObjectVisitable() const; }; +struct DLL_LINKAGE CDestinationNodeInfo : public CPathNodeInfo +{ + CGPathNode::ENodeAction action; + bool furtherProcessingImpossible; + bool blocked; + + CDestinationNodeInfo(); + + virtual void setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject = false) override; +}; + class CNodeHelper { public: @@ -119,8 +129,21 @@ public: virtual CGPathNode * getInitialNode() = 0; }; +class CPathfinderHelper; class CPathfinder; +class IPathfindingRule +{ +public: + virtual void process(CPathfinderHelper * pathfinderHelper, CPathNodeInfo & source, CDestinationNodeInfo & destination) = 0; +}; + +class CMovementAfterDestinationRule : public IPathfindingRule +{ +public: + virtual void process(CPathfinderHelper * pathfinderHelper, CPathNodeInfo & source, CDestinationNodeInfo & destination) override; +}; + class CNeighbourFinder { protected: @@ -130,11 +153,62 @@ protected: std::vector neighbours; public: - CNeighbourFinder(CPathfinder * pathfinder); - virtual std::vector & calculateNeighbours(); + CNeighbourFinder(); + virtual std::vector & calculateNeighbours(CPathNodeInfo & source, CPathfinderHelper * pathfinderHelper, CNodeHelper * nodeHelper); protected: - void addNeighbourTiles(); + void addNeighbourTiles(CPathNodeInfo & source, CPathfinderHelper * pathfinderHelper); +}; + +struct DLL_LINKAGE PathfinderOptions +{ + bool useFlying; + bool useWaterWalking; + bool useEmbarkAndDisembark; + bool useTeleportTwoWay; // Two-way monoliths and Subterranean Gate + bool useTeleportOneWay; // One-way monoliths with one known exit only + 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. + /// + /// 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. + /// 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. + /// + /// 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. + /// 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 + /// 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(); }; class CPathfinder : private CGameInfoCallback @@ -151,60 +225,10 @@ 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: +private: typedef EPathfindingLayer ELayer; - - struct PathfinderOptions - { - bool useFlying; - bool useWaterWalking; - bool useEmbarkAndDisembark; - bool useTeleportTwoWay; // Two-way monoliths and Subterranean Gate - bool useTeleportOneWay; // One-way monoliths with one known exit only - 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. - /// - /// 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. - /// 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. - /// - /// 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. - /// 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 - /// 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(); - } options; - + + PathfinderOptions options; const CGHeroInstance * hero; const std::vector > > &FoW; std::unique_ptr hlp; @@ -236,10 +260,8 @@ public: std::vector neighbours; CPathNodeInfo source; //current (source) path node -> we took it from the queue - CPathNodeInfo destination; //destination node -> it's a neighbour of source that we consider - CGPathNode::ENodeAction destAction; + CDestinationNodeInfo destination; //destination node -> it's a neighbour of source that we consider - void populateNeighbourTiles(std::vector & neighbourTiles); void addTeleportExits(); bool isHeroPatrolLocked() const; @@ -248,27 +270,18 @@ public: bool isLayerTransitionPossible(const ELayer dstLayer) const; bool isLayerTransitionPossible() const; bool isMovementToDestPossible() const; - void checkMovementAfterDestPossible(); CGPathNode::ENodeAction getDestAction() const; CGPathNode::ENodeAction getTeleportDestAction() const; bool isSourceInitialPosition() const; bool isSourceGuarded() const; - bool isDestinationGuarded(const bool ignoreAccessibility = true) const; + bool isDestinationGuarded() const; bool isDestinationGuardian() const; void initializePatrol(); void initializeGraph(); 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; - bool addTeleportTwoWay(const CGTeleport * obj) const; - bool addTeleportOneWay(const CGTeleport * obj) const; - bool addTeleportOneWayRandom(const CGTeleport * obj) const; - bool addTeleportWhirlpool(const CGWhirlpool * obj) const; - }; struct DLL_LINKAGE TurnInfo @@ -300,10 +313,15 @@ struct DLL_LINKAGE TurnInfo int getMaxMovePoints(const EPathfindingLayer layer) const; }; -class DLL_LINKAGE CPathfinderHelper +class DLL_LINKAGE CPathfinderHelper : private CGameInfoCallback { public: - CPathfinderHelper(const CGHeroInstance * Hero, const CPathfinder::PathfinderOptions & Options); + int turn; + const CGHeroInstance * hero; + std::vector turnsInfo; + const PathfinderOptions & options; + + CPathfinderHelper(CGameState * gs, const CGHeroInstance * Hero, const PathfinderOptions & Options); ~CPathfinderHelper(); void updateTurnInfo(const int turn = 0); bool isLayerAvailable(const EPathfindingLayer layer) const; @@ -311,43 +329,36 @@ public: bool hasBonusOfType(const Bonus::BonusType type, const int subtype = -1) const; int getMaxMovePoints(const EPathfindingLayer layer) const; - static void getNeighbours(const CMap * map, const TerrainTile & srct, const int3 & tile, std::vector & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing); + bool isAllowedTeleportEntrance(const CGTeleport * obj) const; + bool addTeleportTwoWay(const CGTeleport * obj) const; + bool addTeleportOneWay(const CGTeleport * obj) const; + bool addTeleportOneWayRandom(const CGTeleport * obj) const; + bool addTeleportWhirlpool(const CGWhirlpool * 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) + + void getNeighbours(const TerrainTile & srct, const int3 & tile, std::vector & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing); - static int getMovementCost( - const CGHeroInstance * h, + int getMovementCost( 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, + int getMovementCost( const CPathNodeInfo & src, const CPathNodeInfo & dst, const int remainingMovePoints = -1, - const TurnInfo * ti = nullptr, const bool checkLast = true) { return getMovementCost( - h, src.coord, dst.coord, src.tile, dst.tile, remainingMovePoints, - ti, checkLast ); } - - static int getMovementCost(const CGHeroInstance * h, const int3 & dst); - -private: - int turn; - const CGHeroInstance * hero; - std::vector turnsInfo; - const CPathfinder::PathfinderOptions & options; }; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index f107f5747..81cc31455 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2158,10 +2158,14 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo tmh.movePoints = h->movement; //check if destination tile is available - auto ti = make_unique(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, nullptr, nullptr, h->movement, ti.get()); + auto pathfinderHelper = make_unique(gs, h, PathfinderOptions()); + + pathfinderHelper->updateTurnInfo(0); + auto ti = pathfinderHelper->getTurnInfo(); + + const bool canFly = pathfinderHelper->hasBonusOfType(Bonus::FLYING_MOVEMENT); + const bool canWalkOnSea = pathfinderHelper->hasBonusOfType(Bonus::WATER_WALKING); + const int cost = pathfinderHelper->getMovementCost(h->getPosition(), hmpos, nullptr, nullptr, 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) @@ -2253,14 +2257,14 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo if (!transit && embarking) { - tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, false, ti.get()); + tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, false, ti); return doMove(TryMoveHero::EMBARK, IGNORE_GUARDS, DONT_VISIT_DEST, LEAVING_TILE); // In H3 embark ignore guards } if (disembarking) { - tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, true, ti.get()); + tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, true, ti); return doMove(TryMoveHero::DISEMBARK, CHECK_FOR_GUARDS, VISIT_DEST, LEAVING_TILE); }