Browse Source

Merge pull request #2246 from IvanSavenko/pathfinding_reorganization

Pathfinding code reorganization
Ivan Savenko 2 years ago
parent
commit
ccc85ff4eb
93 changed files with 2457 additions and 2189 deletions
  1. 4 4
      AI/Nullkiller/AIGateway.cpp
  2. 0 1
      AI/Nullkiller/AIUtility.h
  3. 0 1
      AI/Nullkiller/Behaviors/BuildingBehavior.cpp
  4. 0 1
      AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp
  5. 0 1
      AI/Nullkiller/Behaviors/DefenceBehavior.cpp
  6. 0 1
      AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp
  7. 0 1
      AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp
  8. 0 1
      AI/Nullkiller/Behaviors/StartupBehavior.cpp
  9. 0 1
      AI/Nullkiller/Engine/PriorityEvaluator.cpp
  10. 0 1
      AI/Nullkiller/Goals/AbstractGoal.cpp
  11. 0 1
      AI/Nullkiller/Goals/AdventureSpellCast.cpp
  12. 0 1
      AI/Nullkiller/Goals/BuildBoat.cpp
  13. 0 1
      AI/Nullkiller/Goals/BuildThis.cpp
  14. 0 1
      AI/Nullkiller/Goals/CompleteQuest.cpp
  15. 0 1
      AI/Nullkiller/Goals/Composition.cpp
  16. 0 1
      AI/Nullkiller/Goals/DismissHero.cpp
  17. 0 1
      AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp
  18. 3 4
      AI/Nullkiller/Goals/ExecuteHeroChain.cpp
  19. 0 1
      AI/Nullkiller/Goals/RecruitHero.cpp
  20. 0 1
      AI/Nullkiller/Goals/SaveResources.cpp
  21. 24 22
      AI/Nullkiller/Pathfinding/AINodeStorage.cpp
  22. 6 5
      AI/Nullkiller/Pathfinding/AINodeStorage.h
  23. 4 0
      AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp
  24. 3 0
      AI/Nullkiller/Pathfinding/AIPathfinderConfig.h
  25. 5 0
      AI/Nullkiller/Pathfinding/Actions/SpecialAction.h
  26. 1 0
      AI/Nullkiller/Pathfinding/Actors.cpp
  27. 1 2
      AI/Nullkiller/Pathfinding/Actors.h
  28. 2 2
      AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp
  29. 1 0
      AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.h
  30. 1 0
      AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.h
  31. 1 1
      AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp
  32. 1 0
      AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.h
  33. 4 2
      AI/Nullkiller/Pathfinding/Rules/AIPreviousNodeRule.cpp
  34. 1 0
      AI/Nullkiller/Pathfinding/Rules/AIPreviousNodeRule.h
  35. 0 1
      AI/VCAI/AIUtility.h
  36. 0 1
      AI/VCAI/Goals/AbstractGoal.cpp
  37. 0 1
      AI/VCAI/Goals/AdventureSpellCast.cpp
  38. 0 1
      AI/VCAI/Goals/Build.cpp
  39. 0 1
      AI/VCAI/Goals/BuildBoat.cpp
  40. 0 1
      AI/VCAI/Goals/BuildThis.cpp
  41. 0 1
      AI/VCAI/Goals/CollectRes.cpp
  42. 0 1
      AI/VCAI/Goals/CompleteQuest.cpp
  43. 0 1
      AI/VCAI/Goals/Conquer.cpp
  44. 0 1
      AI/VCAI/Goals/Explore.cpp
  45. 0 1
      AI/VCAI/Goals/GatherArmy.cpp
  46. 0 1
      AI/VCAI/Goals/GatherTroops.cpp
  47. 0 1
      AI/VCAI/Goals/RecruitHero.cpp
  48. 0 1
      AI/VCAI/Goals/VisitObj.cpp
  49. 0 1
      AI/VCAI/Goals/VisitTile.cpp
  50. 0 1
      AI/VCAI/Goals/Win.cpp
  51. 8 6
      AI/VCAI/Pathfinding/AINodeStorage.cpp
  52. 3 2
      AI/VCAI/Pathfinding/AINodeStorage.h
  53. 4 0
      AI/VCAI/Pathfinding/AIPathfinderConfig.cpp
  54. 3 0
      AI/VCAI/Pathfinding/AIPathfinderConfig.h
  55. 6 1
      AI/VCAI/Pathfinding/Actions/ISpecialAction.h
  56. 2 2
      AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.cpp
  57. 1 0
      AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.h
  58. 1 0
      AI/VCAI/Pathfinding/Rules/AIMovementAfterDestinationRule.h
  59. 1 1
      AI/VCAI/Pathfinding/Rules/AIMovementToDestinationRule.cpp
  60. 1 0
      AI/VCAI/Pathfinding/Rules/AIMovementToDestinationRule.h
  61. 1 1
      AI/VCAI/Pathfinding/Rules/AIPreviousNodeRule.cpp
  62. 1 0
      AI/VCAI/Pathfinding/Rules/AIPreviousNodeRule.h
  63. 4 4
      AI/VCAI/VCAI.cpp
  64. 8 8
      client/CPlayerInterface.cpp
  65. 1 0
      client/Client.cpp
  66. 0 1
      client/Client.h
  67. 1 1
      client/PlayerLocalState.cpp
  68. 10 10
      client/adventureMap/AdventureMapInterface.cpp
  69. 1 1
      client/adventureMap/AdventureMapShortcuts.cpp
  70. 2 2
      client/mapView/MapRenderer.cpp
  71. 1 1
      client/mapView/MapRendererContext.cpp
  72. 1 1
      client/mapView/MapViewController.cpp
  73. 16 3
      cmake_modules/VCMI_lib.cmake
  74. 2 0
      lib/CGameState.cpp
  75. 0 1
      lib/CGameState.h
  76. 0 1434
      lib/CPathfinder.cpp
  77. 0 618
      lib/CPathfinder.h
  78. 1 0
      lib/mapObjects/CGHeroInstance.cpp
  79. 161 0
      lib/pathfinder/CGPathNode.cpp
  80. 222 0
      lib/pathfinder/CGPathNode.h
  81. 668 0
      lib/pathfinder/CPathfinder.cpp
  82. 126 0
      lib/pathfinder/CPathfinder.h
  83. 49 0
      lib/pathfinder/INodeStorage.h
  84. 147 0
      lib/pathfinder/NodeStorage.cpp
  85. 52 0
      lib/pathfinder/NodeStorage.h
  86. 67 0
      lib/pathfinder/PathfinderOptions.cpp
  87. 103 0
      lib/pathfinder/PathfinderOptions.h
  88. 16 15
      lib/pathfinder/PathfinderUtil.h
  89. 394 0
      lib/pathfinder/PathfindingRules.cpp
  90. 116 0
      lib/pathfinder/PathfindingRules.h
  91. 140 0
      lib/pathfinder/TurnInfo.cpp
  92. 51 0
      lib/pathfinder/TurnInfo.h
  93. 3 0
      server/CGameHandler.cpp

+ 4 - 4
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

+ 0 - 1
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 <chrono>

+ 0 - 1
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

+ 0 - 1
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
 {

+ 0 - 1
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
 {

+ 0 - 1
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
 {

+ 0 - 1
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
 {

+ 0 - 1
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

+ 0 - 1
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"

+ 0 - 1
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

+ 0 - 1
AI/Nullkiller/Goals/AdventureSpellCast.cpp

@@ -10,7 +10,6 @@
 #include "StdInc.h"
 #include "AdventureSpellCast.h"
 #include "../AIGateway.h"
-#include "../../../lib/CPathfinder.h"
 
 namespace NKAI
 {

+ 0 - 1
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

+ 0 - 1
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"
 
 

+ 0 - 1
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"
 

+ 0 - 1
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"
 
 

+ 0 - 1
AI/Nullkiller/Goals/DismissHero.cpp

@@ -10,7 +10,6 @@
 #include "StdInc.h"
 #include "DismissHero.h"
 #include "../AIGateway.h"
-#include "../../../lib/CPathfinder.h"
 
 namespace NKAI
 {

+ 0 - 1
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

+ 3 - 4
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(

+ 0 - 1
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"
 
 

+ 0 - 1
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

+ 24 - 22
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<CGPathNode *> 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<CGPathNode *> 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<CGPathNode *> 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<ExchangeCandidate> & 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<ExchangeCandidate>
 
 		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<AIPath> 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;
 		}

+ 6 - 5
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<const SpecialAction> 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
 	{

+ 4 - 0
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);

+ 3 - 0
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<AINodeStorage> nodeStorage);
 
+		~AIPathfinderConfig();
+
 		virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override;
 	};
 }

+ 5 - 0
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
 {
 

+ 1 - 0
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;

+ 1 - 2
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;

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

+ 1 - 0
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
 {

+ 1 - 0
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
 {

+ 1 - 1
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;

+ 1 - 0
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
 {

+ 4 - 2
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))

+ 1 - 0
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
 {

+ 0 - 1
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;

+ 0 - 1
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<CCallback> cb;

+ 0 - 1
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<CCallback> cb;
 extern boost::thread_specific_ptr<VCAI> ai;

+ 0 - 1
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"
 
 

+ 0 - 1
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<CCallback> cb;
 extern boost::thread_specific_ptr<VCAI> ai;

+ 0 - 1
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"
 
 

+ 0 - 1
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"
 
 

+ 0 - 1
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<CCallback> cb;
 extern boost::thread_specific_ptr<VCAI> ai;

+ 0 - 1
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"
 
 

+ 0 - 1
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"
 

+ 0 - 1
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"
 
 

+ 0 - 1
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"
 
 

+ 0 - 1
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"
 
 

+ 0 - 1
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"
 
 

+ 0 - 1
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"
 
 

+ 0 - 1
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"
 
 

+ 8 - 6
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<CGPathNode *> 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<CGPathNode *> 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<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand) const
@@ -332,7 +334,7 @@ std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand)
 
 	for(const AIPathNode & node : chains)
 	{
-		if(node.action == CGPathNode::ENodeAction::UNKNOWN)
+		if(node.action == EPathNodeAction::UNKNOWN)
 		{
 			continue;
 		}

+ 3 - 2
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<FuzzyHelper> 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.

+ 4 - 0
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<std::shared_ptr<IPathfindingRule>> makeRuleset(
@@ -41,6 +43,8 @@ namespace AIPathfinding
 	{
 	}
 
+	AIPathfinderConfig::~AIPathfinderConfig() = default;
+
 	CPathfinderHelper * AIPathfinderConfig::getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs)
 	{
 		if(!helper)

+ 3 - 0
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<AINodeStorage> nodeStorage);
 
+		~AIPathfinderConfig();
+
 		virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override;
 	};
 }

+ 6 - 1
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
 	{
 	}
-};
+};

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

+ 1 - 0
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
 {

+ 1 - 0
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
 {

+ 1 - 1
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;

+ 1 - 0
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
 {

+ 1 - 1
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;

+ 1 - 0
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
 {

+ 4 - 4
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
 		}

+ 8 - 8
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);

+ 1 - 0
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"

+ 0 - 1
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
 

+ 1 - 1
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"
 

+ 10 - 10
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<AdventureMapInterface> 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;
 

+ 1 - 1
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)

+ 2 - 2
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<size_t>::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);

+ 1 - 1
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)

+ 1 - 1
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)

+ 16 - 3
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

+ 2 - 0
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"

+ 0 - 1
lib/CGameState.h

@@ -20,7 +20,6 @@
 #include "int3.h"
 #include "CRandomGenerator.h"
 #include "CGameStateFwd.h"
-#include "CPathfinder.h"
 
 namespace boost
 {

+ 0 - 1434
lib/CPathfinder.cpp

@@ -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<const CGameInfoCallback *>(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<ELayer::SAIL>(pos, tile, fow, player, gs));
-					if(useFlying)
-						resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
-					if(useWaterWalking)
-						resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility<ELayer::WATER>(pos, tile, fow, player, gs));
-				}
-				if(tile.terType->isLand())
-				{
-					resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility<ELayer::LAND>(pos, tile, fow, player, gs));
-					if(useFlying)
-						resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
-				}
-			}
-		}
-	}
-}
-
-std::vector<CGPathNode *> NodeStorage::calculateNeighbours(
-	const PathNodeInfo & source,
-	const PathfinderConfig * pathfinderConfig,
-	const CPathfinderHelper * pathfinderHelper)
-{
-	std::vector<CGPathNode *> 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<CGPathNode *> NodeStorage::calculateTeleportations(
-	const PathNodeInfo & source,
-	const PathfinderConfig * pathfinderConfig,
-	const CPathfinderHelper * pathfinderHelper)
-{
-	std::vector<CGPathNode *> 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<int3> CPathfinderHelper::getNeighbourTiles(const PathNodeInfo & source) const
-{
-	std::vector<int3> 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<CGPathNode *> 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<CGPathNode *> { 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<float>(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<float>(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<INodeStorage> nodeStorage, std::vector<std::shared_ptr<IPathfindingRule>> rules):
-	nodeStorage(std::move(nodeStorage)),
-	rules(std::move(rules))
-{
-}
-
-std::vector<std::shared_ptr<IPathfindingRule>> SingleHeroPathfinderConfig::buildRuleSet()
-{
-	return std::vector<std::shared_ptr<IPathfindingRule>>{
-		std::make_shared<LayerTransitionRule>(),
-			std::make_shared<DestinationActionRule>(),
-			std::make_shared<MovementToDestinationRule>(),
-			std::make_shared<MovementCostRule>(),
-			std::make_shared<MovementAfterDestinationRule>()
-	};
-}
-
-SingleHeroPathfinderConfig::SingleHeroPathfinderConfig(CPathsInfo & out, CGameState * gs, const CGHeroInstance * hero)
-	: PathfinderConfig(std::make_shared<NodeStorage>(out, hero), buildRuleSet())
-{
-	pathfinderHelper = std::make_unique<CPathfinderHelper>(gs, hero, options);
-}
-
-CPathfinderHelper * SingleHeroPathfinderConfig::getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs)
-{
-	return pathfinderHelper.get();
-}
-
-CPathfinder::CPathfinder(CGameState * _gs, std::shared_ptr<PathfinderConfig> 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<CGPathNode *> 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<int3> CPathfinderHelper::getAllowedTeleportChannelExits(const TeleportChannelID & channelID) const
-{
-	std::vector<int3> allowedExits;
-
-	for(const auto & objId : getTeleportChannelExits(channelID, hero->tempOwner))
-	{
-		const auto * obj = getObj(objId);
-		if(dynamic_cast<const CGWhirlpool *>(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<int3> CPathfinderHelper::getCastleGates(const PathNodeInfo & source) const
-{
-	std::vector<int3> 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<int3> CPathfinderHelper::getTeleportExits(const PathNodeInfo & source) const
-{
-	std::vector<int3> teleportationExits;
-
-	const auto * objTeleport = dynamic_cast<const CGTeleport *>(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<const CGTeleport *>(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<PlayerColor>(), 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<const CGWhirlpool *>(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<bool>(
-				bl->getFirst(Selector::type()(BonusType::NO_TERRAIN_PENALTY).And(Selector::subtype()(terrain->getIndex())))));
-	}
-
-	freeShipBoarding = static_cast<bool>(bl->getFirst(Selector::type()(BonusType::FREE_SHIP_BOARDING)));
-	flyingMovement = static_cast<bool>(bl->getFirst(Selector::type()(BonusType::FLYING_MOVEMENT)));
-	flyingMovementVal = bl->valOfBonuses(Selector::type()(BonusType::FLYING_MOVEMENT));
-	waterWalking = static_cast<bool>(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<BonusCache>(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<bool>(
-			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<bool>(bonuses->getFirst(Selector::type()(BonusType::FREE_SHIP_BOARDING)));
-		break;
-	case BonusType::FLYING_MOVEMENT:
-		bonusCache->flyingMovement = static_cast<bool>(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<bool>(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<PlayerColor>()),
-	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<int3> & 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<bool>(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<bool>(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<int>(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<int>(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<int>(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<int>(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<int3> 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<const CGHeroInstance *>(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

+ 0 - 618
lib/CPathfinder.h

@@ -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 <boost/heap/fibonacci_heap.hpp>
-
-VCMI_LIB_NAMESPACE_BEGIN
-
-
-class CGHeroInstance;
-class CGObjectInstance;
-struct TerrainTile;
-class CPathfinderHelper;
-class CMap;
-class CGWhirlpool;
-class CPathfinderHelper;
-class CPathfinder;
-class PathfinderConfig;
-
-
-template<typename N>
-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<float>::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<CGPathNode *, boost::heap::compare<NodeComparer<CGPathNode>>>;
-
-	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<CGPathNode> 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<CGPathNode, 4> 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<CGPathNode *> getInitialNodes() = 0;
-
-	virtual std::vector<CGPathNode *> calculateNeighbours(
-		const PathNodeInfo & source,
-		const PathfinderConfig * pathfinderConfig,
-		const CPathfinderHelper * pathfinderHelper) = 0;
-
-	virtual std::vector<CGPathNode *> 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<CGPathNode *> getInitialNodes() override;
-
-	virtual std::vector<CGPathNode *> calculateNeighbours(
-		const PathNodeInfo & source,
-		const PathfinderConfig * pathfinderConfig,
-		const CPathfinderHelper * pathfinderHelper) override;
-
-	virtual std::vector<CGPathNode *> 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<INodeStorage> nodeStorage;
-	std::vector<std::shared_ptr<IPathfindingRule>> rules;
-	PathfinderOptions options;
-
-	PathfinderConfig(
-		std::shared_ptr<INodeStorage> nodeStorage,
-		std::vector<std::shared_ptr<IPathfindingRule>> rules);
-	virtual ~PathfinderConfig() = default;
-
-	virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) = 0;
-};
-
-class DLL_LINKAGE SingleHeroPathfinderConfig : public PathfinderConfig
-{
-private:
-	std::unique_ptr<CPathfinderHelper> 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<std::shared_ptr<IPathfindingRule>> buildRuleSet();
-};
-
-class CPathfinder
-{
-public:
-	friend class CPathfinderHelper;
-
-	CPathfinder(
-		CGameState * _gs,
-		std::shared_ptr<PathfinderConfig> 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<PathfinderConfig> config;
-
-	boost::heap::fibonacci_heap<CGPathNode *, boost::heap::compare<NodeComparer<CGPathNode>> > 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<bool> noTerrainPenalty;
-		bool freeShipBoarding;
-		bool flyingMovement;
-		int flyingMovementVal;
-		bool waterWalking;
-		int waterWalkingVal;
-		int pathfindingVal;
-
-		BonusCache(const TConstBonusListPtr & bonusList);
-	};
-	std::unique_ptr<BonusCache> 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<int3> patrolTiles;
-
-	int turn;
-	PlayerColor owner;
-	const CGHeroInstance * hero;
-	std::vector<TurnInfo *> 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<int3> getCastleGates(const PathNodeInfo & source) const;
-	bool isAllowedTeleportEntrance(const CGTeleport * obj) const;
-	std::vector<int3> 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<int3> getNeighbourTiles(const PathNodeInfo & source) const;
-	std::vector<int3> getTeleportExits(const PathNodeInfo & source) const;
-
-	void getNeighbours(
-		const TerrainTile & srcTile,
-		const int3 & srcCoord,
-		std::vector<int3> & 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

+ 1 - 0
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"

+ 161 - 0
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<const CGHeroInstance *>(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

+ 222 - 0
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 <boost/heap/fibonacci_heap.hpp>
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class CGHeroInstance;
+class CGObjectInstance;
+class CGameState;
+class CPathfinderHelper;
+struct TerrainTile;
+
+template<typename N>
+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<float>::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<CGPathNode *, boost::heap::compare<NodeComparer<CGPathNode>>>;
+
+	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<CGPathNode> 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<CGPathNode, 4> 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

+ 668 - 0
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<int3> CPathfinderHelper::getNeighbourTiles(const PathNodeInfo & source) const
+{
+	std::vector<int3> 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<PathfinderConfig> 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<CGPathNode *> 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<int3> CPathfinderHelper::getAllowedTeleportChannelExits(const TeleportChannelID & channelID) const
+{
+	std::vector<int3> allowedExits;
+
+	for(const auto & objId : getTeleportChannelExits(channelID, hero->tempOwner))
+	{
+		const auto * obj = getObj(objId);
+		if(dynamic_cast<const CGWhirlpool *>(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<int3> CPathfinderHelper::getCastleGates(const PathNodeInfo & source) const
+{
+	std::vector<int3> 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<int3> CPathfinderHelper::getTeleportExits(const PathNodeInfo & source) const
+{
+	std::vector<int3> teleportationExits;
+
+	const auto * objTeleport = dynamic_cast<const CGTeleport *>(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<PlayerColor>(), 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<const CGWhirlpool *>(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<PlayerColor>()),
+	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<int3> & 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<bool>(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<bool>(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<int>(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<int>(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<int>(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<int>(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<int3> 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

+ 126 - 0
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<PathfinderConfig> 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<PathfinderConfig> config;
+
+	boost::heap::fibonacci_heap<CGPathNode *, boost::heap::compare<NodeComparer<CGPathNode>> > 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<int3> patrolTiles;
+
+	int turn;
+	PlayerColor owner;
+	const CGHeroInstance * hero;
+	std::vector<TurnInfo *> 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<int3> getCastleGates(const PathNodeInfo & source) const;
+	bool isAllowedTeleportEntrance(const CGTeleport * obj) const;
+	std::vector<int3> 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<int3> getNeighbourTiles(const PathNodeInfo & source) const;
+	std::vector<int3> getTeleportExits(const PathNodeInfo & source) const;
+
+	void getNeighbours(
+		const TerrainTile & srcTile,
+		const int3 & srcCoord,
+		std::vector<int3> & 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

+ 49 - 0
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<CGPathNode *> getInitialNodes() = 0;
+
+	virtual std::vector<CGPathNode *> calculateNeighbours(
+		const PathNodeInfo & source,
+		const PathfinderConfig * pathfinderConfig,
+		const CPathfinderHelper * pathfinderHelper) = 0;
+
+	virtual std::vector<CGPathNode *> 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

+ 147 - 0
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<const CGameInfoCallback *>(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<ELayer::SAIL>(pos, tile, fow, player, gs));
+					if(useFlying)
+						resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
+					if(useWaterWalking)
+						resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility<ELayer::WATER>(pos, tile, fow, player, gs));
+				}
+				if(tile.terType->isLand())
+				{
+					resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility<ELayer::LAND>(pos, tile, fow, player, gs));
+					if(useFlying)
+						resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
+				}
+			}
+		}
+	}
+}
+
+std::vector<CGPathNode *> NodeStorage::calculateNeighbours(
+	const PathNodeInfo & source,
+	const PathfinderConfig * pathfinderConfig,
+	const CPathfinderHelper * pathfinderHelper)
+{
+	std::vector<CGPathNode *> 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<CGPathNode *> NodeStorage::calculateTeleportations(
+	const PathNodeInfo & source,
+	const PathfinderConfig * pathfinderConfig,
+	const CPathfinderHelper * pathfinderHelper)
+{
+	std::vector<CGPathNode *> 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<CGPathNode *> 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<CGPathNode *> { 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

+ 52 - 0
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<CGPathNode *> getInitialNodes() override;
+
+	virtual std::vector<CGPathNode *> calculateNeighbours(
+		const PathNodeInfo & source,
+		const PathfinderConfig * pathfinderConfig,
+		const CPathfinderHelper * pathfinderHelper) override;
+
+	virtual std::vector<CGPathNode *> calculateTeleportations(
+		const PathNodeInfo & source,
+		const PathfinderConfig * pathfinderConfig,
+		const CPathfinderHelper * pathfinderHelper) override;
+
+	virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 67 - 0
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<INodeStorage> nodeStorage, std::vector<std::shared_ptr<IPathfindingRule>> rules):
+	nodeStorage(std::move(nodeStorage)),
+	rules(std::move(rules))
+{
+}
+
+std::vector<std::shared_ptr<IPathfindingRule>> SingleHeroPathfinderConfig::buildRuleSet()
+{
+	return std::vector<std::shared_ptr<IPathfindingRule>>{
+		std::make_shared<LayerTransitionRule>(),
+			std::make_shared<DestinationActionRule>(),
+			std::make_shared<MovementToDestinationRule>(),
+			std::make_shared<MovementCostRule>(),
+			std::make_shared<MovementAfterDestinationRule>()
+	};
+}
+
+SingleHeroPathfinderConfig::~SingleHeroPathfinderConfig() = default;
+
+SingleHeroPathfinderConfig::SingleHeroPathfinderConfig(CPathsInfo & out, CGameState * gs, const CGHeroInstance * hero)
+	: PathfinderConfig(std::make_shared<NodeStorage>(out, hero), buildRuleSet())
+{
+	pathfinderHelper = std::make_unique<CPathfinderHelper>(gs, hero, options);
+}
+
+CPathfinderHelper * SingleHeroPathfinderConfig::getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs)
+{
+	return pathfinderHelper.get();
+}
+
+VCMI_LIB_NAMESPACE_END

+ 103 - 0
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<INodeStorage> nodeStorage;
+	std::vector<std::shared_ptr<IPathfindingRule>> rules;
+	PathfinderOptions options;
+
+	PathfinderConfig(
+		std::shared_ptr<INodeStorage> nodeStorage,
+		std::vector<std::shared_ptr<IPathfindingRule>> rules);
+	virtual ~PathfinderConfig() = default;
+
+	virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) = 0;
+};
+
+class DLL_LINKAGE SingleHeroPathfinderConfig : public PathfinderConfig
+{
+private:
+	std::unique_ptr<CPathfinderHelper> pathfinderHelper;
+
+public:
+	SingleHeroPathfinderConfig(CPathsInfo & out, CGameState * gs, const CGHeroInstance * hero);
+	virtual ~SingleHeroPathfinderConfig();
+
+	virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override;
+
+	static std::vector<std::shared_ptr<IPathfindingRule>> buildRuleSet();
+};
+
+VCMI_LIB_NAMESPACE_END

+ 16 - 15
lib/PathfinderUtil.h → 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<EPathfindingLayer::EEPathfindingLayer layer>
-	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;
 	}
 }
 

+ 394 - 0
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<float>(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<float>(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<const CGTeleport *>(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

+ 116 - 0
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

+ 140 - 0
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<bool>(
+				bl->getFirst(Selector::type()(BonusType::NO_TERRAIN_PENALTY).And(Selector::subtype()(terrain->getIndex())))));
+	}
+
+	freeShipBoarding = static_cast<bool>(bl->getFirst(Selector::type()(BonusType::FREE_SHIP_BOARDING)));
+	flyingMovement = static_cast<bool>(bl->getFirst(Selector::type()(BonusType::FLYING_MOVEMENT)));
+	flyingMovementVal = bl->valOfBonuses(Selector::type()(BonusType::FLYING_MOVEMENT));
+	waterWalking = static_cast<bool>(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<BonusCache>(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<bool>(
+			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<bool>(bonuses->getFirst(Selector::type()(BonusType::FREE_SHIP_BOARDING)));
+		break;
+	case BonusType::FLYING_MOVEMENT:
+		bonusCache->flyingMovement = static_cast<bool>(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<bool>(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

+ 51 - 0
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<bool> noTerrainPenalty;
+		bool freeShipBoarding;
+		bool flyingMovement;
+		int flyingMovementVal;
+		bool waterWalking;
+		int waterWalkingVal;
+		int pathfindingVal;
+
+		BonusCache(const TConstBonusListPtr & bonusList);
+	};
+	std::unique_ptr<BonusCache> 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

+ 3 - 0
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"