Browse Source

Split pathfinder into multiple smaller files

Ivan Savenko 2 years ago
parent
commit
bd4d2788ed
47 changed files with 2362 additions and 2076 deletions
  1. 3 1
      AI/Nullkiller/Pathfinding/AINodeStorage.cpp
  2. 2 1
      AI/Nullkiller/Pathfinding/AINodeStorage.h
  3. 4 0
      AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp
  4. 3 0
      AI/Nullkiller/Pathfinding/AIPathfinderConfig.h
  5. 1 0
      AI/Nullkiller/Pathfinding/Actors.cpp
  6. 1 2
      AI/Nullkiller/Pathfinding/Actors.h
  7. 1 0
      AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.h
  8. 1 0
      AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.h
  9. 1 0
      AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.h
  10. 2 0
      AI/Nullkiller/Pathfinding/Rules/AIPreviousNodeRule.cpp
  11. 1 0
      AI/Nullkiller/Pathfinding/Rules/AIPreviousNodeRule.h
  12. 3 1
      AI/VCAI/Pathfinding/AINodeStorage.cpp
  13. 2 1
      AI/VCAI/Pathfinding/AINodeStorage.h
  14. 4 0
      AI/VCAI/Pathfinding/AIPathfinderConfig.cpp
  15. 3 0
      AI/VCAI/Pathfinding/AIPathfinderConfig.h
  16. 1 0
      AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.h
  17. 1 0
      AI/VCAI/Pathfinding/Rules/AIMovementAfterDestinationRule.h
  18. 1 0
      AI/VCAI/Pathfinding/Rules/AIMovementToDestinationRule.h
  19. 1 0
      AI/VCAI/Pathfinding/Rules/AIPreviousNodeRule.h
  20. 1 1
      client/CPlayerInterface.cpp
  21. 1 1
      client/Client.cpp
  22. 1 1
      client/PlayerLocalState.cpp
  23. 1 1
      client/adventureMap/AdventureMapInterface.cpp
  24. 1 1
      client/adventureMap/AdventureMapShortcuts.cpp
  25. 1 1
      client/mapView/MapRenderer.cpp
  26. 1 1
      client/mapView/MapRendererContext.cpp
  27. 1 1
      client/mapView/MapViewController.cpp
  28. 16 3
      cmake_modules/VCMI_lib.cmake
  29. 2 1
      lib/CGameState.cpp
  30. 0 1434
      lib/CPathfinder.cpp
  31. 0 618
      lib/CPathfinder.h
  32. 1 1
      lib/mapObjects/CGHeroInstance.cpp
  33. 161 0
      lib/pathfinder/CGPathNode.cpp
  34. 222 0
      lib/pathfinder/CGPathNode.h
  35. 668 0
      lib/pathfinder/CPathfinder.cpp
  36. 126 0
      lib/pathfinder/CPathfinder.h
  37. 49 0
      lib/pathfinder/INodeStorage.h
  38. 147 0
      lib/pathfinder/NodeStorage.cpp
  39. 52 0
      lib/pathfinder/NodeStorage.h
  40. 63 0
      lib/pathfinder/PathfinderOptions.cpp
  41. 103 0
      lib/pathfinder/PathfinderOptions.h
  42. 4 4
      lib/pathfinder/PathfinderUtil.h
  43. 394 0
      lib/pathfinder/PathfindingRules.cpp
  44. 116 0
      lib/pathfinder/PathfindingRules.h
  45. 140 0
      lib/pathfinder/TurnInfo.cpp
  46. 51 0
      lib/pathfinder/TurnInfo.h
  47. 3 1
      server/CGameHandler.cpp

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

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

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

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

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

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

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

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

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

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

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

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

+ 1 - 1
client/Client.cpp

@@ -24,12 +24,12 @@
 #include "../CCallback.h"
 #include "../lib/CConfigHandler.h"
 #include "../lib/CGameState.h"
-#include "../lib/CPathfinder.h"
 #include "../lib/CThreadHelper.h"
 #include "../lib/VCMIDirs.h"
 #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"

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

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

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

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

+ 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 - 1
lib/CGameState.cpp

@@ -15,7 +15,6 @@
 #include "CArtHandler.h"
 #include "CBuildingHandler.h"
 #include "CGeneralTextHandler.h"
-#include "CPathfinder.h"
 #include "CTownHandler.h"
 #include "spells/CSpellHandler.h"
 #include "CHeroHandler.h"
@@ -28,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 - 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 - 1
lib/mapObjects/CGHeroInstance.cpp

@@ -28,10 +28,10 @@
 #include "../IGameCallback.h"
 #include "../CGameState.h"
 #include "../CCreatureHandler.h"
-#include "../CPathfinder.h"
 #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(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
+}
+
+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();
+	}
+};
+
+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;
+};
+
+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 == 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;
+}
+
+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;
+}
+
+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;
+	CGPathNode::ENodeAction 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 == 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;
+}
+
+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;
+}
+
+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, 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;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 63 - 0
lib/pathfinder/PathfinderOptions.cpp

@@ -0,0 +1,63 @@
+/*
+ * 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"
+
+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();
+}

+ 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

+ 4 - 4
lib/PathfinderUtil.h → lib/pathfinder/PathfinderUtil.h

@@ -9,10 +9,10 @@
  */
 #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"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 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 == 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;
+}
+
+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 != 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;
+}
+
+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;
+}
+
+
+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 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;
+	}
+}
+
+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 - 1
server/CGameHandler.cpp

@@ -19,7 +19,9 @@
 #include "../lib/CArtHandler.h"
 #include "../lib/CBuildingHandler.h"
 #include "../lib/CHeroHandler.h"
-#include "../lib/CPathfinder.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"