diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index cf142f177..e47c31375 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -1196,11 +1196,11 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h) //return cb->getTile(coord,false)->topVisitableObj(ignoreHero); }; - auto isTeleportAction = [&](CGPathNode::ENodeAction action) -> bool + auto isTeleportAction = [&](EPathNodeAction action) -> bool { - if(action != CGPathNode::TELEPORT_NORMAL && action != CGPathNode::TELEPORT_BLOCKING_VISIT) + if(action != EPathNodeAction::TELEPORT_NORMAL && action != EPathNodeAction::TELEPORT_BLOCKING_VISIT) { - if(action != CGPathNode::TELEPORT_BATTLE) + if(action != EPathNodeAction::TELEPORT_BATTLE) { return false; } @@ -1311,7 +1311,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h) doChannelProbing(); } - if(path.nodes[0].action == CGPathNode::BLOCKING_VISIT || path.nodes[0].action == CGPathNode::BATTLE) + if(path.nodes[0].action == EPathNodeAction::BLOCKING_VISIT || path.nodes[0].action == EPathNodeAction::BATTLE) { // when we take resource we do not reach its position. We even might not move // also guarded town is not get visited automatically after capturing diff --git a/AI/Nullkiller/AIUtility.h b/AI/Nullkiller/AIUtility.h index 9f193c285..60fef0f24 100644 --- a/AI/Nullkiller/AIUtility.h +++ b/AI/Nullkiller/AIUtility.h @@ -46,7 +46,6 @@ #include "../../lib/spells/CSpellHandler.h" #include "../../lib/CStopWatch.h" #include "../../lib/mapObjects/CGHeroInstance.h" -#include "../../lib/CPathfinder.h" #include "../../CCallback.h" #include diff --git a/AI/Nullkiller/Behaviors/BuildingBehavior.cpp b/AI/Nullkiller/Behaviors/BuildingBehavior.cpp index 2b9fac6d6..3220ff891 100644 --- a/AI/Nullkiller/Behaviors/BuildingBehavior.cpp +++ b/AI/Nullkiller/Behaviors/BuildingBehavior.cpp @@ -15,7 +15,6 @@ #include "../Goals/Composition.h" #include "../Goals/BuildThis.h" #include "../Goals/SaveResources.h" -#include "lib/CPathfinder.h" #include "../Engine/Nullkiller.h" namespace NKAI diff --git a/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp b/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp index 519e66936..7b2a57396 100644 --- a/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp +++ b/AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp @@ -13,7 +13,6 @@ #include "../AIUtility.h" #include "../Goals/BuyArmy.h" #include "../Engine/Nullkiller.h" -#include "lib/CPathfinder.h" namespace NKAI { diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index 3fafc819d..ecba2746b 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -21,7 +21,6 @@ #include "../Goals/CaptureObject.h" #include "../Markers/DefendTown.h" #include "../Goals/ExchangeSwapTownHeroes.h" -#include "lib/CPathfinder.h" namespace NKAI { diff --git a/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp b/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp index d50b6018b..c2622cdc0 100644 --- a/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp +++ b/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp @@ -16,7 +16,6 @@ #include "../Markers/ArmyUpgrade.h" #include "GatherArmyBehavior.h" #include "../AIUtility.h" -#include "lib/CPathfinder.h" namespace NKAI { diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index 81952ad8d..bbc9dd737 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -13,7 +13,6 @@ #include "../AIUtility.h" #include "../Goals/RecruitHero.h" #include "../Goals/ExecuteHeroChain.h" -#include "lib/CPathfinder.h" namespace NKAI { diff --git a/AI/Nullkiller/Behaviors/StartupBehavior.cpp b/AI/Nullkiller/Behaviors/StartupBehavior.cpp index e29f29cb7..b9c994299 100644 --- a/AI/Nullkiller/Behaviors/StartupBehavior.cpp +++ b/AI/Nullkiller/Behaviors/StartupBehavior.cpp @@ -16,7 +16,6 @@ #include "../Goals/ExecuteHeroChain.h" #include "../Goals/ExchangeSwapTownHeroes.h" #include "lib/mapObjects/MapObjects.h" //for victory conditions -#include "lib/CPathfinder.h" #include "../Engine/Nullkiller.h" namespace NKAI diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 1e8790f64..ce83d3afc 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -16,7 +16,6 @@ #include "../../../lib/mapObjectConstructors/CBankInstanceConstructor.h" #include "../../../lib/mapObjects/MapObjects.h" #include "../../../lib/CCreatureHandler.h" -#include "../../../lib/CPathfinder.h" #include "../../../lib/CGameStateFwd.h" #include "../../../lib/VCMI_Lib.h" #include "../../../lib/StartInfo.h" diff --git a/AI/Nullkiller/Goals/AbstractGoal.cpp b/AI/Nullkiller/Goals/AbstractGoal.cpp index c63482230..fd27579c5 100644 --- a/AI/Nullkiller/Goals/AbstractGoal.cpp +++ b/AI/Nullkiller/Goals/AbstractGoal.cpp @@ -10,7 +10,6 @@ #include "StdInc.h" #include "AbstractGoal.h" #include "../AIGateway.h" -#include "../../../lib/CPathfinder.h" #include "../../../lib/StringConstants.h" namespace NKAI diff --git a/AI/Nullkiller/Goals/AdventureSpellCast.cpp b/AI/Nullkiller/Goals/AdventureSpellCast.cpp index 41b2764dc..8a8a3cf96 100644 --- a/AI/Nullkiller/Goals/AdventureSpellCast.cpp +++ b/AI/Nullkiller/Goals/AdventureSpellCast.cpp @@ -10,7 +10,6 @@ #include "StdInc.h" #include "AdventureSpellCast.h" #include "../AIGateway.h" -#include "../../../lib/CPathfinder.h" namespace NKAI { diff --git a/AI/Nullkiller/Goals/BuildBoat.cpp b/AI/Nullkiller/Goals/BuildBoat.cpp index 5ca46dc1c..e69b90d3e 100644 --- a/AI/Nullkiller/Goals/BuildBoat.cpp +++ b/AI/Nullkiller/Goals/BuildBoat.cpp @@ -10,7 +10,6 @@ #include "StdInc.h" #include "BuildBoat.h" #include "../AIGateway.h" -#include "../../../lib/CPathfinder.h" #include "../Behaviors/CaptureObjectsBehavior.h" namespace NKAI diff --git a/AI/Nullkiller/Goals/BuildThis.cpp b/AI/Nullkiller/Goals/BuildThis.cpp index 102e72f59..d61caae44 100644 --- a/AI/Nullkiller/Goals/BuildThis.cpp +++ b/AI/Nullkiller/Goals/BuildThis.cpp @@ -11,7 +11,6 @@ #include "BuildThis.h" #include "../AIGateway.h" #include "../AIUtility.h" -#include "../../../lib/CPathfinder.h" #include "../../../lib/StringConstants.h" diff --git a/AI/Nullkiller/Goals/CompleteQuest.cpp b/AI/Nullkiller/Goals/CompleteQuest.cpp index cbffabad2..6e39c3d47 100644 --- a/AI/Nullkiller/Goals/CompleteQuest.cpp +++ b/AI/Nullkiller/Goals/CompleteQuest.cpp @@ -11,7 +11,6 @@ #include "CompleteQuest.h" #include "../Behaviors/CaptureObjectsBehavior.h" #include "../AIGateway.h" -#include "../../../lib/CPathfinder.h" #include "../../../lib/VCMI_Lib.h" #include "../../../lib/CGeneralTextHandler.h" diff --git a/AI/Nullkiller/Goals/Composition.cpp b/AI/Nullkiller/Goals/Composition.cpp index 3d54e1fdf..a0a487820 100644 --- a/AI/Nullkiller/Goals/Composition.cpp +++ b/AI/Nullkiller/Goals/Composition.cpp @@ -11,7 +11,6 @@ #include "Composition.h" #include "../AIGateway.h" #include "../AIUtility.h" -#include "../../../lib/CPathfinder.h" #include "../../../lib/StringConstants.h" diff --git a/AI/Nullkiller/Goals/DismissHero.cpp b/AI/Nullkiller/Goals/DismissHero.cpp index 303c502b3..ce26e4f10 100644 --- a/AI/Nullkiller/Goals/DismissHero.cpp +++ b/AI/Nullkiller/Goals/DismissHero.cpp @@ -10,7 +10,6 @@ #include "StdInc.h" #include "DismissHero.h" #include "../AIGateway.h" -#include "../../../lib/CPathfinder.h" namespace NKAI { diff --git a/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp b/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp index 7ca29c847..80e8af201 100644 --- a/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp +++ b/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp @@ -11,7 +11,6 @@ #include "ExchangeSwapTownHeroes.h" #include "ExecuteHeroChain.h" #include "../AIGateway.h" -#include "../../../lib/CPathfinder.h" #include "../Engine/Nullkiller.h" namespace NKAI diff --git a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp index c9cbe04a9..820828ea1 100644 --- a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp +++ b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp @@ -10,7 +10,6 @@ #include "StdInc.h" #include "ExecuteHeroChain.h" #include "../AIGateway.h" -#include "../../../lib/CPathfinder.h" #include "../Engine/Nullkiller.h" namespace NKAI @@ -104,9 +103,9 @@ void ExecuteHeroChain::accept(AIGateway * ai) { auto targetNode = cb->getPathsInfo(hero)->getPathInfo(node.coord); - if(targetNode->accessible == CGPathNode::EAccessibility::NOT_SET - || targetNode->accessible == CGPathNode::EAccessibility::BLOCKED - || targetNode->accessible == CGPathNode::EAccessibility::FLYABLE + if(targetNode->accessible == EPathAccessibility::NOT_SET + || targetNode->accessible == EPathAccessibility::BLOCKED + || targetNode->accessible == EPathAccessibility::FLYABLE || targetNode->turns != 0) { logAi->error( diff --git a/AI/Nullkiller/Goals/RecruitHero.cpp b/AI/Nullkiller/Goals/RecruitHero.cpp index d84ef002e..9dc63020b 100644 --- a/AI/Nullkiller/Goals/RecruitHero.cpp +++ b/AI/Nullkiller/Goals/RecruitHero.cpp @@ -11,7 +11,6 @@ #include "Goals.h" #include "../AIGateway.h" #include "../AIUtility.h" -#include "../../../lib/CPathfinder.h" #include "../../../lib/StringConstants.h" diff --git a/AI/Nullkiller/Goals/SaveResources.cpp b/AI/Nullkiller/Goals/SaveResources.cpp index 46f5cfe44..2cf03fc4c 100644 --- a/AI/Nullkiller/Goals/SaveResources.cpp +++ b/AI/Nullkiller/Goals/SaveResources.cpp @@ -10,7 +10,6 @@ #include "StdInc.h" #include "SaveResources.h" #include "../AIGateway.h" -#include "../../../lib/CPathfinder.h" #include "../Behaviors/CaptureObjectsBehavior.h" namespace NKAI diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 8577a9f2f..662446a26 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -16,7 +16,9 @@ #include "../../../CCallback.h" #include "../../../lib/mapping/CMap.h" #include "../../../lib/mapObjects/MapObjects.h" -#include "../../../lib/PathfinderUtil.h" +#include "../../../lib/pathfinder/CPathfinder.h" +#include "../../../lib/pathfinder/PathfinderUtil.h" +#include "../../../lib/pathfinder/PathfinderOptions.h" #include "../../../lib/CPlayerState.h" namespace NKAI @@ -204,7 +206,7 @@ std::vector AINodeStorage::getInitialNodes() initialNode->moveRemains = actor->initialMovement; initialNode->danger = 0; initialNode->setCost(actor->initialTurn); - initialNode->action = CGPathNode::ENodeAction::NORMAL; + initialNode->action = EPathNodeAction::NORMAL; if(actor->isMovable) { @@ -222,7 +224,7 @@ std::vector AINodeStorage::getInitialNodes() return initialNodes; } -void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility) +void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, EPathAccessibility accessibility) { for(AIPathNode & heroNode : nodes.get(coord, layer)) { @@ -260,7 +262,7 @@ void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInf void AINodeStorage::commit( AIPathNode * destination, const AIPathNode * source, - CGPathNode::ENodeAction action, + EPathNodeAction action, int turn, int movementLeft, float cost) const @@ -310,7 +312,7 @@ std::vector AINodeStorage::calculateNeighbours( { auto nextNode = getOrCreateNode(neighbour, i, srcNode->actor); - if(!nextNode || nextNode.value()->accessible == CGPathNode::NOT_SET) + if(!nextNode || nextNode.value()->accessible == EPathAccessibility::NOT_SET) continue; neighbours.push_back(nextNode.value()); @@ -340,7 +342,7 @@ bool AINodeStorage::increaseHeroChainTurnLimit() { for(AIPathNode & node : chains) { - if(node.turns <= heroChainTurn && node.action != CGPathNode::ENodeAction::UNKNOWN) + if(node.turns <= heroChainTurn && node.action != EPathNodeAction::UNKNOWN) { commitedTiles.insert(pos); break; @@ -370,7 +372,7 @@ bool AINodeStorage::calculateHeroChainFinal() { if(node.turns > heroChainTurn && !node.locked - && node.action != CGPathNode::ENodeAction::UNKNOWN + && node.action != EPathNodeAction::UNKNOWN && node.actor->actorExchangeCount > 1 && !hasBetterChain(&node, &node, chains)) { @@ -442,7 +444,7 @@ public: for(AIPathNode & node : chains) { - if(node.turns <= heroChainTurn && node.action != CGPathNode::ENodeAction::UNKNOWN) + if(node.turns <= heroChainTurn && node.action != EPathNodeAction::UNKNOWN) existingChains.push_back(&node); } @@ -642,16 +644,16 @@ void HeroChainCalculationTask::calculateHeroChain( if(node->actor->actorExchangeCount + srcNode->actor->actorExchangeCount > CHAIN_MAX_DEPTH) continue; - if(node->action == CGPathNode::ENodeAction::BATTLE - || node->action == CGPathNode::ENodeAction::TELEPORT_BATTLE - || node->action == CGPathNode::ENodeAction::TELEPORT_NORMAL - || node->action == CGPathNode::ENodeAction::TELEPORT_BLOCKING_VISIT) + if(node->action == EPathNodeAction::BATTLE + || node->action == EPathNodeAction::TELEPORT_BATTLE + || node->action == EPathNodeAction::TELEPORT_NORMAL + || node->action == EPathNodeAction::TELEPORT_BLOCKING_VISIT) { continue; } if(node->turns > heroChainTurn - || (node->action == CGPathNode::ENodeAction::UNKNOWN && node->actor->hero) + || (node->action == EPathNodeAction::UNKNOWN && node->actor->hero) || (node->actor->chainMask & srcNode->actor->chainMask) != 0) { #if NKAI_PATHFINDER_TRACE_LEVEL >= 2 @@ -664,7 +666,7 @@ void HeroChainCalculationTask::calculateHeroChain( srcNode->coord.toString(), (node->turns > heroChainTurn ? "turn limit" - : (node->action == CGPathNode::ENodeAction::UNKNOWN && node->actor->hero) + : (node->action == EPathNodeAction::UNKNOWN && node->actor->hero) ? "action unknown" : "chain mask")); #endif @@ -691,8 +693,8 @@ void HeroChainCalculationTask::calculateHeroChain( std::vector & result) { if(carrier->armyLoss < carrier->actor->armyValue - && (carrier->action != CGPathNode::BATTLE || (carrier->actor->allowBattle && carrier->specialAction)) - && carrier->action != CGPathNode::BLOCKING_VISIT + && (carrier->action != EPathNodeAction::BATTLE || (carrier->actor->allowBattle && carrier->specialAction)) + && carrier->action != EPathNodeAction::BLOCKING_VISIT && (other->armyLoss == 0 || other->armyLoss < other->actor->armyValue)) { #if NKAI_PATHFINDER_TRACE_LEVEL >= 2 @@ -745,7 +747,7 @@ void HeroChainCalculationTask::addHeroChain(const std::vector auto exchangeNode = chainNodeOptional.value(); - if(exchangeNode->action != CGPathNode::ENodeAction::UNKNOWN) + if(exchangeNode->action != EPathNodeAction::UNKNOWN) { #if NKAI_PATHFINDER_TRACE_LEVEL >= 2 logAi->trace( @@ -1055,12 +1057,12 @@ struct TowmPortalFinder movementCost += bestNode->getCost(); - if(node->action == CGPathNode::UNKNOWN || node->getCost() > movementCost) + if(node->action == EPathNodeAction::UNKNOWN || node->getCost() > movementCost) { nodeStorage->commit( node, nodeStorage->getAINode(bestNode), - CGPathNode::TELEPORT_NORMAL, + EPathNodeAction::TELEPORT_NORMAL, bestNode->turns, bestNode->moveRemains - movementNeeded, movementCost); @@ -1188,7 +1190,7 @@ bool AINodeStorage::hasBetterChain( { auto sameNode = node.actor == candidateNode->actor; - if(sameNode || node.action == CGPathNode::ENodeAction::UNKNOWN || !node.actor || !node.actor->hero) + if(sameNode || node.action == EPathNodeAction::UNKNOWN || !node.actor || !node.actor->hero) { continue; } @@ -1271,7 +1273,7 @@ bool AINodeStorage::isTileAccessible(const HeroPtr & hero, const int3 & pos, con for(const AIPathNode & node : chains) { - if(node.action != CGPathNode::ENodeAction::UNKNOWN + if(node.action != EPathNodeAction::UNKNOWN && node.actor && node.actor->hero == hero.h) { return true; @@ -1291,7 +1293,7 @@ std::vector AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand) for(const AIPathNode & node : chains) { - if(node.action == CGPathNode::ENodeAction::UNKNOWN || !node.actor || !node.actor->hero) + if(node.action == EPathNodeAction::UNKNOWN || !node.actor || !node.actor->hero) { continue; } diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index bf4b852e3..22c2d6b21 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -13,7 +13,8 @@ #define NKAI_PATHFINDER_TRACE_LEVEL 0 #define NKAI_TRACE_LEVEL 0 -#include "../../../lib/CPathfinder.h" +#include "../../../lib/pathfinder/CGPathNode.h" +#include "../../../lib/pathfinder/INodeStorage.h" #include "../../../lib/mapObjects/CGHeroInstance.h" #include "../AIUtility.h" #include "../Engine/FuzzyHelper.h" @@ -52,8 +53,8 @@ struct AIPathNode : public CGPathNode STRONG_INLINE bool blocked() const { - return accessible == CGPathNode::EAccessibility::NOT_SET - || accessible == CGPathNode::EAccessibility::BLOCKED; + return accessible == EPathAccessibility::NOT_SET + || accessible == EPathAccessibility::BLOCKED; } void addSpecialAction(std::shared_ptr action); @@ -195,7 +196,7 @@ public: void commit( AIPathNode * destination, const AIPathNode * source, - CGPathNode::ENodeAction action, + EPathNodeAction action, int turn, int movementLeft, float cost) const; @@ -261,7 +262,7 @@ public: } STRONG_INLINE - void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility); + void resetTile(const int3 & tile, EPathfindingLayer layer, EPathAccessibility accessibility); STRONG_INLINE int getBucket(const ChainActor * actor) const { diff --git a/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp b/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp index 0f1be5087..b7314b3d1 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp +++ b/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp @@ -15,6 +15,8 @@ #include "Rules/AIPreviousNodeRule.h" #include "../Engine//Nullkiller.h" +#include "../../../lib/pathfinder/CPathfinder.h" + namespace NKAI { namespace AIPathfinding @@ -44,6 +46,8 @@ namespace AIPathfinding { } + AIPathfinderConfig::~AIPathfinderConfig() = default; + CPathfinderHelper * AIPathfinderConfig::getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) { auto hero = aiNodeStorage->getHero(source.node); diff --git a/AI/Nullkiller/Pathfinding/AIPathfinderConfig.h b/AI/Nullkiller/Pathfinding/AIPathfinderConfig.h index 05e6f5558..3d229a812 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinderConfig.h +++ b/AI/Nullkiller/Pathfinding/AIPathfinderConfig.h @@ -11,6 +11,7 @@ #pragma once #include "AINodeStorage.h" +#include "../../../lib/pathfinder/PathfinderOptions.h" namespace NKAI { @@ -31,6 +32,8 @@ namespace AIPathfinding Nullkiller * ai, std::shared_ptr nodeStorage); + ~AIPathfinderConfig(); + virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override; }; } diff --git a/AI/Nullkiller/Pathfinding/Actions/SpecialAction.h b/AI/Nullkiller/Pathfinding/Actions/SpecialAction.h index 7ed87814c..c14589e75 100644 --- a/AI/Nullkiller/Pathfinding/Actions/SpecialAction.h +++ b/AI/Nullkiller/Pathfinding/Actions/SpecialAction.h @@ -13,6 +13,11 @@ #include "../../AIUtility.h" #include "../../Goals/AbstractGoal.h" +VCMI_LIB_NAMESPACE_BEGIN +struct PathNodeInfo; +struct CDestinationNodeInfo; +VCMI_LIB_NAMESPACE_END + namespace NKAI { diff --git a/AI/Nullkiller/Pathfinding/Actors.cpp b/AI/Nullkiller/Pathfinding/Actors.cpp index 92a53b0cd..1a0c708d3 100644 --- a/AI/Nullkiller/Pathfinding/Actors.cpp +++ b/AI/Nullkiller/Pathfinding/Actors.cpp @@ -13,6 +13,7 @@ #include "../Engine/Nullkiller.h" #include "../../../CCallback.h" #include "../../../lib/mapObjects/MapObjects.h" +#include "../../../lib/pathfinder/TurnInfo.h" #include "Actions/BuyArmyAction.h" using namespace NKAI; diff --git a/AI/Nullkiller/Pathfinding/Actors.h b/AI/Nullkiller/Pathfinding/Actors.h index a4009caa8..eef4bee39 100644 --- a/AI/Nullkiller/Pathfinding/Actors.h +++ b/AI/Nullkiller/Pathfinding/Actors.h @@ -10,7 +10,6 @@ #pragma once -#include "../../../lib/CPathfinder.h" #include "../../../lib/mapObjects/CGHeroInstance.h" #include "../AIUtility.h" #include "Actions/SpecialAction.h" @@ -83,7 +82,7 @@ public: ExchangeResult tryExchangeNoLock(const ChainActor * other) const { return tryExchangeNoLock(this, other); } void setBaseActor(HeroActor * base); virtual const CGObjectInstance * getActorObject() const { return hero; } - int maxMovePoints(CGPathNode::ELayer layer); + int maxMovePoints(EPathfindingLayer layer); protected: virtual ExchangeResult tryExchangeNoLock(const ChainActor * specialActor, const ChainActor * other) const; diff --git a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp index 39af7c893..f555b18c3 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp @@ -131,11 +131,11 @@ namespace AIPathfinding { AIPathNode * boatNode = boatNodeOptional.value(); - if(boatNode->action == CGPathNode::UNKNOWN) + if(boatNode->action == EPathNodeAction::UNKNOWN) { boatNode->addSpecialAction(virtualBoat); destination.blocked = false; - destination.action = CGPathNode::ENodeAction::EMBARK; + destination.action = EPathNodeAction::EMBARK; destination.node = boatNode; result = true; } diff --git a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.h b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.h index c1b57b530..f7d5e27b8 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.h +++ b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.h @@ -15,6 +15,7 @@ #include "../Actions/BoatActions.h" #include "../../../../CCallback.h" #include "../../../../lib/mapObjects/MapObjects.h" +#include "../../../../lib/pathfinder/PathfindingRules.h" namespace NKAI { diff --git a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.h b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.h index 27cde5494..cbaf3909a 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.h +++ b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.h @@ -14,6 +14,7 @@ #include "../../AIGateway.h" #include "../../../../CCallback.h" #include "../../../../lib/mapObjects/MapObjects.h" +#include "../../../../lib/pathfinder/PathfindingRules.h" namespace NKAI { diff --git a/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp index 5cd4f9376..8886f5233 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp @@ -31,7 +31,7 @@ namespace AIPathfinding return; if(blocker == BlockingReason::DESTINATION_BLOCKED - && destination.action == CGPathNode::EMBARK + && destination.action == EPathNodeAction::EMBARK && nodeStorage->getAINode(destination.node)->specialAction) { return; diff --git a/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.h b/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.h index 65cfa4678..d33624514 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.h +++ b/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.h @@ -14,6 +14,7 @@ #include "../../AIGateway.h" #include "../../../../CCallback.h" #include "../../../../lib/mapObjects/MapObjects.h" +#include "../../../../lib/pathfinder/PathfindingRules.h" namespace NKAI { diff --git a/AI/Nullkiller/Pathfinding/Rules/AIPreviousNodeRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AIPreviousNodeRule.cpp index 975f37c2d..5b685f0d8 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIPreviousNodeRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AIPreviousNodeRule.cpp @@ -10,6 +10,8 @@ #include "StdInc.h" #include "AIPreviousNodeRule.h" +#include "../../../../lib/pathfinder/CPathfinder.h" + namespace NKAI { namespace AIPathfinding @@ -25,8 +27,8 @@ namespace AIPathfinding const PathfinderConfig * pathfinderConfig, CPathfinderHelper * pathfinderHelper) const { - if(source.node->action == CGPathNode::ENodeAction::BLOCKING_VISIT - || source.node->action == CGPathNode::ENodeAction::VISIT) + if(source.node->action == EPathNodeAction::BLOCKING_VISIT + || source.node->action == EPathNodeAction::VISIT) { if(source.nodeObject && isObjectPassable(source.nodeObject, pathfinderHelper->hero->tempOwner, source.objectRelations)) diff --git a/AI/Nullkiller/Pathfinding/Rules/AIPreviousNodeRule.h b/AI/Nullkiller/Pathfinding/Rules/AIPreviousNodeRule.h index 22b4ef89c..af9bb4fa4 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIPreviousNodeRule.h +++ b/AI/Nullkiller/Pathfinding/Rules/AIPreviousNodeRule.h @@ -14,6 +14,7 @@ #include "../../AIGateway.h" #include "../../../../CCallback.h" #include "../../../../lib/mapObjects/MapObjects.h" +#include "../../../../lib/pathfinder/PathfindingRules.h" namespace NKAI { diff --git a/AI/VCAI/AIUtility.h b/AI/VCAI/AIUtility.h index cbe0a43c0..ab1dc6521 100644 --- a/AI/VCAI/AIUtility.h +++ b/AI/VCAI/AIUtility.h @@ -16,7 +16,6 @@ #include "../../lib/spells/CSpellHandler.h" #include "../../lib/CStopWatch.h" #include "../../lib/mapObjects/CGHeroInstance.h" -#include "../../lib/CPathfinder.h" #include "../../CCallback.h" class CCallback; diff --git a/AI/VCAI/Goals/AbstractGoal.cpp b/AI/VCAI/Goals/AbstractGoal.cpp index 8275b89ee..87a04f089 100644 --- a/AI/VCAI/Goals/AbstractGoal.cpp +++ b/AI/VCAI/Goals/AbstractGoal.cpp @@ -14,7 +14,6 @@ #include "../FuzzyHelper.h" #include "../ResourceManager.h" #include "../BuildingManager.h" -#include "../../../lib/CPathfinder.h" #include "../../../lib/StringConstants.h" extern boost::thread_specific_ptr cb; diff --git a/AI/VCAI/Goals/AdventureSpellCast.cpp b/AI/VCAI/Goals/AdventureSpellCast.cpp index cd78ea671..c67d81d66 100644 --- a/AI/VCAI/Goals/AdventureSpellCast.cpp +++ b/AI/VCAI/Goals/AdventureSpellCast.cpp @@ -13,7 +13,6 @@ #include "../FuzzyHelper.h" #include "../AIhelper.h" #include "../../../lib/mapObjects/CGTownInstance.h" -#include "../../../lib/CPathfinder.h" extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; diff --git a/AI/VCAI/Goals/Build.cpp b/AI/VCAI/Goals/Build.cpp index c0d0e5155..3bd78b9ef 100644 --- a/AI/VCAI/Goals/Build.cpp +++ b/AI/VCAI/Goals/Build.cpp @@ -17,7 +17,6 @@ #include "../ResourceManager.h" #include "../BuildingManager.h" #include "../../../lib/mapObjects/CGTownInstance.h" -#include "../../../lib/CPathfinder.h" #include "../../../lib/StringConstants.h" diff --git a/AI/VCAI/Goals/BuildBoat.cpp b/AI/VCAI/Goals/BuildBoat.cpp index 4c4136b22..76e76791f 100644 --- a/AI/VCAI/Goals/BuildBoat.cpp +++ b/AI/VCAI/Goals/BuildBoat.cpp @@ -12,7 +12,6 @@ #include "../VCAI.h" #include "../FuzzyHelper.h" #include "../AIhelper.h" -#include "../../../lib/CPathfinder.h" extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; diff --git a/AI/VCAI/Goals/BuildThis.cpp b/AI/VCAI/Goals/BuildThis.cpp index 5223ed39c..9a62539a7 100644 --- a/AI/VCAI/Goals/BuildThis.cpp +++ b/AI/VCAI/Goals/BuildThis.cpp @@ -16,7 +16,6 @@ #include "../ResourceManager.h" #include "../BuildingManager.h" #include "../../../lib/mapObjects/CGTownInstance.h" -#include "../../../lib/CPathfinder.h" #include "../../../lib/StringConstants.h" diff --git a/AI/VCAI/Goals/CollectRes.cpp b/AI/VCAI/Goals/CollectRes.cpp index 90e79628a..35d66413d 100644 --- a/AI/VCAI/Goals/CollectRes.cpp +++ b/AI/VCAI/Goals/CollectRes.cpp @@ -16,7 +16,6 @@ #include "../ResourceManager.h" #include "../BuildingManager.h" #include "../../../lib/mapObjects/CGMarket.h" -#include "../../../lib/CPathfinder.h" #include "../../../lib/StringConstants.h" diff --git a/AI/VCAI/Goals/CompleteQuest.cpp b/AI/VCAI/Goals/CompleteQuest.cpp index ff02c9adb..164aacb3c 100644 --- a/AI/VCAI/Goals/CompleteQuest.cpp +++ b/AI/VCAI/Goals/CompleteQuest.cpp @@ -13,7 +13,6 @@ #include "../FuzzyHelper.h" #include "../AIhelper.h" #include "../../../lib/mapObjects/CQuest.h" -#include "../../../lib/CPathfinder.h" extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; diff --git a/AI/VCAI/Goals/Conquer.cpp b/AI/VCAI/Goals/Conquer.cpp index c6808fe4f..3f9f4b16c 100644 --- a/AI/VCAI/Goals/Conquer.cpp +++ b/AI/VCAI/Goals/Conquer.cpp @@ -15,7 +15,6 @@ #include "../FuzzyHelper.h" #include "../ResourceManager.h" #include "../BuildingManager.h" -#include "../../../lib/CPathfinder.h" #include "../../../lib/StringConstants.h" diff --git a/AI/VCAI/Goals/Explore.cpp b/AI/VCAI/Goals/Explore.cpp index 0300a6105..bdd992c96 100644 --- a/AI/VCAI/Goals/Explore.cpp +++ b/AI/VCAI/Goals/Explore.cpp @@ -15,7 +15,6 @@ #include "../FuzzyHelper.h" #include "../ResourceManager.h" #include "../BuildingManager.h" -#include "../../../lib/CPathfinder.h" #include "../../../lib/StringConstants.h" #include "../../../lib/CPlayerState.h" diff --git a/AI/VCAI/Goals/GatherArmy.cpp b/AI/VCAI/Goals/GatherArmy.cpp index a226a99c6..c36e282e5 100644 --- a/AI/VCAI/Goals/GatherArmy.cpp +++ b/AI/VCAI/Goals/GatherArmy.cpp @@ -16,7 +16,6 @@ #include "../ResourceManager.h" #include "../BuildingManager.h" #include "../../../lib/mapObjects/CGTownInstance.h" -#include "../../../lib/CPathfinder.h" #include "../../../lib/StringConstants.h" diff --git a/AI/VCAI/Goals/GatherTroops.cpp b/AI/VCAI/Goals/GatherTroops.cpp index f2f2a28b4..a93960237 100644 --- a/AI/VCAI/Goals/GatherTroops.cpp +++ b/AI/VCAI/Goals/GatherTroops.cpp @@ -16,7 +16,6 @@ #include "../ResourceManager.h" #include "../BuildingManager.h" #include "../../../lib/mapObjects/CGTownInstance.h" -#include "../../../lib/CPathfinder.h" #include "../../../lib/StringConstants.h" diff --git a/AI/VCAI/Goals/RecruitHero.cpp b/AI/VCAI/Goals/RecruitHero.cpp index f40b66d6a..73f24762c 100644 --- a/AI/VCAI/Goals/RecruitHero.cpp +++ b/AI/VCAI/Goals/RecruitHero.cpp @@ -15,7 +15,6 @@ #include "../FuzzyHelper.h" #include "../ResourceManager.h" #include "../BuildingManager.h" -#include "../../../lib/CPathfinder.h" #include "../../../lib/StringConstants.h" diff --git a/AI/VCAI/Goals/VisitObj.cpp b/AI/VCAI/Goals/VisitObj.cpp index ec5ad68df..5aab8cf06 100644 --- a/AI/VCAI/Goals/VisitObj.cpp +++ b/AI/VCAI/Goals/VisitObj.cpp @@ -15,7 +15,6 @@ #include "../FuzzyHelper.h" #include "../ResourceManager.h" #include "../BuildingManager.h" -#include "../../../lib/CPathfinder.h" #include "../../../lib/StringConstants.h" diff --git a/AI/VCAI/Goals/VisitTile.cpp b/AI/VCAI/Goals/VisitTile.cpp index 70c05a6f8..eaec6efbb 100644 --- a/AI/VCAI/Goals/VisitTile.cpp +++ b/AI/VCAI/Goals/VisitTile.cpp @@ -15,7 +15,6 @@ #include "../FuzzyHelper.h" #include "../ResourceManager.h" #include "../BuildingManager.h" -#include "../../../lib/CPathfinder.h" #include "../../../lib/StringConstants.h" diff --git a/AI/VCAI/Goals/Win.cpp b/AI/VCAI/Goals/Win.cpp index 2cbe511ca..4b4958ed7 100644 --- a/AI/VCAI/Goals/Win.cpp +++ b/AI/VCAI/Goals/Win.cpp @@ -17,7 +17,6 @@ #include "../BuildingManager.h" #include "../../../lib/mapping/CMapHeader.h" //for victory conditions #include "../../../lib/mapObjects/CGTownInstance.h" -#include "../../../lib/CPathfinder.h" #include "../../../lib/StringConstants.h" diff --git a/AI/VCAI/Pathfinding/AINodeStorage.cpp b/AI/VCAI/Pathfinding/AINodeStorage.cpp index 99ba9dfd9..a9b32dbb9 100644 --- a/AI/VCAI/Pathfinding/AINodeStorage.cpp +++ b/AI/VCAI/Pathfinding/AINodeStorage.cpp @@ -14,7 +14,9 @@ #include "../../../CCallback.h" #include "../../../lib/mapping/CMap.h" #include "../../../lib/mapObjects/MapObjects.h" -#include "../../../lib/PathfinderUtil.h" +#include "../../../lib/pathfinder/CPathfinder.h" +#include "../../../lib/pathfinder/PathfinderOptions.h" +#include "../../../lib/pathfinder/PathfinderUtil.h" #include "../../../lib/CPlayerState.h" AINodeStorage::AINodeStorage(const int3 & Sizes) @@ -118,7 +120,7 @@ std::vector AINodeStorage::getInitialNodes() return {initialNode}; } -void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility) +void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, EPathAccessibility accessibility) { for(int i = 0; i < NUM_CHAINS; i++) { @@ -169,7 +171,7 @@ std::vector AINodeStorage::calculateNeighbours( { auto nextNode = getOrCreateNode(neighbour, i, srcNode->chainMask); - if(!nextNode || nextNode.value()->accessible == CGPathNode::NOT_SET) + if(!nextNode || nextNode.value()->accessible == EPathAccessibility::NOT_SET) continue; neighbours.push_back(nextNode.value()); @@ -292,7 +294,7 @@ bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNode for(const AIPathNode & node : chains) { auto sameNode = node.chainMask == destinationNode->chainMask; - if(sameNode || node.action == CGPathNode::ENodeAction::UNKNOWN) + if(sameNode || node.action == EPathNodeAction::UNKNOWN) { continue; } @@ -321,7 +323,7 @@ bool AINodeStorage::isTileAccessible(const int3 & pos, const EPathfindingLayer l { const AIPathNode & node = nodes[layer][pos.z][pos.x][pos.y][0]; - return node.action != CGPathNode::ENodeAction::UNKNOWN; + return node.action != EPathNodeAction::UNKNOWN; } std::vector AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand) const @@ -332,7 +334,7 @@ std::vector AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand) for(const AIPathNode & node : chains) { - if(node.action == CGPathNode::ENodeAction::UNKNOWN) + if(node.action == EPathNodeAction::UNKNOWN) { continue; } diff --git a/AI/VCAI/Pathfinding/AINodeStorage.h b/AI/VCAI/Pathfinding/AINodeStorage.h index 98486612f..51e32fdcc 100644 --- a/AI/VCAI/Pathfinding/AINodeStorage.h +++ b/AI/VCAI/Pathfinding/AINodeStorage.h @@ -10,8 +10,9 @@ #pragma once -#include "../../../lib/CPathfinder.h" #include "../../../lib/mapObjects/CGHeroInstance.h" +#include "../../../lib/pathfinder/CGPathNode.h" +#include "../../../lib/pathfinder/INodeStorage.h" #include "../AIUtility.h" #include "../FuzzyHelper.h" #include "../Goals/AbstractGoal.h" @@ -69,7 +70,7 @@ private: std::unique_ptr dangerEvaluator; STRONG_INLINE - void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility); + void resetTile(const int3 & tile, EPathfindingLayer layer, EPathAccessibility accessibility); public: /// more than 1 chain layer allows us to have more than 1 path to each tile so we can chose more optimal one. diff --git a/AI/VCAI/Pathfinding/AIPathfinderConfig.cpp b/AI/VCAI/Pathfinding/AIPathfinderConfig.cpp index 165f41e7a..cf80922ed 100644 --- a/AI/VCAI/Pathfinding/AIPathfinderConfig.cpp +++ b/AI/VCAI/Pathfinding/AIPathfinderConfig.cpp @@ -14,6 +14,8 @@ #include "Rules/AIMovementToDestinationRule.h" #include "Rules/AIPreviousNodeRule.h" +#include "../../../lib/pathfinder/CPathfinder.h" + namespace AIPathfinding { std::vector> makeRuleset( @@ -41,6 +43,8 @@ namespace AIPathfinding { } + AIPathfinderConfig::~AIPathfinderConfig() = default; + CPathfinderHelper * AIPathfinderConfig::getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) { if(!helper) diff --git a/AI/VCAI/Pathfinding/AIPathfinderConfig.h b/AI/VCAI/Pathfinding/AIPathfinderConfig.h index b7c0664ed..81ec9722c 100644 --- a/AI/VCAI/Pathfinding/AIPathfinderConfig.h +++ b/AI/VCAI/Pathfinding/AIPathfinderConfig.h @@ -12,6 +12,7 @@ #include "AINodeStorage.h" #include "../VCAI.h" +#include "../../../lib/pathfinder/PathfinderOptions.h" namespace AIPathfinding { @@ -27,6 +28,8 @@ namespace AIPathfinding VCAI * ai, std::shared_ptr nodeStorage); + ~AIPathfinderConfig(); + virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override; }; } diff --git a/AI/VCAI/Pathfinding/Actions/ISpecialAction.h b/AI/VCAI/Pathfinding/Actions/ISpecialAction.h index 6b7e77f8d..f223ac942 100644 --- a/AI/VCAI/Pathfinding/Actions/ISpecialAction.h +++ b/AI/VCAI/Pathfinding/Actions/ISpecialAction.h @@ -13,6 +13,11 @@ #include "../../AIUtility.h" #include "../../Goals/AbstractGoal.h" +VCMI_LIB_NAMESPACE_BEGIN +struct PathNodeInfo; +struct CDestinationNodeInfo; +VCMI_LIB_NAMESPACE_END + struct AIPathNode; class ISpecialAction @@ -29,4 +34,4 @@ public: const AIPathNode * srcNode) const { } -}; \ No newline at end of file +}; diff --git a/AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.cpp b/AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.cpp index 6689765e6..eaf4459a6 100644 --- a/AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.cpp +++ b/AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.cpp @@ -122,11 +122,11 @@ namespace AIPathfinding { AIPathNode * boatNode = boatNodeOptional.value(); - if(boatNode->action == CGPathNode::UNKNOWN) + if(boatNode->action == EPathNodeAction::UNKNOWN) { boatNode->specialAction = virtualBoat; destination.blocked = false; - destination.action = CGPathNode::ENodeAction::EMBARK; + destination.action = EPathNodeAction::EMBARK; destination.node = boatNode; result = true; } diff --git a/AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.h b/AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.h index 033e2e1a4..60297a944 100644 --- a/AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.h +++ b/AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.h @@ -15,6 +15,7 @@ #include "../Actions/BoatActions.h" #include "../../../../CCallback.h" #include "../../../../lib/mapObjects/MapObjects.h" +#include "../../../../lib/pathfinder/PathfindingRules.h" namespace AIPathfinding { diff --git a/AI/VCAI/Pathfinding/Rules/AIMovementAfterDestinationRule.h b/AI/VCAI/Pathfinding/Rules/AIMovementAfterDestinationRule.h index 639bfe55e..e5989b94a 100644 --- a/AI/VCAI/Pathfinding/Rules/AIMovementAfterDestinationRule.h +++ b/AI/VCAI/Pathfinding/Rules/AIMovementAfterDestinationRule.h @@ -14,6 +14,7 @@ #include "../../VCAI.h" #include "../../../../CCallback.h" #include "../../../../lib/mapObjects/MapObjects.h" +#include "../../../../lib/pathfinder/PathfindingRules.h" namespace AIPathfinding { diff --git a/AI/VCAI/Pathfinding/Rules/AIMovementToDestinationRule.cpp b/AI/VCAI/Pathfinding/Rules/AIMovementToDestinationRule.cpp index e34575157..01c57b46b 100644 --- a/AI/VCAI/Pathfinding/Rules/AIMovementToDestinationRule.cpp +++ b/AI/VCAI/Pathfinding/Rules/AIMovementToDestinationRule.cpp @@ -29,7 +29,7 @@ namespace AIPathfinding return; if(blocker == BlockingReason::DESTINATION_BLOCKED - && destination.action == CGPathNode::EMBARK + && destination.action == EPathNodeAction::EMBARK && nodeStorage->getAINode(destination.node)->specialAction) { return; diff --git a/AI/VCAI/Pathfinding/Rules/AIMovementToDestinationRule.h b/AI/VCAI/Pathfinding/Rules/AIMovementToDestinationRule.h index 58c535c60..0f81c6ece 100644 --- a/AI/VCAI/Pathfinding/Rules/AIMovementToDestinationRule.h +++ b/AI/VCAI/Pathfinding/Rules/AIMovementToDestinationRule.h @@ -14,6 +14,7 @@ #include "../../VCAI.h" #include "../../../../CCallback.h" #include "../../../../lib/mapObjects/MapObjects.h" +#include "../../../../lib/pathfinder/PathfindingRules.h" namespace AIPathfinding { diff --git a/AI/VCAI/Pathfinding/Rules/AIPreviousNodeRule.cpp b/AI/VCAI/Pathfinding/Rules/AIPreviousNodeRule.cpp index 3dbdcee6d..128a20fd4 100644 --- a/AI/VCAI/Pathfinding/Rules/AIPreviousNodeRule.cpp +++ b/AI/VCAI/Pathfinding/Rules/AIPreviousNodeRule.cpp @@ -23,7 +23,7 @@ namespace AIPathfinding const PathfinderConfig * pathfinderConfig, CPathfinderHelper * pathfinderHelper) const { - if(source.node->action == CGPathNode::ENodeAction::BLOCKING_VISIT || source.node->action == CGPathNode::ENodeAction::VISIT) + if(source.node->action == EPathNodeAction::BLOCKING_VISIT || source.node->action == EPathNodeAction::VISIT) { // we can not directly bypass objects, we need to interact with them first destination.node->theNodeBefore = source.node; diff --git a/AI/VCAI/Pathfinding/Rules/AIPreviousNodeRule.h b/AI/VCAI/Pathfinding/Rules/AIPreviousNodeRule.h index bb3945c8b..294c9517e 100644 --- a/AI/VCAI/Pathfinding/Rules/AIPreviousNodeRule.h +++ b/AI/VCAI/Pathfinding/Rules/AIPreviousNodeRule.h @@ -14,6 +14,7 @@ #include "../../VCAI.h" #include "../../../../CCallback.h" #include "../../../../lib/mapObjects/MapObjects.h" +#include "../../../../lib/pathfinder/PathfindingRules.h" namespace AIPathfinding { diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 1cbf02287..a5ee23f8f 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -1849,11 +1849,11 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) //return cb->getTile(coord,false)->topVisitableObj(ignoreHero); }; - auto isTeleportAction = [&](CGPathNode::ENodeAction action) -> bool + auto isTeleportAction = [&](EPathNodeAction action) -> bool { - if(action != CGPathNode::TELEPORT_NORMAL && action != CGPathNode::TELEPORT_BLOCKING_VISIT) + if(action != EPathNodeAction::TELEPORT_NORMAL && action != EPathNodeAction::TELEPORT_BLOCKING_VISIT) { - if(action != CGPathNode::TELEPORT_BATTLE) + if(action != EPathNodeAction::TELEPORT_BATTLE) { return false; } @@ -1964,7 +1964,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h) doChannelProbing(); } - if(path.nodes[0].action == CGPathNode::BLOCKING_VISIT) + if(path.nodes[0].action == EPathNodeAction::BLOCKING_VISIT) { ret = h && i == 0; // when we take resource we do not reach its position. We even might not move } diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index ddaec2fb4..5536ed170 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -55,6 +55,7 @@ #include "../lib/mapObjects/CGTownInstance.h" #include "../lib/mapObjects/MiscObjects.h" #include "../lib/mapObjects/ObjectTemplate.h" +#include "../lib/pathfinder/CGPathNode.h" #include "../lib/CStack.h" #include "../lib/JsonNode.h" #include "CMusicHandler.h" @@ -70,7 +71,6 @@ #include "gui/WindowHandler.h" #include "windows/InfoWindows.h" #include "../lib/UnlockGuard.h" -#include "../lib/CPathfinder.h" #include "../lib/RoadHandler.h" #include "../lib/TerrainHandler.h" #include "CServerHandler.h" @@ -1884,11 +1884,11 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) return cb->getTile(h->convertToVisitablePos(coord))->topVisitableObj(ignoreHero); }; - auto isTeleportAction = [&](CGPathNode::ENodeAction action) -> bool + auto isTeleportAction = [&](EPathNodeAction action) -> bool { - if (action != CGPathNode::TELEPORT_NORMAL && - action != CGPathNode::TELEPORT_BLOCKING_VISIT && - action != CGPathNode::TELEPORT_BATTLE) + if (action != EPathNodeAction::TELEPORT_NORMAL && + action != EPathNodeAction::TELEPORT_BLOCKING_VISIT && + action != EPathNodeAction::TELEPORT_BATTLE) { return false; } @@ -1933,7 +1933,7 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) if (node->layer == EPathfindingLayer::LAND || node->layer == EPathfindingLayer::SAIL) return true; - if (node->accessible == CGPathNode::ACCESSIBLE) + if (node->accessible == EPathAccessibility::ACCESSIBLE) return true; return false; @@ -1959,8 +1959,8 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path) destinationTeleport = destTeleportObj->id; destinationTeleportPos = nextCoord; doMovement(h->pos, false); - if (path.nodes[i-1].action == CGPathNode::TELEPORT_BLOCKING_VISIT - || path.nodes[i-1].action == CGPathNode::TELEPORT_BATTLE) + if (path.nodes[i-1].action == EPathNodeAction::TELEPORT_BLOCKING_VISIT + || path.nodes[i-1].action == EPathNodeAction::TELEPORT_BATTLE) { destinationTeleport = ObjectInstanceID(); destinationTeleportPos = int3(-1); diff --git a/client/Client.cpp b/client/Client.cpp index a6f359070..de12a497f 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -29,6 +29,7 @@ #include "../lib/battle/BattleInfo.h" #include "../lib/serializer/BinaryDeserializer.h" #include "../lib/mapping/CMapService.h" +#include "../lib/pathfinder/CGPathNode.h" #include "../lib/filesystem/Filesystem.h" #include "../lib/registerTypes/RegisterTypes.h" #include "../lib/serializer/Connection.h" diff --git a/client/Client.h b/client/Client.h index 55c8f0b28..1f731b7bc 100644 --- a/client/Client.h +++ b/client/Client.h @@ -18,7 +18,6 @@ #include "../lib/CStopWatch.h" #include "../lib/int3.h" #include "../lib/CondSh.h" -#include "../lib/CPathfinder.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/client/PlayerLocalState.cpp b/client/PlayerLocalState.cpp index 522da67dd..510dd0bb6 100644 --- a/client/PlayerLocalState.cpp +++ b/client/PlayerLocalState.cpp @@ -11,9 +11,9 @@ #include "PlayerLocalState.h" #include "../CCallback.h" -#include "../lib/CPathfinder.h" #include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/mapObjects/CGTownInstance.h" +#include "../lib/pathfinder/CGPathNode.h" #include "CPlayerInterface.h" #include "adventureMap/AdventureMapInterface.h" diff --git a/client/adventureMap/AdventureMapInterface.cpp b/client/adventureMap/AdventureMapInterface.cpp index 1fedf16ba..50ebb5931 100644 --- a/client/adventureMap/AdventureMapInterface.cpp +++ b/client/adventureMap/AdventureMapInterface.cpp @@ -39,8 +39,8 @@ #include "../../lib/spells/CSpellHandler.h" #include "../../lib/mapObjects/CGHeroInstance.h" #include "../../lib/mapObjects/CGTownInstance.h" -#include "../../lib/CPathfinder.h" #include "../../lib/mapping/CMapDefines.h" +#include "../../lib/pathfinder/CGPathNode.h" std::shared_ptr adventureInt; @@ -615,17 +615,17 @@ void AdventureMapInterface::onTileHovered(const int3 &mapPos) vstd::amin(turns, 3); switch(pathNode->action) { - case CGPathNode::NORMAL: - case CGPathNode::TELEPORT_NORMAL: + case EPathNodeAction::NORMAL: + case EPathNodeAction::TELEPORT_NORMAL: if(pathNode->layer == EPathfindingLayer::LAND) CCS->curh->set(cursorMove[turns]); else CCS->curh->set(cursorSailVisit[turns]); break; - case CGPathNode::VISIT: - case CGPathNode::BLOCKING_VISIT: - case CGPathNode::TELEPORT_BLOCKING_VISIT: + case EPathNodeAction::VISIT: + case EPathNodeAction::BLOCKING_VISIT: + case EPathNodeAction::TELEPORT_BLOCKING_VISIT: if(objAtTile && objAtTile->ID == Obj::HERO) { if(LOCPLINT->localState->getCurrentArmy() == objAtTile) @@ -639,16 +639,16 @@ void AdventureMapInterface::onTileHovered(const int3 &mapPos) CCS->curh->set(cursorSailVisit[turns]); break; - case CGPathNode::BATTLE: - case CGPathNode::TELEPORT_BATTLE: + case EPathNodeAction::BATTLE: + case EPathNodeAction::TELEPORT_BATTLE: CCS->curh->set(cursorAttack[turns]); break; - case CGPathNode::EMBARK: + case EPathNodeAction::EMBARK: CCS->curh->set(cursorSail[turns]); break; - case CGPathNode::DISEMBARK: + case EPathNodeAction::DISEMBARK: CCS->curh->set(cursorDisembark[turns]); break; diff --git a/client/adventureMap/AdventureMapShortcuts.cpp b/client/adventureMap/AdventureMapShortcuts.cpp index f0e639d1e..b617dc4d8 100644 --- a/client/adventureMap/AdventureMapShortcuts.cpp +++ b/client/adventureMap/AdventureMapShortcuts.cpp @@ -30,10 +30,10 @@ #include "../../CCallback.h" #include "../../lib/CConfigHandler.h" #include "../../lib/CGeneralTextHandler.h" -#include "../../lib/CPathfinder.h" #include "../../lib/mapObjects/CGHeroInstance.h" #include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/mapping/CMap.h" +#include "../../lib/pathfinder/CGPathNode.h" AdventureMapShortcuts::AdventureMapShortcuts(AdventureMapInterface & owner) : owner(owner) diff --git a/client/mapView/MapRenderer.cpp b/client/mapView/MapRenderer.cpp index 2a28cc8be..ef8d0d8e4 100644 --- a/client/mapView/MapRenderer.cpp +++ b/client/mapView/MapRenderer.cpp @@ -21,7 +21,6 @@ #include "../../CCallback.h" -#include "../../lib/CPathfinder.h" #include "../../lib/RiverHandler.h" #include "../../lib/RoadHandler.h" #include "../../lib/TerrainHandler.h" @@ -29,6 +28,7 @@ #include "../../lib/mapObjects/MiscObjects.h" #include "../../lib/mapObjects/ObjectTemplate.h" #include "../../lib/mapping/CMapDefines.h" +#include "../../lib/pathfinder/CGPathNode.h" struct NeighborTilesInfo { @@ -714,7 +714,7 @@ size_t MapRendererPath::selectImage(IMapRendererContext & context, const int3 & return std::numeric_limits::max(); bool pathContinuous = iter->coord.areNeighbours(next->coord) && iter->coord.areNeighbours(prev->coord); - bool embarking = iter->action == CGPathNode::EMBARK || iter->action == CGPathNode::DISEMBARK; + bool embarking = iter->action == EPathNodeAction::EMBARK || iter->action == EPathNodeAction::DISEMBARK; if(pathContinuous && !embarking) return selectImageArrow(reachableToday, iter->coord, prev->coord, next->coord); diff --git a/client/mapView/MapRendererContext.cpp b/client/mapView/MapRendererContext.cpp index 6b1ba5c0c..caa355673 100644 --- a/client/mapView/MapRendererContext.cpp +++ b/client/mapView/MapRendererContext.cpp @@ -19,11 +19,11 @@ #include "../CPlayerInterface.h" #include "../PlayerLocalState.h" -#include "../../lib/CPathfinder.h" #include "../../lib/Point.h" #include "../../lib/mapObjects/CGHeroInstance.h" #include "../../lib/spells/CSpellHandler.h" #include "../../lib/mapping/CMap.h" +#include "../../lib/pathfinder/CGPathNode.h" MapRendererBaseContext::MapRendererBaseContext(const MapRendererContextState & viewState) : viewState(viewState) diff --git a/client/mapView/MapViewController.cpp b/client/mapView/MapViewController.cpp index ae866f516..10b658fb4 100644 --- a/client/mapView/MapViewController.cpp +++ b/client/mapView/MapViewController.cpp @@ -22,9 +22,9 @@ #include "../gui/WindowHandler.h" #include "../../lib/CConfigHandler.h" -#include "../../lib/CPathfinder.h" #include "../../lib/mapObjects/CGHeroInstance.h" #include "../../lib/mapObjects/MiscObjects.h" +#include "../../lib/pathfinder/CGPathNode.h" #include "../../lib/spells/ViewSpellInt.h" void MapViewController::setViewCenter(const int3 & position) diff --git a/cmake_modules/VCMI_lib.cmake b/cmake_modules/VCMI_lib.cmake index 01ab3c668..b03ff2bf2 100644 --- a/cmake_modules/VCMI_lib.cmake +++ b/cmake_modules/VCMI_lib.cmake @@ -108,6 +108,13 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/mapping/MapFormatJson.cpp ${MAIN_LIB_DIR}/mapping/ObstacleProxy.cpp + ${MAIN_LIB_DIR}/pathfinder/CGPathNode.cpp + ${MAIN_LIB_DIR}/pathfinder/CPathfinder.cpp + ${MAIN_LIB_DIR}/pathfinder/NodeStorage.cpp + ${MAIN_LIB_DIR}/pathfinder/PathfinderOptions.cpp + ${MAIN_LIB_DIR}/pathfinder/PathfindingRules.cpp + ${MAIN_LIB_DIR}/pathfinder/TurnInfo.cpp + ${MAIN_LIB_DIR}/registerTypes/RegisterTypes.cpp ${MAIN_LIB_DIR}/registerTypes/TypesClientPacks1.cpp ${MAIN_LIB_DIR}/registerTypes/TypesClientPacks2.cpp @@ -219,7 +226,6 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/CHeroHandler.cpp ${MAIN_LIB_DIR}/CModHandler.cpp ${MAIN_LIB_DIR}/CModVersion.cpp - ${MAIN_LIB_DIR}/CPathfinder.cpp ${MAIN_LIB_DIR}/CPlayerState.cpp ${MAIN_LIB_DIR}/CRandomGenerator.cpp ${MAIN_LIB_DIR}/CScriptingModule.cpp @@ -423,6 +429,15 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/mapping/MapFormatJson.h ${MAIN_LIB_DIR}/mapping/ObstacleProxy.h + ${MAIN_LIB_DIR}/pathfinder/INodeStorage.h + ${MAIN_LIB_DIR}/pathfinder/CGPathNode.h + ${MAIN_LIB_DIR}/pathfinder/CPathfinder.h + ${MAIN_LIB_DIR}/pathfinder/NodeStorage.h + ${MAIN_LIB_DIR}/pathfinder/PathfinderOptions.h + ${MAIN_LIB_DIR}/pathfinder/PathfinderUtil.h + ${MAIN_LIB_DIR}/pathfinder/PathfindingRules.h + ${MAIN_LIB_DIR}/pathfinder/TurnInfo.h + ${MAIN_LIB_DIR}/registerTypes/RegisterTypes.h ${MAIN_LIB_DIR}/rewardable/Configuration.h @@ -533,7 +548,6 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/CondSh.h ${MAIN_LIB_DIR}/ConstTransitivePtr.h ${MAIN_LIB_DIR}/Color.h - ${MAIN_LIB_DIR}/CPathfinder.h ${MAIN_LIB_DIR}/CPlayerState.h ${MAIN_LIB_DIR}/CRandomGenerator.h ${MAIN_LIB_DIR}/CScriptingModule.h @@ -563,7 +577,6 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/NetPacksLobby.h ${MAIN_LIB_DIR}/NetPackVisitor.h ${MAIN_LIB_DIR}/ObstacleHandler.h - ${MAIN_LIB_DIR}/PathfinderUtil.h ${MAIN_LIB_DIR}/Point.h ${MAIN_LIB_DIR}/Rect.h ${MAIN_LIB_DIR}/Rect.cpp diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index f5fbb3f8f..ec4209d9b 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -27,6 +27,8 @@ #include "mapObjectConstructors/CObjectClassesHandler.h" #include "StartInfo.h" #include "NetPacks.h" +#include "pathfinder/CPathfinder.h" +#include "pathfinder/PathfinderOptions.h" #include "registerTypes/RegisterTypes.h" #include "battle/BattleInfo.h" #include "JsonNode.h" diff --git a/lib/CGameState.h b/lib/CGameState.h index 2e2adc317..bf0c61dba 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -20,7 +20,6 @@ #include "int3.h" #include "CRandomGenerator.h" #include "CGameStateFwd.h" -#include "CPathfinder.h" namespace boost { diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp deleted file mode 100644 index 93201426d..000000000 --- a/lib/CPathfinder.cpp +++ /dev/null @@ -1,1434 +0,0 @@ -/* - * CPathfinder.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" -#include "CPathfinder.h" - -#include "CHeroHandler.h" -#include "mapping/CMap.h" -#include "CGameState.h" -#include "mapObjects/CGHeroInstance.h" -#include "GameConstants.h" -#include "CStopWatch.h" -#include "CConfigHandler.h" -#include "CPlayerState.h" -#include "PathfinderUtil.h" - -VCMI_LIB_NAMESPACE_BEGIN - -bool canSeeObj(const CGObjectInstance * obj) -{ - /// Pathfinder should ignore placed events - return obj != nullptr && obj->ID != Obj::EVENT; -} - -void NodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs) -{ - //TODO: fix this code duplication with AINodeStorage::initialize, problem is to keep `resetTile` inline - - int3 pos; - const PlayerColor player = out.hero->tempOwner; - const int3 sizes = gs->getMapSize(); - const auto fow = static_cast(gs)->getPlayerTeam(player)->fogOfWarMap; - - //make 200% sure that these are loop invariants (also a bit shorter code), let compiler do the rest(loop unswitching) - const bool useFlying = options.useFlying; - const bool useWaterWalking = options.useWaterWalking; - - for(pos.z=0; pos.z < sizes.z; ++pos.z) - { - for(pos.x=0; pos.x < sizes.x; ++pos.x) - { - for(pos.y=0; pos.y < sizes.y; ++pos.y) - { - const TerrainTile tile = gs->map->getTile(pos); - if(tile.terType->isWater()) - { - resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); - if(useFlying) - resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); - if(useWaterWalking) - resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); - } - if(tile.terType->isLand()) - { - resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); - if(useFlying) - resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); - } - } - } - } -} - -std::vector NodeStorage::calculateNeighbours( - const PathNodeInfo & source, - const PathfinderConfig * pathfinderConfig, - const CPathfinderHelper * pathfinderHelper) -{ - std::vector neighbours; - neighbours.reserve(16); - auto accessibleNeighbourTiles = pathfinderHelper->getNeighbourTiles(source); - - for(auto & neighbour : accessibleNeighbourTiles) - { - for(EPathfindingLayer i = EPathfindingLayer::LAND; i < EPathfindingLayer::NUM_LAYERS; i.advance(1)) - { - auto * node = getNode(neighbour, i); - - if(node->accessible == CGPathNode::NOT_SET) - continue; - - neighbours.push_back(node); - } - } - - return neighbours; -} - -std::vector NodeStorage::calculateTeleportations( - const PathNodeInfo & source, - const PathfinderConfig * pathfinderConfig, - const CPathfinderHelper * pathfinderHelper) -{ - std::vector neighbours; - - if(!source.isNodeObjectVisitable()) - return neighbours; - - auto accessibleExits = pathfinderHelper->getTeleportExits(source); - - for(auto & neighbour : accessibleExits) - { - auto * node = getNode(neighbour, source.node->layer); - - neighbours.push_back(node); - } - - return neighbours; -} - -std::vector CPathfinderHelper::getNeighbourTiles(const PathNodeInfo & source) const -{ - std::vector neighbourTiles; - neighbourTiles.reserve(8); - - getNeighbours( - *source.tile, - source.node->coord, - neighbourTiles, - boost::logic::indeterminate, - source.node->layer == EPathfindingLayer::SAIL); - - if(source.isNodeObjectVisitable()) - { - vstd::erase_if(neighbourTiles, [&](const int3 & tile) -> bool - { - return !canMoveBetween(tile, source.nodeObject->visitablePos()); - }); - } - - return neighbourTiles; -} - -NodeStorage::NodeStorage(CPathsInfo & pathsInfo, const CGHeroInstance * hero) - :out(pathsInfo) -{ - out.hero = hero; - out.hpos = hero->visitablePos(); -} - -void NodeStorage::resetTile(const int3 & tile, const EPathfindingLayer & layer, CGPathNode::EAccessibility accessibility) -{ - getNode(tile, layer)->update(tile, layer, accessibility); -} - -std::vector NodeStorage::getInitialNodes() -{ - auto * initialNode = getNode(out.hpos, out.hero->boat ? out.hero->boat->layer : EPathfindingLayer::LAND); - - initialNode->turns = 0; - initialNode->moveRemains = out.hero->movement; - initialNode->setCost(0.0); - - if(!initialNode->coord.valid()) - { - initialNode->coord = out.hpos; - } - - return std::vector { initialNode }; -} - -void NodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) -{ - assert(destination.node != source.node->theNodeBefore); //two tiles can't point to each other - destination.node->setCost(destination.cost); - destination.node->moveRemains = destination.movementLeft; - destination.node->turns = destination.turn; - destination.node->theNodeBefore = source.node; - destination.node->action = destination.action; -} - -PathfinderOptions::PathfinderOptions() -{ - 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 = settings["pathfinder"]["teleports"]["castleGate"].Bool(); - - lightweightFlyingMode = settings["pathfinder"]["lightweightFlyingMode"].Bool(); - oneTurnSpecialLayersLimit = settings["pathfinder"]["oneTurnSpecialLayersLimit"].Bool(); - originalMovementRules = settings["pathfinder"]["originalMovementRules"].Bool(); -} - -void MovementCostRule::process( - const PathNodeInfo & source, - CDestinationNodeInfo & destination, - const PathfinderConfig * pathfinderConfig, - CPathfinderHelper * pathfinderHelper) const -{ - float costAtNextTile = destination.cost; - int turnAtNextTile = destination.turn; - int moveAtNextTile = destination.movementLeft; - int cost = pathfinderHelper->getMovementCost(source, destination, moveAtNextTile); - int remains = moveAtNextTile - cost; - int sourceLayerMaxMovePoints = pathfinderHelper->getMaxMovePoints(source.node->layer); - - if(remains < 0) - { - //occurs rarely, when hero with low movepoints tries to leave the road - costAtNextTile += static_cast(moveAtNextTile) / sourceLayerMaxMovePoints;//we spent all points of current turn - pathfinderHelper->updateTurnInfo(++turnAtNextTile); - - int destinationLayerMaxMovePoints = pathfinderHelper->getMaxMovePoints(destination.node->layer); - - moveAtNextTile = destinationLayerMaxMovePoints; - - cost = pathfinderHelper->getMovementCost(source, destination, moveAtNextTile); //cost must be updated, movement points changed :( - remains = moveAtNextTile - cost; - } - - 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 = pathfinderHelper->movementPointsAfterEmbark(moveAtNextTile, cost, (destination.action == CGPathNode::DISEMBARK)); - cost = moveAtNextTile - remains; - } - - costAtNextTile += static_cast(cost) / sourceLayerMaxMovePoints; - - destination.cost = costAtNextTile; - destination.turn = turnAtNextTile; - destination.movementLeft = remains; - - if(destination.isBetterWay() && - ((source.node->turns == turnAtNextTile && remains) || pathfinderHelper->passOneTurnLimitCheck(source))) - { - pathfinderConfig->nodeStorage->commit(destination, source); - - return; - } - - destination.blocked = true; -} - -PathfinderConfig::PathfinderConfig(std::shared_ptr nodeStorage, std::vector> rules): - nodeStorage(std::move(nodeStorage)), - rules(std::move(rules)) -{ -} - -std::vector> SingleHeroPathfinderConfig::buildRuleSet() -{ - return std::vector>{ - std::make_shared(), - std::make_shared(), - std::make_shared(), - std::make_shared(), - std::make_shared() - }; -} - -SingleHeroPathfinderConfig::SingleHeroPathfinderConfig(CPathsInfo & out, CGameState * gs, const CGHeroInstance * hero) - : PathfinderConfig(std::make_shared(out, hero), buildRuleSet()) -{ - pathfinderHelper = std::make_unique(gs, hero, options); -} - -CPathfinderHelper * SingleHeroPathfinderConfig::getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) -{ - return pathfinderHelper.get(); -} - -CPathfinder::CPathfinder(CGameState * _gs, std::shared_ptr config): - gamestate(_gs), - config(std::move(config)) -{ - initializeGraph(); -} - - -void CPathfinder::push(CGPathNode * node) -{ - if(node && !node->inPQ) - { - node->inPQ = true; - node->pq = &this->pq; - auto handle = pq.push(node); - node->pqHandle = handle; - } -} - -CGPathNode * CPathfinder::topAndPop() -{ - auto * node = pq.top(); - - pq.pop(); - node->inPQ = false; - node->pq = nullptr; - return node; -} - -void CPathfinder::calculatePaths() -{ - //logGlobal->info("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 - std::vector initialNodes = config->nodeStorage->getInitialNodes(); - int counter = 0; - - for(auto * initialNode : initialNodes) - { - if(!gamestate->isInTheMap(initialNode->coord)/* || !gs->map->isInTheMap(dest)*/) //check input - { - logGlobal->error("CGameState::calculatePaths: Hero outside the gs->map? How dare you..."); - throw std::runtime_error("Wrong checksum"); - } - - source.setNode(gamestate, initialNode); - auto * hlp = config->getOrCreatePathfinderHelper(source, gamestate); - - if(hlp->isHeroPatrolLocked()) - continue; - - pq.push(initialNode); - } - - while(!pq.empty()) - { - counter++; - auto * node = topAndPop(); - - source.setNode(gamestate, node); - source.node->locked = true; - - int movement = source.node->moveRemains; - uint8_t turn = source.node->turns; - float cost = source.node->getCost(); - - auto * hlp = config->getOrCreatePathfinderHelper(source, gamestate); - - hlp->updateTurnInfo(turn); - if(!movement) - { - hlp->updateTurnInfo(++turn); - movement = hlp->getMaxMovePoints(source.node->layer); - if(!hlp->passOneTurnLimitCheck(source)) - continue; - } - - source.isInitialPosition = source.nodeHero == hlp->hero; - source.updateInfo(hlp, gamestate); - - //add accessible neighbouring nodes to the queue - auto neighbourNodes = config->nodeStorage->calculateNeighbours(source, config.get(), hlp); - for(CGPathNode * neighbour : neighbourNodes) - { - if(neighbour->locked) - continue; - - if(!hlp->isLayerAvailable(neighbour->layer)) - continue; - - destination.setNode(gamestate, neighbour); - hlp = config->getOrCreatePathfinderHelper(destination, gamestate); - - if(!hlp->isPatrolMovementAllowed(neighbour->coord)) - continue; - - /// Check transition without tile accessability rules - if(source.node->layer != neighbour->layer && !isLayerTransitionPossible()) - continue; - - destination.turn = turn; - destination.movementLeft = movement; - destination.cost = cost; - destination.updateInfo(hlp, gamestate); - destination.isGuardianTile = destination.guarded && isDestinationGuardian(); - - for(const auto & rule : config->rules) - { - rule->process(source, destination, config.get(), hlp); - - if(destination.blocked) - break; - } - - if(!destination.blocked) - push(destination.node); - - } //neighbours loop - - //just add all passable teleport exits - hlp = config->getOrCreatePathfinderHelper(source, gamestate); - - /// For now we disable teleports usage for patrol movement - /// VCAI not aware about patrol and may stuck while attempt to use teleport - if(hlp->patrolState == CPathfinderHelper::PATROL_RADIUS) - continue; - - auto teleportationNodes = config->nodeStorage->calculateTeleportations(source, config.get(), hlp); - for(CGPathNode * teleportNode : teleportationNodes) - { - if(teleportNode->locked) - continue; - /// TODO: We may consider use invisible exits on FoW border in future - /// Useful for AI when at least one tile around exit is visible and passable - /// Objects are usually visible on FoW border anyway so it's not cheating. - /// - /// For now it's disabled as it's will cause crashes in movement code. - if(teleportNode->accessible == CGPathNode::BLOCKED) - continue; - - destination.setNode(gamestate, teleportNode); - destination.turn = turn; - destination.movementLeft = movement; - destination.cost = cost; - - if(destination.isBetterWay()) - { - destination.action = getTeleportDestAction(); - config->nodeStorage->commit(destination, source); - - if(destination.node->action == CGPathNode::TELEPORT_NORMAL) - push(destination.node); - } - } - } //queue loop - - logAi->trace("CPathfinder finished with %s iterations", std::to_string(counter)); -} - -std::vector CPathfinderHelper::getAllowedTeleportChannelExits(const TeleportChannelID & channelID) const -{ - std::vector allowedExits; - - for(const auto & objId : getTeleportChannelExits(channelID, hero->tempOwner)) - { - const auto * obj = getObj(objId); - if(dynamic_cast(obj)) - { - auto pos = obj->getBlockedPos(); - for(const auto & p : pos) - { - if(gs->map->getTile(p).topVisitableId() == obj->ID) - allowedExits.push_back(p); - } - } - else if(obj && CGTeleport::isExitPassable(gs, hero, obj)) - allowedExits.push_back(obj->visitablePos()); - } - - return allowedExits; -} - -std::vector CPathfinderHelper::getCastleGates(const PathNodeInfo & source) const -{ - std::vector allowedExits; - - auto towns = getPlayerState(hero->tempOwner)->towns; - for(const auto & town : towns) - { - if(town->id != source.nodeObject->id && town->visitingHero == nullptr - && town->hasBuilt(BuildingID::CASTLE_GATE, ETownType::INFERNO)) - { - allowedExits.push_back(town->visitablePos()); - } - } - - return allowedExits; -} - -std::vector CPathfinderHelper::getTeleportExits(const PathNodeInfo & source) const -{ - std::vector teleportationExits; - - const auto * objTeleport = dynamic_cast(source.nodeObject); - if(isAllowedTeleportEntrance(objTeleport)) - { - for(const auto & exit : getAllowedTeleportChannelExits(objTeleport->channel)) - { - teleportationExits.push_back(exit); - } - } - else if(options.useCastleGate - && (source.nodeObject->ID == Obj::TOWN && source.nodeObject->subID == ETownType::INFERNO - && source.objectRelations != PlayerRelations::ENEMIES)) - { - /// TODO: Find way to reuse CPlayerSpecificInfoCallback::getTownsInfo - /// This may be handy if we allow to use teleportation to friendly towns - for(const auto & exit : getCastleGates(source)) - { - teleportationExits.push_back(exit); - } - } - - return teleportationExits; -} - -bool CPathfinderHelper::isHeroPatrolLocked() const -{ - return patrolState == PATROL_LOCKED; -} - -bool CPathfinderHelper::isPatrolMovementAllowed(const int3 & dst) const -{ - if(patrolState == PATROL_RADIUS) - { - if(!vstd::contains(patrolTiles, dst)) - return false; - } - - return true; -} - -bool CPathfinder::isLayerTransitionPossible() const -{ - ELayer destLayer = destination.node->layer; - - /// No layer transition allowed when previous node action is BATTLE - if(source.node->action == CGPathNode::BATTLE) - return false; - - switch(source.node->layer) - { - case ELayer::LAND: - if(destLayer == ELayer::AIR) - { - if(!config->options.lightweightFlyingMode || source.isInitialPosition) - return true; - } - else if(destLayer == ELayer::SAIL) - { - if(destination.tile->isWater()) - return true; - } - else - return true; - - break; - - case ELayer::SAIL: - if(destLayer == ELayer::LAND && !destination.tile->isWater()) - 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; -} - -void LayerTransitionRule::process( - const PathNodeInfo & source, - CDestinationNodeInfo & destination, - const PathfinderConfig * pathfinderConfig, - CPathfinderHelper * pathfinderHelper) const -{ - if(source.node->layer == destination.node->layer) - return; - - switch(source.node->layer) - { - case EPathfindingLayer::LAND: - if(destination.node->layer == EPathfindingLayer::SAIL) - { - /// Cannot enter empty water tile from land -> it has to be visitable - if(destination.node->accessible == CGPathNode::ACCESSIBLE) - destination.blocked = true; - } - - break; - - case EPathfindingLayer::SAIL: - //tile must be accessible -> exception: unblocked blockvis tiles -> clear but guarded by nearby monster coast - if((destination.node->accessible != CGPathNode::ACCESSIBLE && (destination.node->accessible != CGPathNode::BLOCKVIS || destination.tile->blocked)) - || destination.tile->visitable) //TODO: passableness problem -> town says it's passable (thus accessible) but we obviously can't disembark onto town gate - { - destination.blocked = true; - } - - break; - - case EPathfindingLayer::AIR: - if(pathfinderConfig->options.originalMovementRules) - { - if((source.node->accessible != CGPathNode::ACCESSIBLE && - source.node->accessible != CGPathNode::VISITABLE) && - (destination.node->accessible != CGPathNode::VISITABLE && - destination.node->accessible != CGPathNode::ACCESSIBLE)) - { - destination.blocked = true; - } - } - else if(destination.node->accessible != CGPathNode::ACCESSIBLE) - { - /// Hero that fly can only land on accessible tiles - if(destination.nodeObject) - destination.blocked = true; - } - - break; - - case EPathfindingLayer::WATER: - if(destination.node->accessible != CGPathNode::ACCESSIBLE && destination.node->accessible != CGPathNode::VISITABLE) - { - /// Hero that walking on water can transit to accessible and visitable tiles - /// Though hero can't interact with blocking visit objects while standing on water - destination.blocked = true; - } - - break; - } -} - -PathfinderBlockingRule::BlockingReason MovementToDestinationRule::getBlockingReason( - const PathNodeInfo & source, - const CDestinationNodeInfo & destination, - const PathfinderConfig * pathfinderConfig, - const CPathfinderHelper * pathfinderHelper) const -{ - - if(destination.node->accessible == CGPathNode::BLOCKED) - return BlockingReason::DESTINATION_BLOCKED; - - switch(destination.node->layer) - { - case EPathfindingLayer::LAND: - if(!pathfinderHelper->canMoveBetween(source.coord, destination.coord)) - return BlockingReason::DESTINATION_BLOCKED; - - if(source.guarded) - { - if(!(pathfinderConfig->options.originalMovementRules && source.node->layer == EPathfindingLayer::AIR) && - !destination.isGuardianTile) // Can step into tile of guard - { - return BlockingReason::SOURCE_GUARDED; - } - } - - break; - - case EPathfindingLayer::SAIL: - if(!pathfinderHelper->canMoveBetween(source.coord, destination.coord)) - return BlockingReason::DESTINATION_BLOCKED; - - if(source.guarded) - { - // Hero embarked a boat standing on a guarded tile -> we must allow to move away from that tile - if(source.node->action != CGPathNode::EMBARK && !destination.isGuardianTile) - return BlockingReason::SOURCE_GUARDED; - } - - if(source.node->layer == EPathfindingLayer::LAND) - { - if(!destination.isNodeObjectVisitable()) - return BlockingReason::DESTINATION_BLOCKED; - - if(destination.nodeObject->ID != Obj::BOAT && !destination.nodeHero) - return BlockingReason::DESTINATION_BLOCKED; - } - else if(destination.isNodeObjectVisitable() && destination.nodeObject->ID == Obj::BOAT) - { - /// Hero in boat can't visit empty boats - return BlockingReason::DESTINATION_BLOCKED; - } - - break; - - case EPathfindingLayer::WATER: - if(!pathfinderHelper->canMoveBetween(source.coord, destination.coord) - || destination.node->accessible != CGPathNode::ACCESSIBLE) - { - return BlockingReason::DESTINATION_BLOCKED; - } - - if(destination.guarded) - return BlockingReason::DESTINATION_BLOCKED; - - break; - } - - return BlockingReason::NONE; -} - - -void MovementAfterDestinationRule::process( - const PathNodeInfo & source, - CDestinationNodeInfo & destination, - const PathfinderConfig * config, - CPathfinderHelper * pathfinderHelper) const -{ - auto blocker = getBlockingReason(source, destination, config, pathfinderHelper); - - if(blocker == BlockingReason::DESTINATION_GUARDED && destination.action == CGPathNode::ENodeAction::BATTLE) - { - return; // allow bypass guarded tile but only in direction of guard, a bit UI related thing - } - - destination.blocked = blocker != BlockingReason::NONE; -} - -PathfinderBlockingRule::BlockingReason MovementAfterDestinationRule::getBlockingReason( - const PathNodeInfo & source, - const CDestinationNodeInfo & destination, - const PathfinderConfig * config, - const CPathfinderHelper * pathfinderHelper) const -{ - 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 doesn'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 - const auto * objTeleport = dynamic_cast(destination.nodeObject); - if(pathfinderHelper->isAllowedTeleportEntrance(objTeleport)) - { - /// For now we'll always allow transit over teleporters - /// Transit over whirlpools only allowed when hero is protected - return BlockingReason::NONE; - } - 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 - return BlockingReason::NONE; - } - - return BlockingReason::DESTINATION_VISIT; - } - - case CGPathNode::BLOCKING_VISIT: - return destination.guarded - ? BlockingReason::DESTINATION_GUARDED - : BlockingReason::DESTINATION_BLOCKVIS; - - case CGPathNode::NORMAL: - return BlockingReason::NONE; - - case CGPathNode::EMBARK: - if(pathfinderHelper->options.useEmbarkAndDisembark) - return BlockingReason::NONE; - - return BlockingReason::DESTINATION_BLOCKED; - - case CGPathNode::DISEMBARK: - if(pathfinderHelper->options.useEmbarkAndDisembark) - return destination.guarded ? BlockingReason::DESTINATION_GUARDED : BlockingReason::NONE; - - return BlockingReason::DESTINATION_BLOCKED; - - case CGPathNode::BATTLE: - /// Movement after BATTLE action only possible from guarded tile to guardian tile - if(destination.guarded) - return BlockingReason::DESTINATION_GUARDED; - - break; - } - - return BlockingReason::DESTINATION_BLOCKED; -} - -void DestinationActionRule::process( - const PathNodeInfo & source, - CDestinationNodeInfo & destination, - const PathfinderConfig * pathfinderConfig, - CPathfinderHelper * pathfinderHelper) const -{ - if(destination.action != CGPathNode::ENodeAction::UNKNOWN) - { -#ifdef VCMI_TRACE_PATHFINDER - logAi->trace("Accepted precalculated action at %s", destination.coord.toString()); -#endif - return; - } - - CGPathNode::ENodeAction action = CGPathNode::NORMAL; - const auto * hero = pathfinderHelper->hero; - - switch(destination.node->layer) - { - case EPathfindingLayer::LAND: - if(source.node->layer == EPathfindingLayer::SAIL) - { - // TODO: Handle dismebark into guarded areaa - action = CGPathNode::DISEMBARK; - break; - } - - /// don't break - next case shared for both land and sail layers - [[fallthrough]]; - - case EPathfindingLayer::SAIL: - if(destination.isNodeObjectVisitable()) - { - auto objRel = destination.objectRelations; - - if(destination.nodeObject->ID == Obj::BOAT) - action = CGPathNode::EMBARK; - else if(destination.nodeHero) - { - if(destination.heroRelations == PlayerRelations::ENEMIES) - action = CGPathNode::BATTLE; - else - action = CGPathNode::BLOCKING_VISIT; - } - else if(destination.nodeObject->ID == Obj::TOWN) - { - if(destination.nodeObject->passableFor(hero->tempOwner)) - action = CGPathNode::VISIT; - else if(objRel == PlayerRelations::ENEMIES) - action = CGPathNode::BATTLE; - } - else if(destination.nodeObject->ID == Obj::GARRISON || destination.nodeObject->ID == Obj::GARRISON2) - { - if(destination.nodeObject->passableFor(hero->tempOwner)) - { - if(destination.guarded) - action = CGPathNode::BATTLE; - } - else if(objRel == PlayerRelations::ENEMIES) - action = CGPathNode::BATTLE; - } - else if(destination.nodeObject->ID == Obj::BORDER_GATE) - { - if(destination.nodeObject->passableFor(hero->tempOwner)) - { - if(destination.guarded) - action = CGPathNode::BATTLE; - } - else - action = CGPathNode::BLOCKING_VISIT; - } - else if(destination.isGuardianTile) - action = CGPathNode::BATTLE; - else if(destination.nodeObject->blockVisit && !(pathfinderConfig->options.useCastleGate && destination.nodeObject->ID == Obj::TOWN)) - action = CGPathNode::BLOCKING_VISIT; - - if(action == CGPathNode::NORMAL) - { - if(destination.guarded) - action = CGPathNode::BATTLE; - else - action = CGPathNode::VISIT; - } - } - else if(destination.guarded) - action = CGPathNode::BATTLE; - - break; - } - - destination.action = action; -} - -CGPathNode::ENodeAction CPathfinder::getTeleportDestAction() const -{ - CGPathNode::ENodeAction action = CGPathNode::TELEPORT_NORMAL; - - if(destination.isNodeObjectVisitable() && destination.nodeHero) - { - if(destination.heroRelations == PlayerRelations::ENEMIES) - action = CGPathNode::TELEPORT_BATTLE; - else - action = CGPathNode::TELEPORT_BLOCKING_VISIT; - } - - return action; -} - -bool CPathfinder::isDestinationGuardian() const -{ - return gamestate->guardingCreaturePosition(destination.node->coord) == destination.node->coord; -} - -void CPathfinderHelper::initializePatrol() -{ - auto state = PATROL_NONE; - - if(hero->patrol.patrolling && !getPlayerState(hero->tempOwner)->human) - { - if(hero->patrol.patrolRadius) - { - state = PATROL_RADIUS; - gs->getTilesInRange(patrolTiles, hero->patrol.initialPos, hero->patrol.patrolRadius, std::optional(), 0, int3::DIST_MANHATTAN); - } - else - state = PATROL_LOCKED; - } - - patrolState = state; -} - -void CPathfinder::initializeGraph() -{ - INodeStorage * nodeStorage = config->nodeStorage.get(); - nodeStorage->initialize(config->options, gamestate); -} - -bool CPathfinderHelper::canMoveBetween(const int3 & a, const int3 & b) const -{ - return gs->checkForVisitableDir(a, b); -} - -bool CPathfinderHelper::isAllowedTeleportEntrance(const CGTeleport * obj) const -{ - if(!obj || !isTeleportEntrancePassable(obj, hero->tempOwner)) - return false; - - const 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 CPathfinderHelper::addTeleportTwoWay(const CGTeleport * obj) const -{ - return options.useTeleportTwoWay && isTeleportChannelBidirectional(obj->channel, hero->tempOwner); -} - -bool CPathfinderHelper::addTeleportOneWay(const CGTeleport * obj) const -{ - if(options.useTeleportOneWay && isTeleportChannelUnidirectional(obj->channel, hero->tempOwner)) - { - auto passableExits = CGTeleport::getPassableExits(gs, hero, getTeleportChannelExits(obj->channel, hero->tempOwner)); - if(passableExits.size() == 1) - return true; - } - return false; -} - -bool CPathfinderHelper::addTeleportOneWayRandom(const CGTeleport * obj) const -{ - if(options.useTeleportOneWayRandom && isTeleportChannelUnidirectional(obj->channel, hero->tempOwner)) - { - auto passableExits = CGTeleport::getPassableExits(gs, hero, getTeleportChannelExits(obj->channel, hero->tempOwner)); - if(passableExits.size() > 1) - return true; - } - return false; -} - -bool CPathfinderHelper::addTeleportWhirlpool(const CGWhirlpool * obj) const -{ - return options.useTeleportWhirlpool && hasBonusOfType(BonusType::WHIRLPOOL_PROTECTION) && obj; -} - -int CPathfinderHelper::movementPointsAfterEmbark(int movement, int basicCost, bool disembark) const -{ - return hero->movementPointsAfterEmbark(movement, basicCost, disembark, getTurnInfo()); -} - -bool CPathfinderHelper::passOneTurnLimitCheck(const PathNodeInfo & source) const -{ - - if(!options.oneTurnSpecialLayersLimit) - return true; - - if(source.node->layer == EPathfindingLayer::WATER) - return false; - if(source.node->layer == EPathfindingLayer::AIR) - { - return options.originalMovementRules && source.node->accessible == CGPathNode::ACCESSIBLE; - } - - return true; -} - -TurnInfo::BonusCache::BonusCache(const TConstBonusListPtr & bl) -{ - for(const auto & terrain : VLC->terrainTypeHandler->objects) - { - noTerrainPenalty.push_back(static_cast( - bl->getFirst(Selector::type()(BonusType::NO_TERRAIN_PENALTY).And(Selector::subtype()(terrain->getIndex()))))); - } - - freeShipBoarding = static_cast(bl->getFirst(Selector::type()(BonusType::FREE_SHIP_BOARDING))); - flyingMovement = static_cast(bl->getFirst(Selector::type()(BonusType::FLYING_MOVEMENT))); - flyingMovementVal = bl->valOfBonuses(Selector::type()(BonusType::FLYING_MOVEMENT)); - waterWalking = static_cast(bl->getFirst(Selector::type()(BonusType::WATER_WALKING))); - waterWalkingVal = bl->valOfBonuses(Selector::type()(BonusType::WATER_WALKING)); - pathfindingVal = bl->valOfBonuses(Selector::type()(BonusType::ROUGH_TERRAIN_DISCOUNT)); -} - -TurnInfo::TurnInfo(const CGHeroInstance * Hero, const int turn): - hero(Hero), - maxMovePointsLand(-1), - maxMovePointsWater(-1), - turn(turn) -{ - bonuses = hero->getAllBonuses(Selector::days(turn), Selector::all, nullptr, ""); - bonusCache = std::make_unique(bonuses); - nativeTerrain = hero->getNativeTerrain(); -} - -bool TurnInfo::isLayerAvailable(const EPathfindingLayer & layer) const -{ - switch(layer) - { - case EPathfindingLayer::AIR: - if(hero && hero->boat && hero->boat->layer == EPathfindingLayer::AIR) - break; - - if(!hasBonusOfType(BonusType::FLYING_MOVEMENT)) - return false; - - break; - - case EPathfindingLayer::WATER: - if(hero && hero->boat && hero->boat->layer == EPathfindingLayer::WATER) - break; - - if(!hasBonusOfType(BonusType::WATER_WALKING)) - return false; - - break; - } - - return true; -} - -bool TurnInfo::hasBonusOfType(BonusType type, int subtype) const -{ - switch(type) - { - case BonusType::FREE_SHIP_BOARDING: - return bonusCache->freeShipBoarding; - case BonusType::FLYING_MOVEMENT: - return bonusCache->flyingMovement; - case BonusType::WATER_WALKING: - return bonusCache->waterWalking; - case BonusType::NO_TERRAIN_PENALTY: - return bonusCache->noTerrainPenalty[subtype]; - } - - return static_cast( - bonuses->getFirst(Selector::type()(type).And(Selector::subtype()(subtype)))); -} - -int TurnInfo::valOfBonuses(BonusType type, int subtype) const -{ - switch(type) - { - case BonusType::FLYING_MOVEMENT: - return bonusCache->flyingMovementVal; - case BonusType::WATER_WALKING: - return bonusCache->waterWalkingVal; - case BonusType::ROUGH_TERRAIN_DISCOUNT: - return bonusCache->pathfindingVal; - } - - return bonuses->valOfBonuses(Selector::type()(type).And(Selector::subtype()(subtype))); -} - -int TurnInfo::getMaxMovePoints(const EPathfindingLayer & layer) const -{ - if(maxMovePointsLand == -1) - maxMovePointsLand = hero->maxMovePointsCached(true, this); - if(maxMovePointsWater == -1) - maxMovePointsWater = hero->maxMovePointsCached(false, this); - - return layer == EPathfindingLayer::SAIL ? maxMovePointsWater : maxMovePointsLand; -} - -void TurnInfo::updateHeroBonuses(BonusType type, const CSelector& sel) const -{ - switch(type) - { - case BonusType::FREE_SHIP_BOARDING: - bonusCache->freeShipBoarding = static_cast(bonuses->getFirst(Selector::type()(BonusType::FREE_SHIP_BOARDING))); - break; - case BonusType::FLYING_MOVEMENT: - bonusCache->flyingMovement = static_cast(bonuses->getFirst(Selector::type()(BonusType::FLYING_MOVEMENT))); - bonusCache->flyingMovementVal = bonuses->valOfBonuses(Selector::type()(BonusType::FLYING_MOVEMENT)); - break; - case BonusType::WATER_WALKING: - bonusCache->waterWalking = static_cast(bonuses->getFirst(Selector::type()(BonusType::WATER_WALKING))); - bonusCache->waterWalkingVal = bonuses->valOfBonuses(Selector::type()(BonusType::WATER_WALKING)); - break; - case BonusType::ROUGH_TERRAIN_DISCOUNT: - bonusCache->pathfindingVal = bonuses->valOfBonuses(Selector::type()(BonusType::ROUGH_TERRAIN_DISCOUNT)); - break; - default: - bonuses = hero->getAllBonuses(Selector::days(turn), Selector::all, nullptr, ""); - } -} - -CPathfinderHelper::CPathfinderHelper(CGameState * gs, const CGHeroInstance * Hero, const PathfinderOptions & Options): - CGameInfoCallback(gs, std::optional()), - turn(-1), - hero(Hero), - options(Options), - owner(Hero->tempOwner) -{ - turnsInfo.reserve(16); - updateTurnInfo(); - initializePatrol(); -} - -CPathfinderHelper::~CPathfinderHelper() -{ - for(auto * ti : turnsInfo) - delete ti; -} - -void CPathfinderHelper::updateTurnInfo(const int Turn) -{ - if(turn != Turn) - { - turn = Turn; - if(turn >= turnsInfo.size()) - { - auto * ti = new TurnInfo(hero, turn); - turnsInfo.push_back(ti); - } - } -} - -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); -} - -const TurnInfo * CPathfinderHelper::getTurnInfo() const -{ - return turnsInfo[turn]; -} - -bool CPathfinderHelper::hasBonusOfType(const BonusType type, const int subtype) const -{ - return turnsInfo[turn]->hasBonusOfType(type, subtype); -} - -int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer & layer) const -{ - return turnsInfo[turn]->getMaxMovePoints(layer); -} - -void CPathfinderHelper::getNeighbours( - const TerrainTile & srcTile, - const int3 & srcCoord, - std::vector & vec, - const boost::logic::tribool & onLand, - const bool limitCoastSailing) const -{ - 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), - int3(-1, -1, +0), int3(0, -1, +0), int3(+1, -1, +0) - }; - - for(const auto & dir : dirs) - { - const int3 destCoord = srcCoord + dir; - if(!map->isInTheMap(destCoord)) - continue; - - const TerrainTile & destTile = map->getTile(destCoord); - if(!destTile.terType->isPassable()) - continue; - -// //we cannot visit things from blocked tiles -// if(srcTile.blocked && !srcTile.visitable && destTile.visitable && srcTile.blockingObjects.front()->ID != HEROI_TYPE) -// { -// continue; -// } - - /// Following condition let us avoid diagonal movement over coast when sailing - if(srcTile.terType->isWater() && limitCoastSailing && destTile.terType->isWater() && dir.x && dir.y) //diagonal move through water - { - const int3 horizontalNeighbour = srcCoord + int3{dir.x, 0, 0}; - const int3 verticalNeighbour = srcCoord + int3{0, dir.y, 0}; - if(map->getTile(horizontalNeighbour).terType->isLand() || map->getTile(verticalNeighbour).terType->isLand()) - continue; - } - - if(indeterminate(onLand) || onLand == destTile.terType->isLand()) - { - vec.push_back(destCoord); - } - } -} - -int CPathfinderHelper::getMovementCost( - const int3 & src, - const int3 & dst, - const TerrainTile * ct, - const TerrainTile * dt, - const int remainingMovePoints, - const bool checkLast, - boost::logic::tribool isDstSailLayer, - boost::logic::tribool isDstWaterLayer) const -{ - if(src == dst) //same tile - return 0; - - const auto * ti = getTurnInfo(); - - if(ct == nullptr || dt == nullptr) - { - ct = hero->cb->getTile(src); - dt = hero->cb->getTile(dst); - } - - bool isSailLayer; - if(indeterminate(isDstSailLayer)) - isSailLayer = hero->boat && hero->boat->layer == EPathfindingLayer::SAIL && dt->terType->isWater(); - else - isSailLayer = static_cast(isDstSailLayer); - - bool isWaterLayer; - if(indeterminate(isDstWaterLayer)) - isWaterLayer = ((hero->boat && hero->boat->layer == EPathfindingLayer::WATER) || ti->hasBonusOfType(BonusType::WATER_WALKING)) && dt->terType->isWater(); - else - isWaterLayer = static_cast(isDstWaterLayer); - - bool isAirLayer = (hero->boat && hero->boat->layer == EPathfindingLayer::AIR) || ti->hasBonusOfType(BonusType::FLYING_MOVEMENT); - - int ret = hero->getTileCost(*dt, *ct, ti); - if(isSailLayer) - { - if(ct->hasFavorableWinds()) - ret = static_cast(ret * 2.0 / 3); - } - else if(isAirLayer) - vstd::amin(ret, GameConstants::BASE_MOVEMENT_COST + ti->valOfBonuses(BonusType::FLYING_MOVEMENT)); - else if(isWaterLayer && ti->hasBonusOfType(BonusType::WATER_WALKING)) - ret = static_cast(ret * (100.0 + ti->valOfBonuses(BonusType::WATER_WALKING)) / 100.0); - - if(src.x != dst.x && src.y != dst.y) //it's diagonal move - { - int old = ret; - ret = static_cast(ret * M_SQRT2); - //diagonal move costs too much but normal move is possible - allow diagonal move for remaining move points - // https://heroes.thelazy.net/index.php/Movement#Diagonal_move_exception - if(ret > remainingMovePoints && remainingMovePoints >= old) - { - return remainingMovePoints; - } - } - - const int left = remainingMovePoints - ret; - constexpr auto maxCostOfOneStep = static_cast(175 * M_SQRT2); // diagonal move on Swamp - 247 MP - if(checkLast && left > 0 && left <= maxCostOfOneStep) //it might be the last tile - if no further move possible we take all move points - { - std::vector vec; - vec.reserve(8); //optimization - getNeighbours(*dt, dst, vec, ct->terType->isLand(), true); - for(const auto & elem : vec) - { - int fcost = getMovementCost(dst, elem, nullptr, nullptr, left, false); - if(fcost <= left) - { - return ret; - } - } - ret = remainingMovePoints; - } - - return ret; -} - -int3 CGPath::startPos() const -{ - return nodes[nodes.size()-1].coord; -} - -int3 CGPath::endPos() const -{ - return nodes[0].coord; -} - -CPathsInfo::CPathsInfo(const int3 & Sizes, const CGHeroInstance * hero_) - : sizes(Sizes), hero(hero_) -{ - nodes.resize(boost::extents[ELayer::NUM_LAYERS][sizes.z][sizes.x][sizes.y]); -} - -CPathsInfo::~CPathsInfo() = default; - -const CGPathNode * CPathsInfo::getPathInfo(const int3 & tile) const -{ - assert(vstd::iswithin(tile.x, 0, sizes.x)); - assert(vstd::iswithin(tile.y, 0, sizes.y)); - assert(vstd::iswithin(tile.z, 0, sizes.z)); - - return getNode(tile); -} - -bool CPathsInfo::getPath(CGPath & out, const int3 & dst) const -{ - out.nodes.clear(); - const CGPathNode * curnode = getNode(dst); - if(!curnode->theNodeBefore) - return false; - - while(curnode) - { - const CGPathNode cpn = * curnode; - curnode = curnode->theNodeBefore; - out.nodes.push_back(cpn); - } - return true; -} - -const CGPathNode * CPathsInfo::getNode(const int3 & coord) const -{ - const auto * landNode = &nodes[ELayer::LAND][coord.z][coord.x][coord.y]; - if(landNode->reachable()) - return landNode; - else - return &nodes[ELayer::SAIL][coord.z][coord.x][coord.y]; -} - -PathNodeInfo::PathNodeInfo() - : node(nullptr), nodeObject(nullptr), tile(nullptr), coord(-1, -1, -1), guarded(false), isInitialPosition(false) -{ -} - -void PathNodeInfo::setNode(CGameState * gs, CGPathNode * n) -{ - node = n; - - if(coord != node->coord) - { - assert(node->coord.valid()); - - coord = node->coord; - tile = gs->getTile(coord); - nodeObject = tile->topVisitableObj(); - - if(nodeObject && nodeObject->ID == Obj::HERO) - { - nodeHero = dynamic_cast(nodeObject); - nodeObject = tile->topVisitableObj(true); - - if(!nodeObject) - nodeObject = nodeHero; - } - else - { - nodeHero = nullptr; - } - } - - guarded = false; -} - -void PathNodeInfo::updateInfo(CPathfinderHelper * hlp, CGameState * gs) -{ - if(gs->guardingCreaturePosition(node->coord).valid() && !isInitialPosition) - { - guarded = true; - } - - if(nodeObject) - { - objectRelations = gs->getPlayerRelations(hlp->owner, nodeObject->tempOwner); - } - - if(nodeHero) - { - heroRelations = gs->getPlayerRelations(hlp->owner, nodeHero->tempOwner); - } -} - -CDestinationNodeInfo::CDestinationNodeInfo(): - blocked(false), - action(CGPathNode::ENodeAction::UNKNOWN) -{ -} - -void CDestinationNodeInfo::setNode(CGameState * gs, CGPathNode * n) -{ - PathNodeInfo::setNode(gs, n); - - blocked = false; - action = CGPathNode::ENodeAction::UNKNOWN; -} - -bool CDestinationNodeInfo::isBetterWay() const -{ - if(node->turns == 0xff) //we haven't been here before - return true; - else - return cost < node->getCost(); //this route is faster -} - -bool PathNodeInfo::isNodeObjectVisitable() const -{ - /// Hero can't visit objects while walking on water or flying - return (node->layer == EPathfindingLayer::LAND || node->layer == EPathfindingLayer::SAIL) - && (canSeeObj(nodeObject) || canSeeObj(nodeHero)); -} - -VCMI_LIB_NAMESPACE_END diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h deleted file mode 100644 index 19d6a6b1e..000000000 --- a/lib/CPathfinder.h +++ /dev/null @@ -1,618 +0,0 @@ -/* - * CPathfinder.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "VCMI_Lib.h" -#include "IGameCallback.h" -#include "bonuses/Bonus.h" -#include "int3.h" - -#include - -VCMI_LIB_NAMESPACE_BEGIN - - -class CGHeroInstance; -class CGObjectInstance; -struct TerrainTile; -class CPathfinderHelper; -class CMap; -class CGWhirlpool; -class CPathfinderHelper; -class CPathfinder; -class PathfinderConfig; - - -template -struct DLL_LINKAGE NodeComparer -{ - STRONG_INLINE - bool operator()(const N * lhs, const N * rhs) const - { - return lhs->getCost() > rhs->getCost(); - } -}; - -struct DLL_LINKAGE CGPathNode -{ - using ELayer = EPathfindingLayer; - - enum ENodeAction : ui8 - { - UNKNOWN = 0, - EMBARK = 1, - DISEMBARK, - NORMAL, - BATTLE, - VISIT, - BLOCKING_VISIT, - TELEPORT_NORMAL, - TELEPORT_BLOCKING_VISIT, - TELEPORT_BATTLE - }; - - enum EAccessibility : ui8 - { - NOT_SET = 0, - ACCESSIBLE = 1, //tile can be entered and passed - VISITABLE, //tile can be entered as the last tile in path - BLOCKVIS, //visitable from neighboring tile but not passable - FLYABLE, //can only be accessed in air layer - BLOCKED //tile can be neither entered nor visited - }; - - CGPathNode * theNodeBefore; - int3 coord; //coordinates - ELayer layer; - ui32 moveRemains; //remaining movement points after hero reaches the tile - ui8 turns; //how many turns we have to wait before reaching the tile - 0 means current turn - - EAccessibility accessible; - ENodeAction action; - bool locked; - bool inPQ; - - CGPathNode() - : coord(-1), - layer(ELayer::WRONG), - pqHandle(nullptr) - { - reset(); - } - - STRONG_INLINE - void reset() - { - locked = false; - accessible = NOT_SET; - moveRemains = 0; - cost = std::numeric_limits::max(); - turns = 255; - theNodeBefore = nullptr; - action = UNKNOWN; - inPQ = false; - pq = nullptr; - } - - STRONG_INLINE - float getCost() const - { - return cost; - } - - STRONG_INLINE - void setCost(float value) - { - if(value == cost) - return; - - bool getUpNode = value < cost; - cost = value; - // If the node is in the heap, update the heap. - if(inPQ && pq != nullptr) - { - if(getUpNode) - { - pq->increase(this->pqHandle); - } - else - { - pq->decrease(this->pqHandle); - } - } - } - - STRONG_INLINE - void update(const int3 & Coord, const ELayer Layer, const EAccessibility Accessible) - { - if(layer == ELayer::WRONG) - { - coord = Coord; - layer = Layer; - } - else - { - reset(); - } - - accessible = Accessible; - } - - STRONG_INLINE - bool reachable() const - { - return turns < 255; - } - - using TFibHeap = boost::heap::fibonacci_heap>>; - - TFibHeap::handle_type pqHandle; - TFibHeap* pq; - -private: - float cost; //total cost of the path to this tile measured in turns with fractions -}; - -struct DLL_LINKAGE CGPath -{ - std::vector nodes; //just get node by node - - int3 startPos() const; // start point - int3 endPos() const; //destination point -}; - -struct DLL_LINKAGE CPathsInfo -{ - using ELayer = EPathfindingLayer; - - const CGHeroInstance * hero; - int3 hpos; - int3 sizes; - boost::multi_array nodes; //[layer][level][w][h] - - CPathsInfo(const int3 & Sizes, const CGHeroInstance * hero_); - ~CPathsInfo(); - const CGPathNode * getPathInfo(const int3 & tile) const; - bool getPath(CGPath & out, const int3 & dst) const; - const CGPathNode * getNode(const int3 & coord) const; - - STRONG_INLINE - CGPathNode * getNode(const int3 & coord, const ELayer layer) - { - return &nodes[layer][coord.z][coord.x][coord.y]; - } -}; - -struct DLL_LINKAGE PathNodeInfo -{ - CGPathNode * node; - const CGObjectInstance * nodeObject; - const CGHeroInstance * nodeHero; - const TerrainTile * tile; - int3 coord; - bool guarded; - PlayerRelations::PlayerRelations objectRelations; - PlayerRelations::PlayerRelations heroRelations; - bool isInitialPosition; - - PathNodeInfo(); - - virtual void setNode(CGameState * gs, CGPathNode * n); - - void updateInfo(CPathfinderHelper * hlp, CGameState * gs); - - bool isNodeObjectVisitable() const; -}; - -struct DLL_LINKAGE CDestinationNodeInfo : public PathNodeInfo -{ - CGPathNode::ENodeAction action; - int turn; - int movementLeft; - float cost; //same as CGPathNode::cost - bool blocked; - bool isGuardianTile; - - CDestinationNodeInfo(); - - virtual void setNode(CGameState * gs, CGPathNode * n) override; - - virtual bool isBetterWay() const; -}; - -class IPathfindingRule -{ -public: - virtual ~IPathfindingRule() = default; - virtual void process( - const PathNodeInfo & source, - CDestinationNodeInfo & destination, - const PathfinderConfig * pathfinderConfig, - CPathfinderHelper * pathfinderHelper) const = 0; -}; - -class DLL_LINKAGE MovementCostRule : public IPathfindingRule -{ -public: - virtual void process( - const PathNodeInfo & source, - CDestinationNodeInfo & destination, - const PathfinderConfig * pathfinderConfig, - CPathfinderHelper * pathfinderHelper) const override; -}; - -class DLL_LINKAGE LayerTransitionRule : public IPathfindingRule -{ -public: - virtual void process( - const PathNodeInfo & source, - CDestinationNodeInfo & destination, - const PathfinderConfig * pathfinderConfig, - CPathfinderHelper * pathfinderHelper) const override; -}; - -class DLL_LINKAGE DestinationActionRule : public IPathfindingRule -{ -public: - virtual void process( - const PathNodeInfo & source, - CDestinationNodeInfo & destination, - const PathfinderConfig * pathfinderConfig, - CPathfinderHelper * pathfinderHelper) const override; -}; - -class DLL_LINKAGE PathfinderBlockingRule : public IPathfindingRule -{ -public: - virtual void process( - const PathNodeInfo & source, - CDestinationNodeInfo & destination, - const PathfinderConfig * pathfinderConfig, - CPathfinderHelper * pathfinderHelper) const override - { - auto blockingReason = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper); - - destination.blocked = blockingReason != BlockingReason::NONE; - } - -protected: - enum class BlockingReason - { - NONE = 0, - SOURCE_GUARDED = 1, - DESTINATION_GUARDED = 2, - SOURCE_BLOCKED = 3, - DESTINATION_BLOCKED = 4, - DESTINATION_BLOCKVIS = 5, - DESTINATION_VISIT = 6 - }; - - virtual BlockingReason getBlockingReason( - const PathNodeInfo & source, - const CDestinationNodeInfo & destination, - const PathfinderConfig * pathfinderConfig, - const CPathfinderHelper * pathfinderHelper) const = 0; -}; - -class DLL_LINKAGE MovementAfterDestinationRule : public PathfinderBlockingRule -{ -public: - virtual void process( - const PathNodeInfo & source, - CDestinationNodeInfo & destination, - const PathfinderConfig * pathfinderConfig, - CPathfinderHelper * pathfinderHelper) const override; - -protected: - virtual BlockingReason getBlockingReason( - const PathNodeInfo & source, - const CDestinationNodeInfo & destination, - const PathfinderConfig * pathfinderConfig, - const CPathfinderHelper * pathfinderHelper) const override; -}; - -class DLL_LINKAGE MovementToDestinationRule : public PathfinderBlockingRule -{ -protected: - virtual BlockingReason getBlockingReason( - const PathNodeInfo & source, - const CDestinationNodeInfo & destination, - const PathfinderConfig * pathfinderConfig, - const CPathfinderHelper * pathfinderHelper) const override; -}; - -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 DLL_LINKAGE INodeStorage -{ -public: - using ELayer = EPathfindingLayer; - - virtual ~INodeStorage() = default; - - virtual std::vector getInitialNodes() = 0; - - virtual std::vector calculateNeighbours( - const PathNodeInfo & source, - const PathfinderConfig * pathfinderConfig, - const CPathfinderHelper * pathfinderHelper) = 0; - - virtual std::vector calculateTeleportations( - const PathNodeInfo & source, - const PathfinderConfig * pathfinderConfig, - const CPathfinderHelper * pathfinderHelper) = 0; - - virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) = 0; - - virtual void initialize(const PathfinderOptions & options, const CGameState * gs) = 0; -}; - -class DLL_LINKAGE NodeStorage : public INodeStorage -{ -private: - CPathsInfo & out; - - STRONG_INLINE - void resetTile(const int3 & tile, const EPathfindingLayer & layer, CGPathNode::EAccessibility accessibility); - -public: - NodeStorage(CPathsInfo & pathsInfo, const CGHeroInstance * hero); - - STRONG_INLINE - CGPathNode * getNode(const int3 & coord, const EPathfindingLayer layer) - { - return out.getNode(coord, layer); - } - - void initialize(const PathfinderOptions & options, const CGameState * gs) override; - virtual ~NodeStorage() = default; - - virtual std::vector getInitialNodes() override; - - virtual std::vector calculateNeighbours( - const PathNodeInfo & source, - const PathfinderConfig * pathfinderConfig, - const CPathfinderHelper * pathfinderHelper) override; - - virtual std::vector calculateTeleportations( - const PathNodeInfo & source, - const PathfinderConfig * pathfinderConfig, - const CPathfinderHelper * pathfinderHelper) override; - - virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override; -}; - -class DLL_LINKAGE PathfinderConfig -{ -public: - std::shared_ptr nodeStorage; - std::vector> rules; - PathfinderOptions options; - - PathfinderConfig( - std::shared_ptr nodeStorage, - std::vector> rules); - virtual ~PathfinderConfig() = default; - - virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) = 0; -}; - -class DLL_LINKAGE SingleHeroPathfinderConfig : public PathfinderConfig -{ -private: - std::unique_ptr pathfinderHelper; - -public: - SingleHeroPathfinderConfig(CPathsInfo & out, CGameState * gs, const CGHeroInstance * hero); - virtual ~SingleHeroPathfinderConfig() = default; - - virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override; - - static std::vector> buildRuleSet(); -}; - -class CPathfinder -{ -public: - friend class CPathfinderHelper; - - CPathfinder( - CGameState * _gs, - std::shared_ptr config); - - 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: - CGameState * gamestate; - - using ELayer = EPathfindingLayer; - - std::shared_ptr config; - - boost::heap::fibonacci_heap> > pq; - - PathNodeInfo source; //current (source) path node -> we took it from the queue - CDestinationNodeInfo destination; //destination node -> it's a neighbour of source that we consider - - bool isLayerTransitionPossible() const; - CGPathNode::ENodeAction getTeleportDestAction() const; - - bool isDestinationGuardian() const; - - void initializeGraph(); - - STRONG_INLINE - void push(CGPathNode * node); - - STRONG_INLINE - CGPathNode * topAndPop(); -}; - -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; - int pathfindingVal; - - BonusCache(const TConstBonusListPtr & bonusList); - }; - std::unique_ptr bonusCache; - - const CGHeroInstance * hero; - mutable TConstBonusListPtr bonuses; - mutable int maxMovePointsLand; - mutable int maxMovePointsWater; - TerrainId nativeTerrain; - int turn; - - TurnInfo(const CGHeroInstance * Hero, const int Turn = 0); - bool isLayerAvailable(const EPathfindingLayer & layer) const; - bool hasBonusOfType(const BonusType type, const int subtype = -1) const; - int valOfBonuses(const BonusType type, const int subtype = -1) const; - void updateHeroBonuses(BonusType type, const CSelector& sel) const; - int getMaxMovePoints(const EPathfindingLayer & layer) const; -}; - -class DLL_LINKAGE CPathfinderHelper : private CGameInfoCallback -{ -public: - enum EPatrolState - { - PATROL_NONE = 0, - PATROL_LOCKED = 1, - PATROL_RADIUS - } patrolState; - std::unordered_set patrolTiles; - - int turn; - PlayerColor owner; - const CGHeroInstance * hero; - std::vector turnsInfo; - const PathfinderOptions & options; - - CPathfinderHelper(CGameState * gs, const CGHeroInstance * Hero, const PathfinderOptions & Options); - virtual ~CPathfinderHelper(); - void initializePatrol(); - bool isHeroPatrolLocked() const; - bool isPatrolMovementAllowed(const int3 & dst) const; - void updateTurnInfo(const int turn = 0); - bool isLayerAvailable(const EPathfindingLayer & layer) const; - const TurnInfo * getTurnInfo() const; - bool hasBonusOfType(const BonusType type, const int subtype = -1) const; - int getMaxMovePoints(const EPathfindingLayer & layer) const; - - std::vector getCastleGates(const PathNodeInfo & source) const; - bool isAllowedTeleportEntrance(const CGTeleport * obj) const; - std::vector getAllowedTeleportChannelExits(const TeleportChannelID & channelID) 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) - - std::vector getNeighbourTiles(const PathNodeInfo & source) const; - std::vector getTeleportExits(const PathNodeInfo & source) const; - - void getNeighbours( - const TerrainTile & srcTile, - const int3 & srcCoord, - std::vector & vec, - const boost::logic::tribool & onLand, - const bool limitCoastSailing) const; - - int getMovementCost( - const int3 & src, - const int3 & dst, - const TerrainTile * ct, - const TerrainTile * dt, - const int remainingMovePoints = -1, - const bool checkLast = true, - boost::logic::tribool isDstSailLayer = boost::logic::indeterminate, - boost::logic::tribool isDstWaterLayer = boost::logic::indeterminate) const; - - int getMovementCost( - const PathNodeInfo & src, - const PathNodeInfo & dst, - const int remainingMovePoints = -1, - const bool checkLast = true) const - { - return getMovementCost( - src.coord, - dst.coord, - src.tile, - dst.tile, - remainingMovePoints, - checkLast, - dst.node->layer == EPathfindingLayer::SAIL, - dst.node->layer == EPathfindingLayer::WATER - ); - } - - int movementPointsAfterEmbark(int movement, int basicCost, bool disembark) const; - bool passOneTurnLimitCheck(const PathNodeInfo & source) const; -}; - -VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index a05d97f01..6a6f59592 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -31,6 +31,7 @@ #include "../CTownHandler.h" #include "../mapping/CMap.h" #include "CGTownInstance.h" +#include "../pathfinder/TurnInfo.h" #include "../serializer/JsonSerializeFormat.h" #include "../mapObjectConstructors/AObjectTypeHandler.h" #include "../mapObjectConstructors/CObjectClassesHandler.h" diff --git a/lib/pathfinder/CGPathNode.cpp b/lib/pathfinder/CGPathNode.cpp new file mode 100644 index 000000000..10111e107 --- /dev/null +++ b/lib/pathfinder/CGPathNode.cpp @@ -0,0 +1,161 @@ +/* + * CGPathNode.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "CGPathNode.h" + +#include "CPathfinder.h" + +#include "../CGameState.h" +#include "../mapObjects/CGHeroInstance.h" +#include "../mapping/CMapDefines.h" + +VCMI_LIB_NAMESPACE_BEGIN + +static bool canSeeObj(const CGObjectInstance * obj) +{ + /// Pathfinder should ignore placed events + return obj != nullptr && obj->ID != Obj::EVENT; +} + +int3 CGPath::startPos() const +{ + return nodes[nodes.size()-1].coord; +} + +int3 CGPath::endPos() const +{ + return nodes[0].coord; +} + +CPathsInfo::CPathsInfo(const int3 & Sizes, const CGHeroInstance * hero_) + : sizes(Sizes), hero(hero_) +{ + nodes.resize(boost::extents[ELayer::NUM_LAYERS][sizes.z][sizes.x][sizes.y]); +} + +CPathsInfo::~CPathsInfo() = default; + +const CGPathNode * CPathsInfo::getPathInfo(const int3 & tile) const +{ + assert(vstd::iswithin(tile.x, 0, sizes.x)); + assert(vstd::iswithin(tile.y, 0, sizes.y)); + assert(vstd::iswithin(tile.z, 0, sizes.z)); + + return getNode(tile); +} + +bool CPathsInfo::getPath(CGPath & out, const int3 & dst) const +{ + out.nodes.clear(); + const CGPathNode * curnode = getNode(dst); + if(!curnode->theNodeBefore) + return false; + + while(curnode) + { + const CGPathNode cpn = * curnode; + curnode = curnode->theNodeBefore; + out.nodes.push_back(cpn); + } + return true; +} + +const CGPathNode * CPathsInfo::getNode(const int3 & coord) const +{ + const auto * landNode = &nodes[ELayer::LAND][coord.z][coord.x][coord.y]; + if(landNode->reachable()) + return landNode; + else + return &nodes[ELayer::SAIL][coord.z][coord.x][coord.y]; +} + +PathNodeInfo::PathNodeInfo() + : node(nullptr), nodeObject(nullptr), tile(nullptr), coord(-1, -1, -1), guarded(false), isInitialPosition(false) +{ +} + +void PathNodeInfo::setNode(CGameState * gs, CGPathNode * n) +{ + node = n; + + if(coord != node->coord) + { + assert(node->coord.valid()); + + coord = node->coord; + tile = gs->getTile(coord); + nodeObject = tile->topVisitableObj(); + + if(nodeObject && nodeObject->ID == Obj::HERO) + { + nodeHero = dynamic_cast(nodeObject); + nodeObject = tile->topVisitableObj(true); + + if(!nodeObject) + nodeObject = nodeHero; + } + else + { + nodeHero = nullptr; + } + } + + guarded = false; +} + +void PathNodeInfo::updateInfo(CPathfinderHelper * hlp, CGameState * gs) +{ + if(gs->guardingCreaturePosition(node->coord).valid() && !isInitialPosition) + { + guarded = true; + } + + if(nodeObject) + { + objectRelations = gs->getPlayerRelations(hlp->owner, nodeObject->tempOwner); + } + + if(nodeHero) + { + heroRelations = gs->getPlayerRelations(hlp->owner, nodeHero->tempOwner); + } +} + +bool PathNodeInfo::isNodeObjectVisitable() const +{ + /// Hero can't visit objects while walking on water or flying + return (node->layer == EPathfindingLayer::LAND || node->layer == EPathfindingLayer::SAIL) + && (canSeeObj(nodeObject) || canSeeObj(nodeHero)); +} + + +CDestinationNodeInfo::CDestinationNodeInfo(): + blocked(false), + action(EPathNodeAction::UNKNOWN) +{ +} + +void CDestinationNodeInfo::setNode(CGameState * gs, CGPathNode * n) +{ + PathNodeInfo::setNode(gs, n); + + blocked = false; + action = EPathNodeAction::UNKNOWN; +} + +bool CDestinationNodeInfo::isBetterWay() const +{ + if(node->turns == 0xff) //we haven't been here before + return true; + else + return cost < node->getCost(); //this route is faster +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/pathfinder/CGPathNode.h b/lib/pathfinder/CGPathNode.h new file mode 100644 index 000000000..4b7b2577e --- /dev/null +++ b/lib/pathfinder/CGPathNode.h @@ -0,0 +1,222 @@ +/* + * CGPathNode.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../GameConstants.h" +#include "../int3.h" + +#include + +VCMI_LIB_NAMESPACE_BEGIN + +class CGHeroInstance; +class CGObjectInstance; +class CGameState; +class CPathfinderHelper; +struct TerrainTile; + +template +struct DLL_LINKAGE NodeComparer +{ + STRONG_INLINE + bool operator()(const N * lhs, const N * rhs) const + { + return lhs->getCost() > rhs->getCost(); + } +}; + +enum class EPathAccessibility : ui8 +{ + NOT_SET, + ACCESSIBLE, //tile can be entered and passed + VISITABLE, //tile can be entered as the last tile in path + BLOCKVIS, //visitable from neighboring tile but not passable + FLYABLE, //can only be accessed in air layer + BLOCKED //tile can be neither entered nor visited +}; + +enum class EPathNodeAction : ui8 +{ + UNKNOWN, + EMBARK, + DISEMBARK, + NORMAL, + BATTLE, + VISIT, + BLOCKING_VISIT, + TELEPORT_NORMAL, + TELEPORT_BLOCKING_VISIT, + TELEPORT_BATTLE +}; + +struct DLL_LINKAGE CGPathNode +{ + using ELayer = EPathfindingLayer; + + CGPathNode * theNodeBefore; + int3 coord; //coordinates + ELayer layer; + ui32 moveRemains; //remaining movement points after hero reaches the tile + ui8 turns; //how many turns we have to wait before reaching the tile - 0 means current turn + + EPathAccessibility accessible; + EPathNodeAction action; + bool locked; + bool inPQ; + + CGPathNode() + : coord(-1), + layer(ELayer::WRONG), + pqHandle(nullptr) + { + reset(); + } + + STRONG_INLINE + void reset() + { + locked = false; + accessible = EPathAccessibility::NOT_SET; + moveRemains = 0; + cost = std::numeric_limits::max(); + turns = 255; + theNodeBefore = nullptr; + action = EPathNodeAction::UNKNOWN; + inPQ = false; + pq = nullptr; + } + + STRONG_INLINE + float getCost() const + { + return cost; + } + + STRONG_INLINE + void setCost(float value) + { + if(value == cost) + return; + + bool getUpNode = value < cost; + cost = value; + // If the node is in the heap, update the heap. + if(inPQ && pq != nullptr) + { + if(getUpNode) + { + pq->increase(this->pqHandle); + } + else + { + pq->decrease(this->pqHandle); + } + } + } + + STRONG_INLINE + void update(const int3 & Coord, const ELayer Layer, const EPathAccessibility Accessible) + { + if(layer == ELayer::WRONG) + { + coord = Coord; + layer = Layer; + } + else + { + reset(); + } + + accessible = Accessible; + } + + STRONG_INLINE + bool reachable() const + { + return turns < 255; + } + + using TFibHeap = boost::heap::fibonacci_heap>>; + + TFibHeap::handle_type pqHandle; + TFibHeap* pq; + +private: + float cost; //total cost of the path to this tile measured in turns with fractions +}; + +struct DLL_LINKAGE CGPath +{ + std::vector nodes; //just get node by node + + int3 startPos() const; // start point + int3 endPos() const; //destination point +}; + +struct DLL_LINKAGE CPathsInfo +{ + using ELayer = EPathfindingLayer; + + const CGHeroInstance * hero; + int3 hpos; + int3 sizes; + boost::multi_array nodes; //[layer][level][w][h] + + CPathsInfo(const int3 & Sizes, const CGHeroInstance * hero_); + ~CPathsInfo(); + const CGPathNode * getPathInfo(const int3 & tile) const; + bool getPath(CGPath & out, const int3 & dst) const; + const CGPathNode * getNode(const int3 & coord) const; + + STRONG_INLINE + CGPathNode * getNode(const int3 & coord, const ELayer layer) + { + return &nodes[layer][coord.z][coord.x][coord.y]; + } +}; + +struct DLL_LINKAGE PathNodeInfo +{ + CGPathNode * node; + const CGObjectInstance * nodeObject; + const CGHeroInstance * nodeHero; + const TerrainTile * tile; + int3 coord; + bool guarded; + PlayerRelations::PlayerRelations objectRelations; + PlayerRelations::PlayerRelations heroRelations; + bool isInitialPosition; + + PathNodeInfo(); + + virtual void setNode(CGameState * gs, CGPathNode * n); + + void updateInfo(CPathfinderHelper * hlp, CGameState * gs); + + bool isNodeObjectVisitable() const; +}; + +struct DLL_LINKAGE CDestinationNodeInfo : public PathNodeInfo +{ + EPathNodeAction action; + int turn; + int movementLeft; + float cost; //same as CGPathNode::cost + bool blocked; + bool isGuardianTile; + + CDestinationNodeInfo(); + + virtual void setNode(CGameState * gs, CGPathNode * n) override; + + virtual bool isBetterWay() const; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/pathfinder/CPathfinder.cpp b/lib/pathfinder/CPathfinder.cpp new file mode 100644 index 000000000..57100071a --- /dev/null +++ b/lib/pathfinder/CPathfinder.cpp @@ -0,0 +1,668 @@ +/* + * CPathfinder.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "CPathfinder.h" + +#include "INodeStorage.h" +#include "PathfinderOptions.h" +#include "PathfindingRules.h" +#include "TurnInfo.h" + +#include "../CGameState.h" +#include "../CPlayerState.h" +#include "../TerrainHandler.h" +#include "../mapObjects/CGHeroInstance.h" +#include "../mapping/CMap.h" + +VCMI_LIB_NAMESPACE_BEGIN + +std::vector CPathfinderHelper::getNeighbourTiles(const PathNodeInfo & source) const +{ + std::vector neighbourTiles; + neighbourTiles.reserve(8); + + getNeighbours( + *source.tile, + source.node->coord, + neighbourTiles, + boost::logic::indeterminate, + source.node->layer == EPathfindingLayer::SAIL); + + if(source.isNodeObjectVisitable()) + { + vstd::erase_if(neighbourTiles, [&](const int3 & tile) -> bool + { + return !canMoveBetween(tile, source.nodeObject->visitablePos()); + }); + } + + return neighbourTiles; +} + +CPathfinder::CPathfinder(CGameState * _gs, std::shared_ptr config): + gamestate(_gs), + config(std::move(config)) +{ + initializeGraph(); +} + + +void CPathfinder::push(CGPathNode * node) +{ + if(node && !node->inPQ) + { + node->inPQ = true; + node->pq = &this->pq; + auto handle = pq.push(node); + node->pqHandle = handle; + } +} + +CGPathNode * CPathfinder::topAndPop() +{ + auto * node = pq.top(); + + pq.pop(); + node->inPQ = false; + node->pq = nullptr; + return node; +} + +void CPathfinder::calculatePaths() +{ + //logGlobal->info("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 + std::vector initialNodes = config->nodeStorage->getInitialNodes(); + int counter = 0; + + for(auto * initialNode : initialNodes) + { + if(!gamestate->isInTheMap(initialNode->coord)/* || !gs->map->isInTheMap(dest)*/) //check input + { + logGlobal->error("CGameState::calculatePaths: Hero outside the gs->map? How dare you..."); + throw std::runtime_error("Wrong checksum"); + } + + source.setNode(gamestate, initialNode); + auto * hlp = config->getOrCreatePathfinderHelper(source, gamestate); + + if(hlp->isHeroPatrolLocked()) + continue; + + pq.push(initialNode); + } + + while(!pq.empty()) + { + counter++; + auto * node = topAndPop(); + + source.setNode(gamestate, node); + source.node->locked = true; + + int movement = source.node->moveRemains; + uint8_t turn = source.node->turns; + float cost = source.node->getCost(); + + auto * hlp = config->getOrCreatePathfinderHelper(source, gamestate); + + hlp->updateTurnInfo(turn); + if(!movement) + { + hlp->updateTurnInfo(++turn); + movement = hlp->getMaxMovePoints(source.node->layer); + if(!hlp->passOneTurnLimitCheck(source)) + continue; + } + + source.isInitialPosition = source.nodeHero == hlp->hero; + source.updateInfo(hlp, gamestate); + + //add accessible neighbouring nodes to the queue + auto neighbourNodes = config->nodeStorage->calculateNeighbours(source, config.get(), hlp); + for(CGPathNode * neighbour : neighbourNodes) + { + if(neighbour->locked) + continue; + + if(!hlp->isLayerAvailable(neighbour->layer)) + continue; + + destination.setNode(gamestate, neighbour); + hlp = config->getOrCreatePathfinderHelper(destination, gamestate); + + if(!hlp->isPatrolMovementAllowed(neighbour->coord)) + continue; + + /// Check transition without tile accessability rules + if(source.node->layer != neighbour->layer && !isLayerTransitionPossible()) + continue; + + destination.turn = turn; + destination.movementLeft = movement; + destination.cost = cost; + destination.updateInfo(hlp, gamestate); + destination.isGuardianTile = destination.guarded && isDestinationGuardian(); + + for(const auto & rule : config->rules) + { + rule->process(source, destination, config.get(), hlp); + + if(destination.blocked) + break; + } + + if(!destination.blocked) + push(destination.node); + + } //neighbours loop + + //just add all passable teleport exits + hlp = config->getOrCreatePathfinderHelper(source, gamestate); + + /// For now we disable teleports usage for patrol movement + /// VCAI not aware about patrol and may stuck while attempt to use teleport + if(hlp->patrolState == CPathfinderHelper::PATROL_RADIUS) + continue; + + auto teleportationNodes = config->nodeStorage->calculateTeleportations(source, config.get(), hlp); + for(CGPathNode * teleportNode : teleportationNodes) + { + if(teleportNode->locked) + continue; + /// TODO: We may consider use invisible exits on FoW border in future + /// Useful for AI when at least one tile around exit is visible and passable + /// Objects are usually visible on FoW border anyway so it's not cheating. + /// + /// For now it's disabled as it's will cause crashes in movement code. + if(teleportNode->accessible == EPathAccessibility::BLOCKED) + continue; + + destination.setNode(gamestate, teleportNode); + destination.turn = turn; + destination.movementLeft = movement; + destination.cost = cost; + + if(destination.isBetterWay()) + { + destination.action = getTeleportDestAction(); + config->nodeStorage->commit(destination, source); + + if(destination.node->action == EPathNodeAction::TELEPORT_NORMAL) + push(destination.node); + } + } + } //queue loop + + logAi->trace("CPathfinder finished with %s iterations", std::to_string(counter)); +} + +std::vector CPathfinderHelper::getAllowedTeleportChannelExits(const TeleportChannelID & channelID) const +{ + std::vector allowedExits; + + for(const auto & objId : getTeleportChannelExits(channelID, hero->tempOwner)) + { + const auto * obj = getObj(objId); + if(dynamic_cast(obj)) + { + auto pos = obj->getBlockedPos(); + for(const auto & p : pos) + { + if(gs->map->getTile(p).topVisitableId() == obj->ID) + allowedExits.push_back(p); + } + } + else if(obj && CGTeleport::isExitPassable(gs, hero, obj)) + allowedExits.push_back(obj->visitablePos()); + } + + return allowedExits; +} + +std::vector CPathfinderHelper::getCastleGates(const PathNodeInfo & source) const +{ + std::vector allowedExits; + + auto towns = getPlayerState(hero->tempOwner)->towns; + for(const auto & town : towns) + { + if(town->id != source.nodeObject->id && town->visitingHero == nullptr + && town->hasBuilt(BuildingID::CASTLE_GATE, ETownType::INFERNO)) + { + allowedExits.push_back(town->visitablePos()); + } + } + + return allowedExits; +} + +std::vector CPathfinderHelper::getTeleportExits(const PathNodeInfo & source) const +{ + std::vector teleportationExits; + + const auto * objTeleport = dynamic_cast(source.nodeObject); + if(isAllowedTeleportEntrance(objTeleport)) + { + for(const auto & exit : getAllowedTeleportChannelExits(objTeleport->channel)) + { + teleportationExits.push_back(exit); + } + } + else if(options.useCastleGate + && (source.nodeObject->ID == Obj::TOWN && source.nodeObject->subID == ETownType::INFERNO + && source.objectRelations != PlayerRelations::ENEMIES)) + { + /// TODO: Find way to reuse CPlayerSpecificInfoCallback::getTownsInfo + /// This may be handy if we allow to use teleportation to friendly towns + for(const auto & exit : getCastleGates(source)) + { + teleportationExits.push_back(exit); + } + } + + return teleportationExits; +} + +bool CPathfinderHelper::isHeroPatrolLocked() const +{ + return patrolState == PATROL_LOCKED; +} + +bool CPathfinderHelper::isPatrolMovementAllowed(const int3 & dst) const +{ + if(patrolState == PATROL_RADIUS) + { + if(!vstd::contains(patrolTiles, dst)) + return false; + } + + return true; +} + +bool CPathfinder::isLayerTransitionPossible() const +{ + ELayer destLayer = destination.node->layer; + + /// No layer transition allowed when previous node action is BATTLE + if(source.node->action == EPathNodeAction::BATTLE) + return false; + + switch(source.node->layer) + { + case ELayer::LAND: + if(destLayer == ELayer::AIR) + { + if(!config->options.lightweightFlyingMode || source.isInitialPosition) + return true; + } + else if(destLayer == ELayer::SAIL) + { + if(destination.tile->isWater()) + return true; + } + else + return true; + + break; + + case ELayer::SAIL: + if(destLayer == ELayer::LAND && !destination.tile->isWater()) + 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; +} + +EPathNodeAction CPathfinder::getTeleportDestAction() const +{ + EPathNodeAction action = EPathNodeAction::TELEPORT_NORMAL; + + if(destination.isNodeObjectVisitable() && destination.nodeHero) + { + if(destination.heroRelations == PlayerRelations::ENEMIES) + action = EPathNodeAction::TELEPORT_BATTLE; + else + action = EPathNodeAction::TELEPORT_BLOCKING_VISIT; + } + + return action; +} + +bool CPathfinder::isDestinationGuardian() const +{ + return gamestate->guardingCreaturePosition(destination.node->coord) == destination.node->coord; +} + +void CPathfinderHelper::initializePatrol() +{ + auto state = PATROL_NONE; + + if(hero->patrol.patrolling && !getPlayerState(hero->tempOwner)->human) + { + if(hero->patrol.patrolRadius) + { + state = PATROL_RADIUS; + gs->getTilesInRange(patrolTiles, hero->patrol.initialPos, hero->patrol.patrolRadius, std::optional(), 0, int3::DIST_MANHATTAN); + } + else + state = PATROL_LOCKED; + } + + patrolState = state; +} + +void CPathfinder::initializeGraph() +{ + INodeStorage * nodeStorage = config->nodeStorage.get(); + nodeStorage->initialize(config->options, gamestate); +} + +bool CPathfinderHelper::canMoveBetween(const int3 & a, const int3 & b) const +{ + return gs->checkForVisitableDir(a, b); +} + +bool CPathfinderHelper::isAllowedTeleportEntrance(const CGTeleport * obj) const +{ + if(!obj || !isTeleportEntrancePassable(obj, hero->tempOwner)) + return false; + + const 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 CPathfinderHelper::addTeleportTwoWay(const CGTeleport * obj) const +{ + return options.useTeleportTwoWay && isTeleportChannelBidirectional(obj->channel, hero->tempOwner); +} + +bool CPathfinderHelper::addTeleportOneWay(const CGTeleport * obj) const +{ + if(options.useTeleportOneWay && isTeleportChannelUnidirectional(obj->channel, hero->tempOwner)) + { + auto passableExits = CGTeleport::getPassableExits(gs, hero, getTeleportChannelExits(obj->channel, hero->tempOwner)); + if(passableExits.size() == 1) + return true; + } + return false; +} + +bool CPathfinderHelper::addTeleportOneWayRandom(const CGTeleport * obj) const +{ + if(options.useTeleportOneWayRandom && isTeleportChannelUnidirectional(obj->channel, hero->tempOwner)) + { + auto passableExits = CGTeleport::getPassableExits(gs, hero, getTeleportChannelExits(obj->channel, hero->tempOwner)); + if(passableExits.size() > 1) + return true; + } + return false; +} + +bool CPathfinderHelper::addTeleportWhirlpool(const CGWhirlpool * obj) const +{ + return options.useTeleportWhirlpool && hasBonusOfType(BonusType::WHIRLPOOL_PROTECTION) && obj; +} + +int CPathfinderHelper::movementPointsAfterEmbark(int movement, int basicCost, bool disembark) const +{ + return hero->movementPointsAfterEmbark(movement, basicCost, disembark, getTurnInfo()); +} + +bool CPathfinderHelper::passOneTurnLimitCheck(const PathNodeInfo & source) const +{ + + if(!options.oneTurnSpecialLayersLimit) + return true; + + if(source.node->layer == EPathfindingLayer::WATER) + return false; + if(source.node->layer == EPathfindingLayer::AIR) + { + return options.originalMovementRules && source.node->accessible == EPathAccessibility::ACCESSIBLE; + } + + return true; +} + +CPathfinderHelper::CPathfinderHelper(CGameState * gs, const CGHeroInstance * Hero, const PathfinderOptions & Options): + CGameInfoCallback(gs, std::optional()), + turn(-1), + hero(Hero), + options(Options), + owner(Hero->tempOwner) +{ + turnsInfo.reserve(16); + updateTurnInfo(); + initializePatrol(); +} + +CPathfinderHelper::~CPathfinderHelper() +{ + for(auto * ti : turnsInfo) + delete ti; +} + +void CPathfinderHelper::updateTurnInfo(const int Turn) +{ + if(turn != Turn) + { + turn = Turn; + if(turn >= turnsInfo.size()) + { + auto * ti = new TurnInfo(hero, turn); + turnsInfo.push_back(ti); + } + } +} + +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); +} + +const TurnInfo * CPathfinderHelper::getTurnInfo() const +{ + return turnsInfo[turn]; +} + +bool CPathfinderHelper::hasBonusOfType(const BonusType type, const int subtype) const +{ + return turnsInfo[turn]->hasBonusOfType(type, subtype); +} + +int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer & layer) const +{ + return turnsInfo[turn]->getMaxMovePoints(layer); +} + +void CPathfinderHelper::getNeighbours( + const TerrainTile & srcTile, + const int3 & srcCoord, + std::vector & vec, + const boost::logic::tribool & onLand, + const bool limitCoastSailing) const +{ + 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), + int3(-1, -1, +0), int3(0, -1, +0), int3(+1, -1, +0) + }; + + for(const auto & dir : dirs) + { + const int3 destCoord = srcCoord + dir; + if(!map->isInTheMap(destCoord)) + continue; + + const TerrainTile & destTile = map->getTile(destCoord); + if(!destTile.terType->isPassable()) + continue; + +// //we cannot visit things from blocked tiles +// if(srcTile.blocked && !srcTile.visitable && destTile.visitable && srcTile.blockingObjects.front()->ID != HEROI_TYPE) +// { +// continue; +// } + + /// Following condition let us avoid diagonal movement over coast when sailing + if(srcTile.terType->isWater() && limitCoastSailing && destTile.terType->isWater() && dir.x && dir.y) //diagonal move through water + { + const int3 horizontalNeighbour = srcCoord + int3{dir.x, 0, 0}; + const int3 verticalNeighbour = srcCoord + int3{0, dir.y, 0}; + if(map->getTile(horizontalNeighbour).terType->isLand() || map->getTile(verticalNeighbour).terType->isLand()) + continue; + } + + if(indeterminate(onLand) || onLand == destTile.terType->isLand()) + { + vec.push_back(destCoord); + } + } +} + +int CPathfinderHelper::getMovementCost( + const PathNodeInfo & src, + const PathNodeInfo & dst, + const int remainingMovePoints, + const bool checkLast) const +{ + return getMovementCost( + src.coord, + dst.coord, + src.tile, + dst.tile, + remainingMovePoints, + checkLast, + dst.node->layer == EPathfindingLayer::SAIL, + dst.node->layer == EPathfindingLayer::WATER + ); +} + +int CPathfinderHelper::getMovementCost( + const int3 & src, + const int3 & dst, + const TerrainTile * ct, + const TerrainTile * dt, + const int remainingMovePoints, + const bool checkLast, + boost::logic::tribool isDstSailLayer, + boost::logic::tribool isDstWaterLayer) const +{ + if(src == dst) //same tile + return 0; + + const auto * ti = getTurnInfo(); + + if(ct == nullptr || dt == nullptr) + { + ct = hero->cb->getTile(src); + dt = hero->cb->getTile(dst); + } + + bool isSailLayer; + if(indeterminate(isDstSailLayer)) + isSailLayer = hero->boat && hero->boat->layer == EPathfindingLayer::SAIL && dt->terType->isWater(); + else + isSailLayer = static_cast(isDstSailLayer); + + bool isWaterLayer; + if(indeterminate(isDstWaterLayer)) + isWaterLayer = ((hero->boat && hero->boat->layer == EPathfindingLayer::WATER) || ti->hasBonusOfType(BonusType::WATER_WALKING)) && dt->terType->isWater(); + else + isWaterLayer = static_cast(isDstWaterLayer); + + bool isAirLayer = (hero->boat && hero->boat->layer == EPathfindingLayer::AIR) || ti->hasBonusOfType(BonusType::FLYING_MOVEMENT); + + int ret = hero->getTileCost(*dt, *ct, ti); + if(isSailLayer) + { + if(ct->hasFavorableWinds()) + ret = static_cast(ret * 2.0 / 3); + } + else if(isAirLayer) + vstd::amin(ret, GameConstants::BASE_MOVEMENT_COST + ti->valOfBonuses(BonusType::FLYING_MOVEMENT)); + else if(isWaterLayer && ti->hasBonusOfType(BonusType::WATER_WALKING)) + ret = static_cast(ret * (100.0 + ti->valOfBonuses(BonusType::WATER_WALKING)) / 100.0); + + if(src.x != dst.x && src.y != dst.y) //it's diagonal move + { + int old = ret; + ret = static_cast(ret * M_SQRT2); + //diagonal move costs too much but normal move is possible - allow diagonal move for remaining move points + // https://heroes.thelazy.net/index.php/Movement#Diagonal_move_exception + if(ret > remainingMovePoints && remainingMovePoints >= old) + { + return remainingMovePoints; + } + } + + const int left = remainingMovePoints - ret; + constexpr auto maxCostOfOneStep = static_cast(175 * M_SQRT2); // diagonal move on Swamp - 247 MP + if(checkLast && left > 0 && left <= maxCostOfOneStep) //it might be the last tile - if no further move possible we take all move points + { + std::vector vec; + vec.reserve(8); //optimization + getNeighbours(*dt, dst, vec, ct->terType->isLand(), true); + for(const auto & elem : vec) + { + int fcost = getMovementCost(dst, elem, nullptr, nullptr, left, false); + if(fcost <= left) + { + return ret; + } + } + ret = remainingMovePoints; + } + + return ret; +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/pathfinder/CPathfinder.h b/lib/pathfinder/CPathfinder.h new file mode 100644 index 000000000..4c196ee3b --- /dev/null +++ b/lib/pathfinder/CPathfinder.h @@ -0,0 +1,126 @@ +/* + * CPathfinder.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "CGPathNode.h" +#include "../IGameCallback.h" +#include "../bonuses/BonusEnum.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class CGWhirlpool; +struct TurnInfo; +struct PathfinderOptions; + +class CPathfinder +{ +public: + friend class CPathfinderHelper; + + CPathfinder( + CGameState * _gs, + std::shared_ptr config); + + 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: + CGameState * gamestate; + + using ELayer = EPathfindingLayer; + + std::shared_ptr config; + + boost::heap::fibonacci_heap> > pq; + + PathNodeInfo source; //current (source) path node -> we took it from the queue + CDestinationNodeInfo destination; //destination node -> it's a neighbour of source that we consider + + bool isLayerTransitionPossible() const; + EPathNodeAction getTeleportDestAction() const; + + bool isDestinationGuardian() const; + + void initializeGraph(); + + STRONG_INLINE + void push(CGPathNode * node); + + STRONG_INLINE + CGPathNode * topAndPop(); +}; + +class DLL_LINKAGE CPathfinderHelper : private CGameInfoCallback +{ +public: + enum EPatrolState + { + PATROL_NONE = 0, + PATROL_LOCKED = 1, + PATROL_RADIUS + } patrolState; + std::unordered_set patrolTiles; + + int turn; + PlayerColor owner; + const CGHeroInstance * hero; + std::vector turnsInfo; + const PathfinderOptions & options; + + CPathfinderHelper(CGameState * gs, const CGHeroInstance * Hero, const PathfinderOptions & Options); + virtual ~CPathfinderHelper(); + void initializePatrol(); + bool isHeroPatrolLocked() const; + bool isPatrolMovementAllowed(const int3 & dst) const; + void updateTurnInfo(const int turn = 0); + bool isLayerAvailable(const EPathfindingLayer & layer) const; + const TurnInfo * getTurnInfo() const; + bool hasBonusOfType(const BonusType type, const int subtype = -1) const; + int getMaxMovePoints(const EPathfindingLayer & layer) const; + + std::vector getCastleGates(const PathNodeInfo & source) const; + bool isAllowedTeleportEntrance(const CGTeleport * obj) const; + std::vector getAllowedTeleportChannelExits(const TeleportChannelID & channelID) 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) + + std::vector getNeighbourTiles(const PathNodeInfo & source) const; + std::vector getTeleportExits(const PathNodeInfo & source) const; + + void getNeighbours( + const TerrainTile & srcTile, + const int3 & srcCoord, + std::vector & vec, + const boost::logic::tribool & onLand, + const bool limitCoastSailing) const; + + int getMovementCost( + const int3 & src, + const int3 & dst, + const TerrainTile * ct, + const TerrainTile * dt, + const int remainingMovePoints = -1, + const bool checkLast = true, + boost::logic::tribool isDstSailLayer = boost::logic::indeterminate, + boost::logic::tribool isDstWaterLayer = boost::logic::indeterminate) const; + + int getMovementCost( + const PathNodeInfo & src, + const PathNodeInfo & dst, + const int remainingMovePoints = -1, + const bool checkLast = true) const; + + int movementPointsAfterEmbark(int movement, int basicCost, bool disembark) const; + bool passOneTurnLimitCheck(const PathNodeInfo & source) const; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/pathfinder/INodeStorage.h b/lib/pathfinder/INodeStorage.h new file mode 100644 index 000000000..09caafdab --- /dev/null +++ b/lib/pathfinder/INodeStorage.h @@ -0,0 +1,49 @@ +/* + * INodeStorage.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../GameConstants.h" + +VCMI_LIB_NAMESPACE_BEGIN + +struct CDestinationNodeInfo; +struct CGPathNode; +struct PathfinderOptions; +struct PathNodeInfo; + +class CGameState; +class CPathfinderHelper; +class PathfinderConfig; + +class DLL_LINKAGE INodeStorage +{ +public: + using ELayer = EPathfindingLayer; + + virtual ~INodeStorage() = default; + + virtual std::vector getInitialNodes() = 0; + + virtual std::vector calculateNeighbours( + const PathNodeInfo & source, + const PathfinderConfig * pathfinderConfig, + const CPathfinderHelper * pathfinderHelper) = 0; + + virtual std::vector calculateTeleportations( + const PathNodeInfo & source, + const PathfinderConfig * pathfinderConfig, + const CPathfinderHelper * pathfinderHelper) = 0; + + virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) = 0; + + virtual void initialize(const PathfinderOptions & options, const CGameState * gs) = 0; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/pathfinder/NodeStorage.cpp b/lib/pathfinder/NodeStorage.cpp new file mode 100644 index 000000000..9c4863032 --- /dev/null +++ b/lib/pathfinder/NodeStorage.cpp @@ -0,0 +1,147 @@ +/* + * NodeStorage.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "NodeStorage.h" + +#include "CPathfinder.h" +#include "PathfinderUtil.h" +#include "PathfinderOptions.h" + +#include "../CPlayerState.h" +#include "../mapObjects/CGHeroInstance.h" +#include "../mapping/CMap.h" + +VCMI_LIB_NAMESPACE_BEGIN + +void NodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs) +{ + //TODO: fix this code duplication with AINodeStorage::initialize, problem is to keep `resetTile` inline + + int3 pos; + const PlayerColor player = out.hero->tempOwner; + const int3 sizes = gs->getMapSize(); + const auto fow = static_cast(gs)->getPlayerTeam(player)->fogOfWarMap; + + //make 200% sure that these are loop invariants (also a bit shorter code), let compiler do the rest(loop unswitching) + const bool useFlying = options.useFlying; + const bool useWaterWalking = options.useWaterWalking; + + for(pos.z=0; pos.z < sizes.z; ++pos.z) + { + for(pos.x=0; pos.x < sizes.x; ++pos.x) + { + for(pos.y=0; pos.y < sizes.y; ++pos.y) + { + const TerrainTile tile = gs->map->getTile(pos); + if(tile.terType->isWater()) + { + resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); + if(useFlying) + resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); + if(useWaterWalking) + resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); + } + if(tile.terType->isLand()) + { + resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); + if(useFlying) + resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); + } + } + } + } +} + +std::vector NodeStorage::calculateNeighbours( + const PathNodeInfo & source, + const PathfinderConfig * pathfinderConfig, + const CPathfinderHelper * pathfinderHelper) +{ + std::vector neighbours; + neighbours.reserve(16); + auto accessibleNeighbourTiles = pathfinderHelper->getNeighbourTiles(source); + + for(auto & neighbour : accessibleNeighbourTiles) + { + for(EPathfindingLayer i = EPathfindingLayer::LAND; i < EPathfindingLayer::NUM_LAYERS; i.advance(1)) + { + auto * node = getNode(neighbour, i); + + if(node->accessible == EPathAccessibility::NOT_SET) + continue; + + neighbours.push_back(node); + } + } + + return neighbours; +} + +std::vector NodeStorage::calculateTeleportations( + const PathNodeInfo & source, + const PathfinderConfig * pathfinderConfig, + const CPathfinderHelper * pathfinderHelper) +{ + std::vector neighbours; + + if(!source.isNodeObjectVisitable()) + return neighbours; + + auto accessibleExits = pathfinderHelper->getTeleportExits(source); + + for(auto & neighbour : accessibleExits) + { + auto * node = getNode(neighbour, source.node->layer); + + neighbours.push_back(node); + } + + return neighbours; +} + +NodeStorage::NodeStorage(CPathsInfo & pathsInfo, const CGHeroInstance * hero) + :out(pathsInfo) +{ + out.hero = hero; + out.hpos = hero->visitablePos(); +} + +void NodeStorage::resetTile(const int3 & tile, const EPathfindingLayer & layer, EPathAccessibility accessibility) +{ + getNode(tile, layer)->update(tile, layer, accessibility); +} + +std::vector NodeStorage::getInitialNodes() +{ + auto * initialNode = getNode(out.hpos, out.hero->boat ? out.hero->boat->layer : EPathfindingLayer::LAND); + + initialNode->turns = 0; + initialNode->moveRemains = out.hero->movement; + initialNode->setCost(0.0); + + if(!initialNode->coord.valid()) + { + initialNode->coord = out.hpos; + } + + return std::vector { initialNode }; +} + +void NodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) +{ + assert(destination.node != source.node->theNodeBefore); //two tiles can't point to each other + destination.node->setCost(destination.cost); + destination.node->moveRemains = destination.movementLeft; + destination.node->turns = destination.turn; + destination.node->theNodeBefore = source.node; + destination.node->action = destination.action; +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/pathfinder/NodeStorage.h b/lib/pathfinder/NodeStorage.h new file mode 100644 index 000000000..eddb23201 --- /dev/null +++ b/lib/pathfinder/NodeStorage.h @@ -0,0 +1,52 @@ +/* + * NodeStorage.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "INodeStorage.h" +#include "CGPathNode.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class DLL_LINKAGE NodeStorage : public INodeStorage +{ +private: + CPathsInfo & out; + + STRONG_INLINE + void resetTile(const int3 & tile, const EPathfindingLayer & layer, EPathAccessibility accessibility); + +public: + NodeStorage(CPathsInfo & pathsInfo, const CGHeroInstance * hero); + + STRONG_INLINE + CGPathNode * getNode(const int3 & coord, const EPathfindingLayer layer) + { + return out.getNode(coord, layer); + } + + void initialize(const PathfinderOptions & options, const CGameState * gs) override; + virtual ~NodeStorage() = default; + + virtual std::vector getInitialNodes() override; + + virtual std::vector calculateNeighbours( + const PathNodeInfo & source, + const PathfinderConfig * pathfinderConfig, + const CPathfinderHelper * pathfinderHelper) override; + + virtual std::vector calculateTeleportations( + const PathNodeInfo & source, + const PathfinderConfig * pathfinderConfig, + const CPathfinderHelper * pathfinderHelper) override; + + virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/pathfinder/PathfinderOptions.cpp b/lib/pathfinder/PathfinderOptions.cpp new file mode 100644 index 000000000..c473b9321 --- /dev/null +++ b/lib/pathfinder/PathfinderOptions.cpp @@ -0,0 +1,67 @@ +/* + * CPathfinder.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "PathfinderOptions.h" + +#include "../CConfigHandler.h" +#include "NodeStorage.h" +#include "PathfindingRules.h" +#include "CPathfinder.h" + +VCMI_LIB_NAMESPACE_BEGIN + +PathfinderOptions::PathfinderOptions() +{ + 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 = settings["pathfinder"]["teleports"]["castleGate"].Bool(); + + lightweightFlyingMode = settings["pathfinder"]["lightweightFlyingMode"].Bool(); + oneTurnSpecialLayersLimit = settings["pathfinder"]["oneTurnSpecialLayersLimit"].Bool(); + originalMovementRules = settings["pathfinder"]["originalMovementRules"].Bool(); +} + +PathfinderConfig::PathfinderConfig(std::shared_ptr nodeStorage, std::vector> rules): + nodeStorage(std::move(nodeStorage)), + rules(std::move(rules)) +{ +} + +std::vector> SingleHeroPathfinderConfig::buildRuleSet() +{ + return std::vector>{ + std::make_shared(), + std::make_shared(), + std::make_shared(), + std::make_shared(), + std::make_shared() + }; +} + +SingleHeroPathfinderConfig::~SingleHeroPathfinderConfig() = default; + +SingleHeroPathfinderConfig::SingleHeroPathfinderConfig(CPathsInfo & out, CGameState * gs, const CGHeroInstance * hero) + : PathfinderConfig(std::make_shared(out, hero), buildRuleSet()) +{ + pathfinderHelper = std::make_unique(gs, hero, options); +} + +CPathfinderHelper * SingleHeroPathfinderConfig::getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) +{ + return pathfinderHelper.get(); +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/pathfinder/PathfinderOptions.h b/lib/pathfinder/PathfinderOptions.h new file mode 100644 index 000000000..32859ac82 --- /dev/null +++ b/lib/pathfinder/PathfinderOptions.h @@ -0,0 +1,103 @@ +/* + * PathfinderOptions.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +VCMI_LIB_NAMESPACE_BEGIN + +class INodeStorage; +class IPathfindingRule; +class CPathfinderHelper; +class CGameState; +class CGHeroInstance; + +struct PathNodeInfo; +struct CPathsInfo; + +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 DLL_LINKAGE PathfinderConfig +{ +public: + std::shared_ptr nodeStorage; + std::vector> rules; + PathfinderOptions options; + + PathfinderConfig( + std::shared_ptr nodeStorage, + std::vector> rules); + virtual ~PathfinderConfig() = default; + + virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) = 0; +}; + +class DLL_LINKAGE SingleHeroPathfinderConfig : public PathfinderConfig +{ +private: + std::unique_ptr pathfinderHelper; + +public: + SingleHeroPathfinderConfig(CPathsInfo & out, CGameState * gs, const CGHeroInstance * hero); + virtual ~SingleHeroPathfinderConfig(); + + virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override; + + static std::vector> buildRuleSet(); +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/PathfinderUtil.h b/lib/pathfinder/PathfinderUtil.h similarity index 65% rename from lib/PathfinderUtil.h rename to lib/pathfinder/PathfinderUtil.h index dcb40306b..365f37a89 100644 --- a/lib/PathfinderUtil.h +++ b/lib/pathfinder/PathfinderUtil.h @@ -9,10 +9,11 @@ */ #pragma once -#include "TerrainHandler.h" -#include "mapObjects/CGObjectInstance.h" -#include "mapping/CMapDefines.h" -#include "CGameState.h" +#include "../TerrainHandler.h" +#include "../mapObjects/CGObjectInstance.h" +#include "../mapping/CMapDefines.h" +#include "../CGameState.h" +#include "CGPathNode.h" VCMI_LIB_NAMESPACE_BEGIN @@ -22,10 +23,10 @@ namespace PathfinderUtil using ELayer = EPathfindingLayer; template - CGPathNode::EAccessibility evaluateAccessibility(const int3 & pos, const TerrainTile & tinfo, FoW fow, const PlayerColor player, const CGameState * gs) + EPathAccessibility evaluateAccessibility(const int3 & pos, const TerrainTile & tinfo, FoW fow, const PlayerColor player, const CGameState * gs) { if(!(*fow)[pos.z][pos.x][pos.y]) - return CGPathNode::BLOCKED; + return EPathAccessibility::BLOCKED; switch(layer) { @@ -35,47 +36,47 @@ namespace PathfinderUtil { if(tinfo.visitableObjects.front()->ID == Obj::SANCTUARY && tinfo.visitableObjects.back()->ID == Obj::HERO && tinfo.visitableObjects.back()->tempOwner != player) //non-owned hero stands on Sanctuary { - return CGPathNode::BLOCKED; + return EPathAccessibility::BLOCKED; } else { for(const CGObjectInstance * obj : tinfo.visitableObjects) { if(obj->blockVisit) - return CGPathNode::BLOCKVIS; + return EPathAccessibility::BLOCKVIS; else if(obj->passableFor(player)) - return CGPathNode::ACCESSIBLE; + return EPathAccessibility::ACCESSIBLE; else if(obj->ID != Obj::EVENT) - return CGPathNode::VISITABLE; + return EPathAccessibility::VISITABLE; } } } else if(tinfo.blocked) { - return CGPathNode::BLOCKED; + return EPathAccessibility::BLOCKED; } else if(gs->guardingCreaturePosition(pos).valid()) { // Monster close by; blocked visit for battle - return CGPathNode::BLOCKVIS; + return EPathAccessibility::BLOCKVIS; } break; case ELayer::WATER: if(tinfo.blocked || tinfo.terType->isLand()) - return CGPathNode::BLOCKED; + return EPathAccessibility::BLOCKED; break; case ELayer::AIR: if(tinfo.blocked || tinfo.terType->isLand()) - return CGPathNode::FLYABLE; + return EPathAccessibility::FLYABLE; break; } - return CGPathNode::ACCESSIBLE; + return EPathAccessibility::ACCESSIBLE; } } diff --git a/lib/pathfinder/PathfindingRules.cpp b/lib/pathfinder/PathfindingRules.cpp new file mode 100644 index 000000000..1d3b2d772 --- /dev/null +++ b/lib/pathfinder/PathfindingRules.cpp @@ -0,0 +1,394 @@ +/* + * PathfindingRules.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "PathfindingRules.h" + +#include "CGPathNode.h" +#include "CPathfinder.h" +#include "INodeStorage.h" +#include "PathfinderOptions.h" + +#include "../mapObjects/CGHeroInstance.h" +#include "../mapObjects/MiscObjects.h" +#include "../mapping/CMapDefines.h" + +VCMI_LIB_NAMESPACE_BEGIN + +void MovementCostRule::process( + const PathNodeInfo & source, + CDestinationNodeInfo & destination, + const PathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const +{ + float costAtNextTile = destination.cost; + int turnAtNextTile = destination.turn; + int moveAtNextTile = destination.movementLeft; + int cost = pathfinderHelper->getMovementCost(source, destination, moveAtNextTile); + int remains = moveAtNextTile - cost; + int sourceLayerMaxMovePoints = pathfinderHelper->getMaxMovePoints(source.node->layer); + + if(remains < 0) + { + //occurs rarely, when hero with low movepoints tries to leave the road + costAtNextTile += static_cast(moveAtNextTile) / sourceLayerMaxMovePoints;//we spent all points of current turn + pathfinderHelper->updateTurnInfo(++turnAtNextTile); + + int destinationLayerMaxMovePoints = pathfinderHelper->getMaxMovePoints(destination.node->layer); + + moveAtNextTile = destinationLayerMaxMovePoints; + + cost = pathfinderHelper->getMovementCost(source, destination, moveAtNextTile); //cost must be updated, movement points changed :( + remains = moveAtNextTile - cost; + } + + if(destination.action == EPathNodeAction::EMBARK || destination.action == EPathNodeAction::DISEMBARK) + { + /// FREE_SHIP_BOARDING bonus only remove additional penalty + /// land <-> sail transition still cost movement points as normal movement + remains = pathfinderHelper->movementPointsAfterEmbark(moveAtNextTile, cost, (destination.action == EPathNodeAction::DISEMBARK)); + cost = moveAtNextTile - remains; + } + + costAtNextTile += static_cast(cost) / sourceLayerMaxMovePoints; + + destination.cost = costAtNextTile; + destination.turn = turnAtNextTile; + destination.movementLeft = remains; + + if(destination.isBetterWay() && + ((source.node->turns == turnAtNextTile && remains) || pathfinderHelper->passOneTurnLimitCheck(source))) + { + pathfinderConfig->nodeStorage->commit(destination, source); + + return; + } + + destination.blocked = true; +} + +void PathfinderBlockingRule::process( + const PathNodeInfo & source, + CDestinationNodeInfo & destination, + const PathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const +{ + auto blockingReason = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper); + + destination.blocked = blockingReason != BlockingReason::NONE; +} + +void DestinationActionRule::process( + const PathNodeInfo & source, + CDestinationNodeInfo & destination, + const PathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const +{ + if(destination.action != EPathNodeAction::UNKNOWN) + { +#ifdef VCMI_TRACE_PATHFINDER + logAi->trace("Accepted precalculated action at %s", destination.coord.toString()); +#endif + return; + } + + EPathNodeAction action = EPathNodeAction::NORMAL; + const auto * hero = pathfinderHelper->hero; + + switch(destination.node->layer) + { + case EPathfindingLayer::LAND: + if(source.node->layer == EPathfindingLayer::SAIL) + { + // TODO: Handle dismebark into guarded areaa + action = EPathNodeAction::DISEMBARK; + break; + } + + /// don't break - next case shared for both land and sail layers + [[fallthrough]]; + + case EPathfindingLayer::SAIL: + if(destination.isNodeObjectVisitable()) + { + auto objRel = destination.objectRelations; + + if(destination.nodeObject->ID == Obj::BOAT) + action = EPathNodeAction::EMBARK; + else if(destination.nodeHero) + { + if(destination.heroRelations == PlayerRelations::ENEMIES) + action = EPathNodeAction::BATTLE; + else + action = EPathNodeAction::BLOCKING_VISIT; + } + else if(destination.nodeObject->ID == Obj::TOWN) + { + if(destination.nodeObject->passableFor(hero->tempOwner)) + action = EPathNodeAction::VISIT; + else if(objRel == PlayerRelations::ENEMIES) + action = EPathNodeAction::BATTLE; + } + else if(destination.nodeObject->ID == Obj::GARRISON || destination.nodeObject->ID == Obj::GARRISON2) + { + if(destination.nodeObject->passableFor(hero->tempOwner)) + { + if(destination.guarded) + action = EPathNodeAction::BATTLE; + } + else if(objRel == PlayerRelations::ENEMIES) + action = EPathNodeAction::BATTLE; + } + else if(destination.nodeObject->ID == Obj::BORDER_GATE) + { + if(destination.nodeObject->passableFor(hero->tempOwner)) + { + if(destination.guarded) + action = EPathNodeAction::BATTLE; + } + else + action = EPathNodeAction::BLOCKING_VISIT; + } + else if(destination.isGuardianTile) + action = EPathNodeAction::BATTLE; + else if(destination.nodeObject->blockVisit && !(pathfinderConfig->options.useCastleGate && destination.nodeObject->ID == Obj::TOWN)) + action = EPathNodeAction::BLOCKING_VISIT; + + if(action == EPathNodeAction::NORMAL) + { + if(destination.guarded) + action = EPathNodeAction::BATTLE; + else + action = EPathNodeAction::VISIT; + } + } + else if(destination.guarded) + action = EPathNodeAction::BATTLE; + + break; + } + + destination.action = action; +} + +void MovementAfterDestinationRule::process( + const PathNodeInfo & source, + CDestinationNodeInfo & destination, + const PathfinderConfig * config, + CPathfinderHelper * pathfinderHelper) const +{ + auto blocker = getBlockingReason(source, destination, config, pathfinderHelper); + + if(blocker == BlockingReason::DESTINATION_GUARDED && destination.action == EPathNodeAction::BATTLE) + { + return; // allow bypass guarded tile but only in direction of guard, a bit UI related thing + } + + destination.blocked = blocker != BlockingReason::NONE; +} + + +PathfinderBlockingRule::BlockingReason MovementAfterDestinationRule::getBlockingReason( + const PathNodeInfo & source, + const CDestinationNodeInfo & destination, + const PathfinderConfig * config, + const CPathfinderHelper * pathfinderHelper) const +{ + 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 doesn't fly + case EPathNodeAction::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 + const auto * objTeleport = dynamic_cast(destination.nodeObject); + if(pathfinderHelper->isAllowedTeleportEntrance(objTeleport)) + { + /// For now we'll always allow transit over teleporters + /// Transit over whirlpools only allowed when hero is protected + return BlockingReason::NONE; + } + 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 + return BlockingReason::NONE; + } + + return BlockingReason::DESTINATION_VISIT; + } + + case EPathNodeAction::BLOCKING_VISIT: + return destination.guarded + ? BlockingReason::DESTINATION_GUARDED + : BlockingReason::DESTINATION_BLOCKVIS; + + case EPathNodeAction::NORMAL: + return BlockingReason::NONE; + + case EPathNodeAction::EMBARK: + if(pathfinderHelper->options.useEmbarkAndDisembark) + return BlockingReason::NONE; + + return BlockingReason::DESTINATION_BLOCKED; + + case EPathNodeAction::DISEMBARK: + if(pathfinderHelper->options.useEmbarkAndDisembark) + return destination.guarded ? BlockingReason::DESTINATION_GUARDED : BlockingReason::NONE; + + return BlockingReason::DESTINATION_BLOCKED; + + case EPathNodeAction::BATTLE: + /// Movement after BATTLE action only possible from guarded tile to guardian tile + if(destination.guarded) + return BlockingReason::DESTINATION_GUARDED; + + break; + } + + return BlockingReason::DESTINATION_BLOCKED; +} + + +PathfinderBlockingRule::BlockingReason MovementToDestinationRule::getBlockingReason( + const PathNodeInfo & source, + const CDestinationNodeInfo & destination, + const PathfinderConfig * pathfinderConfig, + const CPathfinderHelper * pathfinderHelper) const +{ + + if(destination.node->accessible == EPathAccessibility::BLOCKED) + return BlockingReason::DESTINATION_BLOCKED; + + switch(destination.node->layer) + { + case EPathfindingLayer::LAND: + if(!pathfinderHelper->canMoveBetween(source.coord, destination.coord)) + return BlockingReason::DESTINATION_BLOCKED; + + if(source.guarded) + { + if(!(pathfinderConfig->options.originalMovementRules && source.node->layer == EPathfindingLayer::AIR) && + !destination.isGuardianTile) // Can step into tile of guard + { + return BlockingReason::SOURCE_GUARDED; + } + } + + break; + + case EPathfindingLayer::SAIL: + if(!pathfinderHelper->canMoveBetween(source.coord, destination.coord)) + return BlockingReason::DESTINATION_BLOCKED; + + if(source.guarded) + { + // Hero embarked a boat standing on a guarded tile -> we must allow to move away from that tile + if(source.node->action != EPathNodeAction::EMBARK && !destination.isGuardianTile) + return BlockingReason::SOURCE_GUARDED; + } + + if(source.node->layer == EPathfindingLayer::LAND) + { + if(!destination.isNodeObjectVisitable()) + return BlockingReason::DESTINATION_BLOCKED; + + if(destination.nodeObject->ID != Obj::BOAT && !destination.nodeHero) + return BlockingReason::DESTINATION_BLOCKED; + } + else if(destination.isNodeObjectVisitable() && destination.nodeObject->ID == Obj::BOAT) + { + /// Hero in boat can't visit empty boats + return BlockingReason::DESTINATION_BLOCKED; + } + + break; + + case EPathfindingLayer::WATER: + if(!pathfinderHelper->canMoveBetween(source.coord, destination.coord) + || destination.node->accessible != EPathAccessibility::ACCESSIBLE) + { + return BlockingReason::DESTINATION_BLOCKED; + } + + if(destination.guarded) + return BlockingReason::DESTINATION_BLOCKED; + + break; + } + + return BlockingReason::NONE; +} + +void LayerTransitionRule::process( + const PathNodeInfo & source, + CDestinationNodeInfo & destination, + const PathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const +{ + if(source.node->layer == destination.node->layer) + return; + + switch(source.node->layer) + { + case EPathfindingLayer::LAND: + if(destination.node->layer == EPathfindingLayer::SAIL) + { + /// Cannot enter empty water tile from land -> it has to be visitable + if(destination.node->accessible == EPathAccessibility::ACCESSIBLE) + destination.blocked = true; + } + + break; + + case EPathfindingLayer::SAIL: + //tile must be accessible -> exception: unblocked blockvis tiles -> clear but guarded by nearby monster coast + if((destination.node->accessible != EPathAccessibility::ACCESSIBLE && (destination.node->accessible != EPathAccessibility::BLOCKVIS || destination.tile->blocked)) + || destination.tile->visitable) //TODO: passableness problem -> town says it's passable (thus accessible) but we obviously can't disembark onto town gate + { + destination.blocked = true; + } + + break; + + case EPathfindingLayer::AIR: + if(pathfinderConfig->options.originalMovementRules) + { + if((source.node->accessible != EPathAccessibility::ACCESSIBLE && + source.node->accessible != EPathAccessibility::VISITABLE) && + (destination.node->accessible != EPathAccessibility::VISITABLE && + destination.node->accessible != EPathAccessibility::ACCESSIBLE)) + { + destination.blocked = true; + } + } + else if(destination.node->accessible != EPathAccessibility::ACCESSIBLE) + { + /// Hero that fly can only land on accessible tiles + if(destination.nodeObject) + destination.blocked = true; + } + + break; + + case EPathfindingLayer::WATER: + if(destination.node->accessible != EPathAccessibility::ACCESSIBLE && destination.node->accessible != EPathAccessibility::VISITABLE) + { + /// Hero that walking on water can transit to accessible and visitable tiles + /// Though hero can't interact with blocking visit objects while standing on water + destination.blocked = true; + } + + break; + } +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/pathfinder/PathfindingRules.h b/lib/pathfinder/PathfindingRules.h new file mode 100644 index 000000000..6f7a9e2ae --- /dev/null +++ b/lib/pathfinder/PathfindingRules.h @@ -0,0 +1,116 @@ +/* + * PathfindingRules.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +VCMI_LIB_NAMESPACE_BEGIN + +struct CDestinationNodeInfo; +struct PathNodeInfo; + +class CPathfinderHelper; +class PathfinderConfig; + +class IPathfindingRule +{ +public: + virtual ~IPathfindingRule() = default; + virtual void process( + const PathNodeInfo & source, + CDestinationNodeInfo & destination, + const PathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const = 0; +}; + +class DLL_LINKAGE MovementCostRule : public IPathfindingRule +{ +public: + void process( + const PathNodeInfo & source, + CDestinationNodeInfo & destination, + const PathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const override; +}; + +class DLL_LINKAGE LayerTransitionRule : public IPathfindingRule +{ +public: + void process( + const PathNodeInfo & source, + CDestinationNodeInfo & destination, + const PathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const override; +}; + +class DLL_LINKAGE DestinationActionRule : public IPathfindingRule +{ +public: + void process( + const PathNodeInfo & source, + CDestinationNodeInfo & destination, + const PathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const override; +}; + +class DLL_LINKAGE PathfinderBlockingRule : public IPathfindingRule +{ +public: + void process( + const PathNodeInfo & source, + CDestinationNodeInfo & destination, + const PathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const override; + +protected: + enum class BlockingReason + { + NONE = 0, + SOURCE_GUARDED = 1, + DESTINATION_GUARDED = 2, + SOURCE_BLOCKED = 3, + DESTINATION_BLOCKED = 4, + DESTINATION_BLOCKVIS = 5, + DESTINATION_VISIT = 6 + }; + + virtual BlockingReason getBlockingReason( + const PathNodeInfo & source, + const CDestinationNodeInfo & destination, + const PathfinderConfig * pathfinderConfig, + const CPathfinderHelper * pathfinderHelper) const = 0; +}; + +class DLL_LINKAGE MovementAfterDestinationRule : public PathfinderBlockingRule +{ +public: + void process( + const PathNodeInfo & source, + CDestinationNodeInfo & destination, + const PathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const override; + +protected: + BlockingReason getBlockingReason( + const PathNodeInfo & source, + const CDestinationNodeInfo & destination, + const PathfinderConfig * pathfinderConfig, + const CPathfinderHelper * pathfinderHelper) const override; +}; + +class DLL_LINKAGE MovementToDestinationRule : public PathfinderBlockingRule +{ +protected: + BlockingReason getBlockingReason( + const PathNodeInfo & source, + const CDestinationNodeInfo & destination, + const PathfinderConfig * pathfinderConfig, + const CPathfinderHelper * pathfinderHelper) const override; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/pathfinder/TurnInfo.cpp b/lib/pathfinder/TurnInfo.cpp new file mode 100644 index 000000000..fc9cdf2ed --- /dev/null +++ b/lib/pathfinder/TurnInfo.cpp @@ -0,0 +1,140 @@ +/* + * TurnInfo.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "TurnInfo.h" + +#include "../TerrainHandler.h" +#include "../VCMI_Lib.h" +#include "../bonuses/BonusList.h" +#include "../mapObjects/CGHeroInstance.h" +#include "../mapObjects/MiscObjects.h" + +VCMI_LIB_NAMESPACE_BEGIN + +TurnInfo::BonusCache::BonusCache(const TConstBonusListPtr & bl) +{ + for(const auto & terrain : VLC->terrainTypeHandler->objects) + { + noTerrainPenalty.push_back(static_cast( + bl->getFirst(Selector::type()(BonusType::NO_TERRAIN_PENALTY).And(Selector::subtype()(terrain->getIndex()))))); + } + + freeShipBoarding = static_cast(bl->getFirst(Selector::type()(BonusType::FREE_SHIP_BOARDING))); + flyingMovement = static_cast(bl->getFirst(Selector::type()(BonusType::FLYING_MOVEMENT))); + flyingMovementVal = bl->valOfBonuses(Selector::type()(BonusType::FLYING_MOVEMENT)); + waterWalking = static_cast(bl->getFirst(Selector::type()(BonusType::WATER_WALKING))); + waterWalkingVal = bl->valOfBonuses(Selector::type()(BonusType::WATER_WALKING)); + pathfindingVal = bl->valOfBonuses(Selector::type()(BonusType::ROUGH_TERRAIN_DISCOUNT)); +} + +TurnInfo::TurnInfo(const CGHeroInstance * Hero, const int turn): + hero(Hero), + maxMovePointsLand(-1), + maxMovePointsWater(-1), + turn(turn) +{ + bonuses = hero->getAllBonuses(Selector::days(turn), Selector::all, nullptr, ""); + bonusCache = std::make_unique(bonuses); + nativeTerrain = hero->getNativeTerrain(); +} + +bool TurnInfo::isLayerAvailable(const EPathfindingLayer & layer) const +{ + switch(layer) + { + case EPathfindingLayer::AIR: + if(hero && hero->boat && hero->boat->layer == EPathfindingLayer::AIR) + break; + + if(!hasBonusOfType(BonusType::FLYING_MOVEMENT)) + return false; + + break; + + case EPathfindingLayer::WATER: + if(hero && hero->boat && hero->boat->layer == EPathfindingLayer::WATER) + break; + + if(!hasBonusOfType(BonusType::WATER_WALKING)) + return false; + + break; + } + + return true; +} + +bool TurnInfo::hasBonusOfType(BonusType type, int subtype) const +{ + switch(type) + { + case BonusType::FREE_SHIP_BOARDING: + return bonusCache->freeShipBoarding; + case BonusType::FLYING_MOVEMENT: + return bonusCache->flyingMovement; + case BonusType::WATER_WALKING: + return bonusCache->waterWalking; + case BonusType::NO_TERRAIN_PENALTY: + return bonusCache->noTerrainPenalty[subtype]; + } + + return static_cast( + bonuses->getFirst(Selector::type()(type).And(Selector::subtype()(subtype)))); +} + +int TurnInfo::valOfBonuses(BonusType type, int subtype) const +{ + switch(type) + { + case BonusType::FLYING_MOVEMENT: + return bonusCache->flyingMovementVal; + case BonusType::WATER_WALKING: + return bonusCache->waterWalkingVal; + case BonusType::ROUGH_TERRAIN_DISCOUNT: + return bonusCache->pathfindingVal; + } + + return bonuses->valOfBonuses(Selector::type()(type).And(Selector::subtype()(subtype))); +} + +int TurnInfo::getMaxMovePoints(const EPathfindingLayer & layer) const +{ + if(maxMovePointsLand == -1) + maxMovePointsLand = hero->maxMovePointsCached(true, this); + if(maxMovePointsWater == -1) + maxMovePointsWater = hero->maxMovePointsCached(false, this); + + return layer == EPathfindingLayer::SAIL ? maxMovePointsWater : maxMovePointsLand; +} + +void TurnInfo::updateHeroBonuses(BonusType type, const CSelector& sel) const +{ + switch(type) + { + case BonusType::FREE_SHIP_BOARDING: + bonusCache->freeShipBoarding = static_cast(bonuses->getFirst(Selector::type()(BonusType::FREE_SHIP_BOARDING))); + break; + case BonusType::FLYING_MOVEMENT: + bonusCache->flyingMovement = static_cast(bonuses->getFirst(Selector::type()(BonusType::FLYING_MOVEMENT))); + bonusCache->flyingMovementVal = bonuses->valOfBonuses(Selector::type()(BonusType::FLYING_MOVEMENT)); + break; + case BonusType::WATER_WALKING: + bonusCache->waterWalking = static_cast(bonuses->getFirst(Selector::type()(BonusType::WATER_WALKING))); + bonusCache->waterWalkingVal = bonuses->valOfBonuses(Selector::type()(BonusType::WATER_WALKING)); + break; + case BonusType::ROUGH_TERRAIN_DISCOUNT: + bonusCache->pathfindingVal = bonuses->valOfBonuses(Selector::type()(BonusType::ROUGH_TERRAIN_DISCOUNT)); + break; + default: + bonuses = hero->getAllBonuses(Selector::days(turn), Selector::all, nullptr, ""); + } +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/pathfinder/TurnInfo.h b/lib/pathfinder/TurnInfo.h new file mode 100644 index 000000000..aa7d77b40 --- /dev/null +++ b/lib/pathfinder/TurnInfo.h @@ -0,0 +1,51 @@ +/* + * TurnInfo.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../bonuses/Bonus.h" +#include "../GameConstants.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class CGHeroInstance; + +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; + int pathfindingVal; + + BonusCache(const TConstBonusListPtr & bonusList); + }; + std::unique_ptr bonusCache; + + const CGHeroInstance * hero; + mutable TConstBonusListPtr bonuses; + mutable int maxMovePointsLand; + mutable int maxMovePointsWater; + TerrainId nativeTerrain; + int turn; + + TurnInfo(const CGHeroInstance * Hero, const int Turn = 0); + bool isLayerAvailable(const EPathfindingLayer & layer) const; + bool hasBonusOfType(const BonusType type, const int subtype = -1) const; + int valOfBonuses(const BonusType type, const int subtype = -1) const; + void updateHeroBonuses(BonusType type, const CSelector& sel) const; + int getMaxMovePoints(const EPathfindingLayer & layer) const; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index fc416b4bf..42f51e0b5 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -19,6 +19,9 @@ #include "../lib/CArtHandler.h" #include "../lib/CBuildingHandler.h" #include "../lib/CHeroHandler.h" +#include "../lib/pathfinder/CPathfinder.h" +#include "../lib/pathfinder/PathfinderOptions.h" +#include "../lib/pathfinder/TurnInfo.h" #include "../lib/spells/AbilityCaster.h" #include "../lib/spells/BonusCaster.h" #include "../lib/spells/CSpellHandler.h"