Răsfoiți Sursa

AI: first rule extracted for pathfinder

Andrii Danylchenko 7 ani în urmă
părinte
comite
6ac987794c
5 a modificat fișierele cu 205 adăugiri și 192 ștergeri
  1. 11 0
      AI/VCAI/AIUtility.cpp
  2. 1 0
      AI/VCAI/AIUtility.h
  3. 81 95
      lib/CPathfinder.cpp
  4. 102 91
      lib/CPathfinder.h
  5. 10 6
      server/CGameHandler.cpp

+ 11 - 0
AI/VCAI/AIUtility.cpp

@@ -597,3 +597,14 @@ bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2
 	else
 		return false;
 }
+
+uint32_t distanceToTile(const CGHeroInstance * hero, int3 pos)
+{
+	auto pathInfo = cb->getPathsInfo(hero)->getPathInfo(pos);
+	uint32_t totalMovementPoints = pathInfo->turns * hero->maxMovePoints(true) + hero->movement;
+	
+	if(totalMovementPoints < pathInfo->moveRemains) // should not be but who knows
+		return 0;
+
+	return totalMovementPoints - pathInfo->moveRemains;
+}

+ 1 - 0
AI/VCAI/AIUtility.h

@@ -176,6 +176,7 @@ bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2
 ui64 howManyReinforcementsCanBuy(HeroPtr h, const CGTownInstance * t);
 ui64 howManyReinforcementsCanGet(HeroPtr h, const CGTownInstance * t);
 int3 whereToExplore(HeroPtr h);
+uint32_t distanceToTile(const CGHeroInstance * hero, int3 pos);
 
 class CDistanceSorter
 {

+ 81 - 95
lib/CPathfinder.cpp

@@ -25,20 +25,20 @@ bool canSeeObj(const CGObjectInstance * obj)
 	return obj != nullptr && obj->ID != Obj::EVENT;
 }
 
-CNeighbourFinder::CNeighbourFinder(CPathfinder * pathfinder)
-	:pathfinder(pathfinder), neighbours(), neighbourTiles(), accessibleNeighbourTiles()
+CNeighbourFinder::CNeighbourFinder()
+	:neighbours(), neighbourTiles(), accessibleNeighbourTiles()
 {
 }
 
-std::vector<CGPathNode *> & CNeighbourFinder::calculateNeighbours()
+std::vector<CGPathNode *> & CNeighbourFinder::calculateNeighbours(CPathNodeInfo & source, CPathfinderHelper * pathfinderHelper, CNodeHelper * nodeHelper)
 {
-	addNeighbourTiles();
+	addNeighbourTiles(source, pathfinderHelper);
 
 	for(auto & neighbour : accessibleNeighbourTiles)
 	{
 		for(EPathfindingLayer i = EPathfindingLayer::LAND; i <= EPathfindingLayer::AIR; i.advance(1))
 		{
-			auto node = pathfinder->nodeHelper->getNode(neighbour, i);
+			auto node = nodeHelper->getNode(neighbour, i);
 
 			if(node->accessible == CGPathNode::NOT_SET)
 				continue;
@@ -50,19 +50,24 @@ std::vector<CGPathNode *> & CNeighbourFinder::calculateNeighbours()
 	return neighbours;
 }
 
-void CNeighbourFinder::addNeighbourTiles()
+void CNeighbourFinder::addNeighbourTiles(CPathNodeInfo & source, CPathfinderHelper * pathfinderHelper)
 {
 	neighbourTiles.clear();
 	accessibleNeighbourTiles.clear();
 	neighbours.clear();
 
-	pathfinder->populateNeighbourTiles(neighbourTiles);
+	pathfinderHelper->getNeighbours(
+		*source.tile,
+		source.node->coord,
+		neighbourTiles,
+		boost::logic::indeterminate,
+		source.node->layer == EPathfindingLayer::SAIL);
 
-	if(pathfinder->source.isNodeObjectVisitable())
+	if(source.isNodeObjectVisitable())
 	{
 		for(int3 tile : neighbourTiles)
 		{
-			if(pathfinder->canMoveBetween(tile, pathfinder->source.nodeObject->visitablePos()))
+			if(pathfinderHelper->canMoveBetween(tile, source.nodeObject->visitablePos()))
 				accessibleNeighbourTiles.push_back(tile);
 		}
 	}
@@ -99,7 +104,7 @@ public:
 	}
 };
 
-CPathfinder::PathfinderOptions::PathfinderOptions()
+PathfinderOptions::PathfinderOptions()
 {
 	useFlying = settings["pathfinder"]["layers"]["flying"].Bool();
 	useWaterWalking = settings["pathfinder"]["layers"]["waterWalking"].Bool();
@@ -125,7 +130,7 @@ CPathfinder::CPathfinder(
 		_gs,
 		_hero,
 		std::make_shared<CPathfinderNodeHelper>(_out, _hero),
-		std::make_shared<CNeighbourFinder>(this))
+		std::make_shared<CNeighbourFinder>())
 {
 }
 
@@ -145,9 +150,7 @@ CPathfinder::CPathfinder(
 	assert(hero);
 	assert(hero == getHero(hero->id));
 
-    destAction = CGPathNode::UNKNOWN;
-
-	hlp = make_unique<CPathfinderHelper>(hero, options);
+	hlp = make_unique<CPathfinderHelper>(_gs, hero, options);
 
 	initializePatrol();
 	initializeGraph();
@@ -222,7 +225,7 @@ void CPathfinder::calculatePaths()
 		}
 
 		//add accessible neighbouring nodes to the queue
-		auto neighbourNodes = neighbourFinder->calculateNeighbours();
+		auto neighbourNodes = neighbourFinder->calculateNeighbours(source, hlp.get(), nodeHelper.get());
 		for(auto & neighbour : neighbourNodes)
 		{
 			destination.setNode(gs, neighbour);
@@ -244,26 +247,29 @@ void CPathfinder::calculatePaths()
 			if(source.node->layer != destination.node->layer && !isLayerTransitionPossible())
 				continue;
 
+			destination.guarded = isDestinationGuarded();
+
 			if(!isMovementToDestPossible())
 				continue;
 
-			destAction = getDestAction();
+			destination.action = getDestAction();
+
 			int turnAtNextTile = turn, moveAtNextTile = movement;
-			int cost = CPathfinderHelper::getMovementCost(hero, source, destination, moveAtNextTile, hlp->getTurnInfo());
+			int cost = hlp->getMovementCost(source, destination, moveAtNextTile);
 			int remains = moveAtNextTile - cost;
 			if(remains < 0)
 			{
 				//occurs rarely, when hero with low movepoints tries to leave the road
 				hlp->updateTurnInfo(++turnAtNextTile);
 				moveAtNextTile = hlp->getMaxMovePoints(destination.node->layer);
-				cost = CPathfinderHelper::getMovementCost(hero, source, destination, moveAtNextTile, hlp->getTurnInfo()); //cost must be updated, movement points changed :(
+				cost = hlp->getMovementCost(source, destination, moveAtNextTile); //cost must be updated, movement points changed :(
 				remains = moveAtNextTile - cost;
 			}
-			if(destAction == CGPathNode::EMBARK || destAction == CGPathNode::DISEMBARK)
+			if(destination.action == CGPathNode::EMBARK || destination.action == CGPathNode::DISEMBARK)
 			{
 				/// FREE_SHIP_BOARDING bonus only remove additional penalty
 				/// land <-> sail transition still cost movement points as normal movement
-				remains = hero->movementPointsAfterEmbark(moveAtNextTile, cost, destAction - 1, hlp->getTurnInfo());
+				remains = hero->movementPointsAfterEmbark(moveAtNextTile, cost, destination.action - 1, hlp->getTurnInfo());
 				cost = moveAtNextTile - remains;
 			}
 
@@ -274,10 +280,11 @@ void CPathfinder::calculatePaths()
 				destination.node->moveRemains = remains;
 				destination.node->turns = turnAtNextTile;
 				destination.node->theNodeBefore = source.node;
-				destination.node->action = destAction;
+				destination.node->action = destination.action;
+
+				CMovementAfterDestinationRule rl = CMovementAfterDestinationRule();
+				rl.process(hlp.get(), source, destination);
 
-				// TODO: move this method to a separete module kind of IPathfinderRule.process(). And all above.
-				checkMovementAfterDestPossible();
 				if(!destination.furtherProcessingImpossible)
 					pq.push(destination.node);
 			}
@@ -313,17 +320,6 @@ void CPathfinder::calculatePaths()
 	} //queue loop
 }
 
-void CPathfinder::populateNeighbourTiles(std::vector<int3> & neighbourTiles)
-{
-	CPathfinderHelper::getNeighbours(
-		gs->map,
-		*source.tile,
-		source.node->coord,
-		neighbourTiles,
-		boost::logic::indeterminate,
-		source.node->layer == EPathfindingLayer::SAIL);
-}
-
 void CPathfinder::addTeleportExits()
 {
 	neighbours.clear();
@@ -333,7 +329,7 @@ void CPathfinder::addTeleportExits()
 		return;
 
 	const CGTeleport * objTeleport = dynamic_cast<const CGTeleport *>(source.nodeObject);
-	if(isAllowedTeleportEntrance(objTeleport))
+	if(hlp->isAllowedTeleportEntrance(objTeleport))
 	{
 		for(auto objId : getTeleportChannelExits(objTeleport->channel, hero->tempOwner))
 		{
@@ -497,7 +493,7 @@ bool CPathfinder::isMovementToDestPossible() const
 	switch(destination.node->layer)
 	{
 	case ELayer::LAND:
-		if(!canMoveBetween(source.node->coord, destination.node->coord))
+		if(!hlp->canMoveBetween(source.node->coord, destination.node->coord))
 			return false;
 		if(isSourceGuarded())
 		{
@@ -511,7 +507,7 @@ bool CPathfinder::isMovementToDestPossible() const
 		break;
 
 	case ELayer::SAIL:
-		if(!canMoveBetween(source.node->coord, destination.node->coord))
+		if(!hlp->canMoveBetween(source.node->coord, destination.node->coord))
 			return false;
 		if(isSourceGuarded())
 		{
@@ -537,7 +533,7 @@ bool CPathfinder::isMovementToDestPossible() const
 		break;
 
 	case ELayer::WATER:
-		if(!canMoveBetween(source.node->coord, destination.node->coord) || destination.node->accessible != CGPathNode::ACCESSIBLE)
+		if(!hlp->canMoveBetween(source.node->coord, destination.node->coord) || destination.node->accessible != CGPathNode::ACCESSIBLE)
 			return false;
 		if(isDestinationGuarded())
 			return false;
@@ -548,9 +544,9 @@ bool CPathfinder::isMovementToDestPossible() const
 	return true;
 }
 
-void CPathfinder::checkMovementAfterDestPossible()
+void CMovementAfterDestinationRule::process(CPathfinderHelper * pathfinderHelper, CPathNodeInfo & source, CDestinationNodeInfo & destination)
 {
-	switch(destAction)
+	switch(destination.action)
 	{
 	/// TODO: Investigate what kind of limitation is possible to apply on movement from visitable tiles
 	/// Likely in many cases we don't need to add visitable tile to queue when hero don't fly
@@ -559,14 +555,14 @@ void CPathfinder::checkMovementAfterDestPossible()
 		/// For now we only add visitable tile into queue when it's teleporter that allow transit
 		/// Movement from visitable tile when hero is standing on it is possible into any layer
 		const CGTeleport * objTeleport = dynamic_cast<const CGTeleport *>(destination.nodeObject);
-		if(isAllowedTeleportEntrance(objTeleport))
+		if(pathfinderHelper->isAllowedTeleportEntrance(objTeleport))
 		{
 			/// For now we'll always allow transit over teleporters
 			/// Transit over whirlpools only allowed when hero protected
 			return;
 		}
-		else if(destination.nodeObject->ID == Obj::GARRISON 
-			|| destination.nodeObject->ID == Obj::GARRISON2 
+		else if(destination.nodeObject->ID == Obj::GARRISON
+			|| destination.nodeObject->ID == Obj::GARRISON2
 			|| destination.nodeObject->ID == Obj::BORDER_GATE)
 		{
 			/// Transit via unguarded garrisons is always possible
@@ -580,20 +576,20 @@ void CPathfinder::checkMovementAfterDestPossible()
 		return;
 
 	case CGPathNode::EMBARK:
-		if(options.useEmbarkAndDisembark)
+		if(pathfinderHelper->options.useEmbarkAndDisembark)
 			return;
 
 		break;
 
 	case CGPathNode::DISEMBARK:
-		if(options.useEmbarkAndDisembark && !isDestinationGuarded())
+		if(pathfinderHelper->options.useEmbarkAndDisembark && !destination.guarded)
 			return;
 
 		break;
 
 	case CGPathNode::BATTLE:
 		/// Movement after BATTLE action only possible from guarded tile to guardian tile
-		if(isDestinationGuarded())
+		if(destination.guarded)
 			return;
 
 		break;
@@ -643,7 +639,7 @@ CGPathNode::ENodeAction CPathfinder::getDestAction() const
 			{
 				if(destination.nodeObject->passableFor(hero->tempOwner))
 				{
-					if(isDestinationGuarded(true))
+					if(isDestinationGuarded())
 						action = CGPathNode::BATTLE;
 				}
 				else if(objRel == PlayerRelations::ENEMIES)
@@ -653,7 +649,7 @@ CGPathNode::ENodeAction CPathfinder::getDestAction() const
 			{
 				if(destination.nodeObject->passableFor(hero->tempOwner))
 				{
-					if(isDestinationGuarded(true))
+					if(isDestinationGuarded())
 						action = CGPathNode::BATTLE;
 				}
 				else
@@ -716,17 +712,11 @@ bool CPathfinder::isSourceGuarded() const
 	return false;
 }
 
-bool CPathfinder::isDestinationGuarded(const bool ignoreAccessibility) const
+bool CPathfinder::isDestinationGuarded() const
 {
 	/// isDestinationGuarded is exception needed for garrisons.
 	/// When monster standing behind garrison it's visitable and guarded at the same time.
-	if(gs->guardingCreaturePosition(destination.node->coord).valid()
-		&& (ignoreAccessibility || destination.node->accessible == CGPathNode::BLOCKVIS))
-	{
-		return true;
-	}
-
-	return false;
+	return gs->guardingCreaturePosition(destination.node->coord).valid();
 }
 
 bool CPathfinder::isDestinationGuardian() const
@@ -855,12 +845,12 @@ CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const int3 & pos,
 	return CGPathNode::ACCESSIBLE;
 }
 
-bool CPathfinder::canMoveBetween(const int3 & a, const int3 & b) const
+bool CPathfinderHelper::canMoveBetween(const int3 & a, const int3 & b) const
 {
 	return gs->checkForVisitableDir(a, b);
 }
 
-bool CPathfinder::isAllowedTeleportEntrance(const CGTeleport * obj) const
+bool CPathfinderHelper::isAllowedTeleportEntrance(const CGTeleport * obj) const
 {
 	if(!obj || !isTeleportEntrancePassable(obj, hero->tempOwner))
 		return false;
@@ -877,12 +867,12 @@ bool CPathfinder::isAllowedTeleportEntrance(const CGTeleport * obj) const
 	return false;
 }
 
-bool CPathfinder::addTeleportTwoWay(const CGTeleport * obj) const
+bool CPathfinderHelper::addTeleportTwoWay(const CGTeleport * obj) const
 {
 	return options.useTeleportTwoWay && isTeleportChannelBidirectional(obj->channel, hero->tempOwner);
 }
 
-bool CPathfinder::addTeleportOneWay(const CGTeleport * obj) const
+bool CPathfinderHelper::addTeleportOneWay(const CGTeleport * obj) const
 {
 	if(options.useTeleportOneWay && isTeleportChannelUnidirectional(obj->channel, hero->tempOwner))
 	{
@@ -893,7 +883,7 @@ bool CPathfinder::addTeleportOneWay(const CGTeleport * obj) const
 	return false;
 }
 
-bool CPathfinder::addTeleportOneWayRandom(const CGTeleport * obj) const
+bool CPathfinderHelper::addTeleportOneWayRandom(const CGTeleport * obj) const
 {
 	if(options.useTeleportOneWayRandom && isTeleportChannelUnidirectional(obj->channel, hero->tempOwner))
 	{
@@ -904,9 +894,9 @@ bool CPathfinder::addTeleportOneWayRandom(const CGTeleport * obj) const
 	return false;
 }
 
-bool CPathfinder::addTeleportWhirlpool(const CGWhirlpool * obj) const
+bool CPathfinderHelper::addTeleportWhirlpool(const CGWhirlpool * obj) const
 {
-	return options.useTeleportWhirlpool && hlp->hasBonusOfType(Bonus::WHIRLPOOL_PROTECTION) && obj;
+	return options.useTeleportWhirlpool && hasBonusOfType(Bonus::WHIRLPOOL_PROTECTION) && obj;
 }
 
 TurnInfo::BonusCache::BonusCache(TBonusListPtr bl)
@@ -997,8 +987,8 @@ int TurnInfo::getMaxMovePoints(const EPathfindingLayer layer) const
 	return layer == EPathfindingLayer::SAIL ? maxMovePointsWater : maxMovePointsLand;
 }
 
-CPathfinderHelper::CPathfinderHelper(const CGHeroInstance * Hero, const CPathfinder::PathfinderOptions & Options)
-	: turn(-1), hero(Hero), options(Options)
+CPathfinderHelper::CPathfinderHelper(CGameState * gs, const CGHeroInstance * Hero, const PathfinderOptions & Options)
+	: CGameInfoCallback(gs, boost::optional<PlayerColor>()), turn(-1), hero(Hero), options(Options)
 {
 	turnsInfo.reserve(16);
 	updateTurnInfo();
@@ -1058,8 +1048,10 @@ int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer layer) const
 	return turnsInfo[turn]->getMaxMovePoints(layer);
 }
 
-void CPathfinderHelper::getNeighbours(const CMap * map, const TerrainTile & srct, const int3 & tile, std::vector<int3> & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing)
+void CPathfinderHelper::getNeighbours(const TerrainTile & srct, const int3 & tile, std::vector<int3> & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing)
 {
+	CMap * map = gs->map;
+
 	static const int3 dirs[] = {
 		int3(-1, +1, +0),	int3(0, +1, +0),	int3(+1, +1, +0),
 		int3(-1, +0, +0),	/* source pos */	int3(+1, +0, +0),
@@ -1101,28 +1093,23 @@ void CPathfinderHelper::getNeighbours(const CMap * map, const TerrainTile & srct
 	}
 }
 
-int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & src, const int3 & dst, const TerrainTile * ct, const TerrainTile * dt, const int remainingMovePoints, const TurnInfo * ti, const bool checkLast)
+int CPathfinderHelper::getMovementCost(const int3 & src, const int3 & dst, const TerrainTile * ct, const TerrainTile * dt, const int remainingMovePoints, const bool checkLast)
 {
 	if(src == dst) //same tile
 		return 0;
 
-	bool localTi = false;
-	if(!ti)
-	{
-		localTi = true;
-		ti = new TurnInfo(h);
-	}
+	auto ti = getTurnInfo();
 
 	if(ct == nullptr || dt == nullptr)
 	{
-		ct = h->cb->getTile(src);
-		dt = h->cb->getTile(dst);
+		ct = hero->cb->getTile(src);
+		dt = hero->cb->getTile(dst);
 	}
 
 	/// TODO: by the original game rules hero shouldn't be affected by terrain penalty while flying.
 	/// Also flying movement only has penalty when player moving over blocked tiles.
 	/// So if you only have base flying with 40% penalty you can still ignore terrain penalty while having zero flying penalty.
-	int ret = h->getTileCost(*dt, *ct, ti);
+	int ret = hero->getTileCost(*dt, *ct, ti);
 	/// Unfortunately this can't be implemented yet as server don't know when player flying and when he's not.
 	/// Difference in cost calculation on client and server is much worse than incorrect cost.
 	/// So this one is waiting till server going to use pathfinder rules for path validation.
@@ -1133,9 +1120,9 @@ int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & sr
 	}
 	else if(dt->terType == ETerrainType::WATER)
 	{
-		if(h->boat && ct->hasFavorableWinds() && dt->hasFavorableWinds())
+		if(hero->boat && ct->hasFavorableWinds() && dt->hasFavorableWinds())
 			ret *= 0.666;
-		else if(!h->boat && ti->hasBonusOfType(Bonus::WATER_WALKING))
+		else if(!hero->boat && ti->hasBonusOfType(Bonus::WATER_WALKING))
 		{
 			ret *= (100.0 + ti->valOfBonuses(Bonus::WATER_WALKING)) / 100.0;
 		}
@@ -1148,9 +1135,6 @@ int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & sr
 		//diagonal move costs too much but normal move is possible - allow diagonal move for remaining move points
 		if(ret > remainingMovePoints && remainingMovePoints >= old)
 		{
-			if(localTi)
-				delete ti;
-
 			return remainingMovePoints;
 		}
 	}
@@ -1162,32 +1146,21 @@ int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & sr
 	{
 		std::vector<int3> vec;
 		vec.reserve(8); //optimization
-		getNeighbours(h->cb->gameState()->map, *dt, dst, vec, ct->terType != ETerrainType::WATER, true);
+		getNeighbours(*dt, dst, vec, ct->terType != ETerrainType::WATER, true);
 		for(auto & elem : vec)
 		{
-			int fcost = getMovementCost(h, dst, elem, nullptr, nullptr, left, ti, false);
+			int fcost = getMovementCost(dst, elem, nullptr, nullptr, left, false);
 			if(fcost <= left)
 			{
-				if(localTi)
-					delete ti;
-
 				return ret;
 			}
 		}
 		ret = remainingMovePoints;
 	}
 
-	if(localTi)
-		delete ti;
-
 	return ret;
 }
 
-int CPathfinderHelper::getMovementCost(const CGHeroInstance * h, const int3 & dst)
-{
-	return getMovementCost(h, h->visitablePos(), dst, nullptr, nullptr, h->movement);
-}
-
 CGPathNode::CGPathNode()
 	: coord(int3(-1, -1, -1)), layer(ELayer::WRONG)
 {
@@ -1308,7 +1281,7 @@ CGPathNode * CPathsInfo::getNode(const int3 & coord, const ELayer layer)
 }
 
 CPathNodeInfo::CPathNodeInfo()
-	: node(nullptr), nodeObject(nullptr), tile(nullptr), coord(-1, -1, -1), blocked(false), furtherProcessingImpossible(false)
+	: node(nullptr), nodeObject(nullptr), tile(nullptr), coord(-1, -1, -1), guarded(false)
 {
 }
 
@@ -1325,8 +1298,21 @@ void CPathNodeInfo::setNode(CGameState * gs, CGPathNode * n, bool excludeTopObje
 		nodeObject = tile->topVisitableObj(excludeTopObject);
 	}
 
+	guarded = false;
+}
+
+CDestinationNodeInfo::CDestinationNodeInfo()
+	: CPathNodeInfo(), blocked(false), furtherProcessingImpossible(false), action(CGPathNode::ENodeAction::UNKNOWN)
+{
+}
+
+void CDestinationNodeInfo::setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject)
+{
+	CPathNodeInfo::setNode(gs, n, excludeTopObject);
+
 	blocked = false;
 	furtherProcessingImpossible = false;
+	action = CGPathNode::ENodeAction::UNKNOWN;
 }
 
 bool CPathNodeInfo::isNodeObjectVisitable() const

+ 102 - 91
lib/CPathfinder.h

@@ -102,16 +102,26 @@ struct DLL_LINKAGE CPathNodeInfo
 	const CGObjectInstance * nodeObject;
 	const TerrainTile * tile;
 	int3 coord;
-	bool blocked;
-	bool furtherProcessingImpossible;
+	bool guarded;
 
 	CPathNodeInfo();
 
-	void setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject = false);
+	virtual void setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject = false);
 
 	bool isNodeObjectVisitable() const;
 };
 
+struct DLL_LINKAGE CDestinationNodeInfo : public CPathNodeInfo
+{
+	CGPathNode::ENodeAction action;
+	bool furtherProcessingImpossible;
+	bool blocked;
+
+	CDestinationNodeInfo();
+
+	virtual void setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject = false) override;
+};
+
 class CNodeHelper
 {
 public:
@@ -119,8 +129,21 @@ public:
 	virtual CGPathNode * getInitialNode() = 0;
 };
 
+class CPathfinderHelper;
 class CPathfinder;
 
+class IPathfindingRule
+{
+public:
+	virtual void process(CPathfinderHelper * pathfinderHelper, CPathNodeInfo & source, CDestinationNodeInfo & destination) = 0;
+};
+
+class CMovementAfterDestinationRule : public IPathfindingRule
+{
+public:
+	virtual void process(CPathfinderHelper * pathfinderHelper, CPathNodeInfo & source, CDestinationNodeInfo & destination) override;
+};
+
 class CNeighbourFinder
 {
 protected:
@@ -130,11 +153,62 @@ protected:
 	std::vector<CGPathNode *> neighbours;
 
 public:
-	CNeighbourFinder(CPathfinder * pathfinder);
-	virtual std::vector<CGPathNode *> & calculateNeighbours();
+	CNeighbourFinder();
+	virtual std::vector<CGPathNode *> & calculateNeighbours(CPathNodeInfo & source, CPathfinderHelper * pathfinderHelper, CNodeHelper * nodeHelper);
 
 protected:
-	void addNeighbourTiles();
+	void addNeighbourTiles(CPathNodeInfo & source, CPathfinderHelper * pathfinderHelper);
+};
+
+struct DLL_LINKAGE PathfinderOptions
+{
+	bool useFlying;
+	bool useWaterWalking;
+	bool useEmbarkAndDisembark;
+	bool useTeleportTwoWay; // Two-way monoliths and Subterranean Gate
+	bool useTeleportOneWay; // One-way monoliths with one known exit only
+	bool useTeleportOneWayRandom; // One-way monoliths with more than one known exit
+	bool useTeleportWhirlpool; // Force enabled if hero protected or unaffected (have one stack of one creature)
+
+							   /// TODO: Find out with client and server code, merge with normal teleporters.
+							   /// Likely proper implementation would require some refactoring of CGTeleport.
+							   /// So for now this is unfinished and disabled by default.
+	bool useCastleGate;
+
+	/// If true transition into air layer only possible from initial node.
+	/// This is drastically decrease path calculation complexity (and time).
+	/// Downside is less MP effective paths calculation.
+	///
+	/// TODO: If this option end up useful for slow devices it's can be improved:
+	/// - Allow transition into air layer not only from initial position, but also from teleporters.
+	///   Movement into air can be also allowed when hero disembarked.
+	/// - Other idea is to allow transition into air within certain radius of N tiles around hero.
+	///   Patrol support need similar functionality so it's won't be ton of useless code.
+	///   Such limitation could be useful as it's can be scaled depend on device performance.
+	bool lightweightFlyingMode;
+
+	/// This option enable one turn limitation for flying and water walking.
+	/// So if we're out of MP while cp is blocked or water tile we won't add dest tile to queue.
+	///
+	/// Following imitation is default H3 mechanics, but someone may want to disable it in mods.
+	/// After all this limit should benefit performance on maps with tons of water or blocked tiles.
+	///
+	/// TODO:
+	/// - Behavior when option is disabled not implemented and will lead to crashes.
+	bool oneTurnSpecialLayersLimit;
+
+	/// VCMI have different movement rules to solve flaws original engine has.
+	/// If this option enabled you'll able to do following things in fly:
+	/// - Move from blocked tiles to visitable one
+	/// - Move from guarded tiles to blockvis tiles without being attacked
+	/// - Move from guarded tiles to guarded visitable tiles with being attacked after
+	/// TODO:
+	/// - Option should also allow same tile land <-> air layer transitions.
+	///   Current implementation only allow go into (from) air layer only to neighbour tiles.
+	///   I find it's reasonable limitation, but it's will make some movements more expensive than in H3.
+	bool originalMovementRules;
+
+	PathfinderOptions();
 };
 
 class CPathfinder : private CGameInfoCallback
@@ -151,60 +225,10 @@ public:
 
 	void calculatePaths(); //calculates possible paths for hero, uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists
 
-//private:
+private:
 	typedef EPathfindingLayer ELayer;
-
-	struct PathfinderOptions
-	{
-		bool useFlying;
-		bool useWaterWalking;
-		bool useEmbarkAndDisembark;
-		bool useTeleportTwoWay; // Two-way monoliths and Subterranean Gate
-		bool useTeleportOneWay; // One-way monoliths with one known exit only
-		bool useTeleportOneWayRandom; // One-way monoliths with more than one known exit
-		bool useTeleportWhirlpool; // Force enabled if hero protected or unaffected (have one stack of one creature)
-
-		/// TODO: Find out with client and server code, merge with normal teleporters.
-		/// Likely proper implementation would require some refactoring of CGTeleport.
-		/// So for now this is unfinished and disabled by default.
-		bool useCastleGate;
-
-		/// If true transition into air layer only possible from initial node.
-		/// This is drastically decrease path calculation complexity (and time).
-		/// Downside is less MP effective paths calculation.
-		///
-		/// TODO: If this option end up useful for slow devices it's can be improved:
-		/// - Allow transition into air layer not only from initial position, but also from teleporters.
-		///   Movement into air can be also allowed when hero disembarked.
-		/// - Other idea is to allow transition into air within certain radius of N tiles around hero.
-		///   Patrol support need similar functionality so it's won't be ton of useless code.
-		///   Such limitation could be useful as it's can be scaled depend on device performance.
-		bool lightweightFlyingMode;
-
-		/// This option enable one turn limitation for flying and water walking.
-		/// So if we're out of MP while cp is blocked or water tile we won't add dest tile to queue.
-		///
-		/// Following imitation is default H3 mechanics, but someone may want to disable it in mods.
-		/// After all this limit should benefit performance on maps with tons of water or blocked tiles.
-		///
-		/// TODO:
-		/// - Behavior when option is disabled not implemented and will lead to crashes.
-		bool oneTurnSpecialLayersLimit;
-
-		/// VCMI have different movement rules to solve flaws original engine has.
-		/// If this option enabled you'll able to do following things in fly:
-		/// - Move from blocked tiles to visitable one
-		/// - Move from guarded tiles to blockvis tiles without being attacked
-		/// - Move from guarded tiles to guarded visitable tiles with being attacked after
-		/// TODO:
-		/// - Option should also allow same tile land <-> air layer transitions.
-		///   Current implementation only allow go into (from) air layer only to neighbour tiles.
-		///   I find it's reasonable limitation, but it's will make some movements more expensive than in H3.
-		bool originalMovementRules;
-
-		PathfinderOptions();
-	} options;
-
+	
+	PathfinderOptions options;
 	const CGHeroInstance * hero;
 	const std::vector<std::vector<std::vector<ui8> > > &FoW;
 	std::unique_ptr<CPathfinderHelper> hlp;
@@ -236,10 +260,8 @@ public:
 	std::vector<int3> neighbours;
 
 	CPathNodeInfo source; //current (source) path node -> we took it from the queue
-	CPathNodeInfo destination; //destination node -> it's a neighbour of source that we consider
-	CGPathNode::ENodeAction destAction;
+	CDestinationNodeInfo destination; //destination node -> it's a neighbour of source that we consider
 
-	void populateNeighbourTiles(std::vector<int3> & neighbourTiles);
 	void addTeleportExits();
 
 	bool isHeroPatrolLocked() const;
@@ -248,27 +270,18 @@ public:
 	bool isLayerTransitionPossible(const ELayer dstLayer) const;
 	bool isLayerTransitionPossible() const;
 	bool isMovementToDestPossible() const;
-	void checkMovementAfterDestPossible();
 	CGPathNode::ENodeAction getDestAction() const;
 	CGPathNode::ENodeAction getTeleportDestAction() const;
 
 	bool isSourceInitialPosition() const;
 	bool isSourceGuarded() const;
-	bool isDestinationGuarded(const bool ignoreAccessibility = true) const;
+	bool isDestinationGuarded() const;
 	bool isDestinationGuardian() const;
 
 	void initializePatrol();
 	void initializeGraph();
 
 	CGPathNode::EAccessibility evaluateAccessibility(const int3 & pos, const TerrainTile * tinfo, const ELayer layer) const;
-	bool canMoveBetween(const int3 & a, const int3 & b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility)
-
-	bool isAllowedTeleportEntrance(const CGTeleport * obj) const;
-	bool addTeleportTwoWay(const CGTeleport * obj) const;
-	bool addTeleportOneWay(const CGTeleport * obj) const;
-	bool addTeleportOneWayRandom(const CGTeleport * obj) const;
-	bool addTeleportWhirlpool(const CGWhirlpool * obj) const;
-
 };
 
 struct DLL_LINKAGE TurnInfo
@@ -300,10 +313,15 @@ struct DLL_LINKAGE TurnInfo
 	int getMaxMovePoints(const EPathfindingLayer layer) const;
 };
 
-class DLL_LINKAGE CPathfinderHelper
+class DLL_LINKAGE CPathfinderHelper : private CGameInfoCallback
 {
 public:
-	CPathfinderHelper(const CGHeroInstance * Hero, const CPathfinder::PathfinderOptions & Options);
+	int turn;
+	const CGHeroInstance * hero;
+	std::vector<TurnInfo *> turnsInfo;
+	const PathfinderOptions & options;
+
+	CPathfinderHelper(CGameState * gs, const CGHeroInstance * Hero, const PathfinderOptions & Options);
 	~CPathfinderHelper();
 	void updateTurnInfo(const int turn = 0);
 	bool isLayerAvailable(const EPathfindingLayer layer) const;
@@ -311,43 +329,36 @@ public:
 	bool hasBonusOfType(const Bonus::BonusType type, const int subtype = -1) const;
 	int getMaxMovePoints(const EPathfindingLayer layer) const;
 
-	static void getNeighbours(const CMap * map, const TerrainTile & srct, const int3 & tile, std::vector<int3> & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing);
+	bool isAllowedTeleportEntrance(const CGTeleport * obj) const;
+	bool addTeleportTwoWay(const CGTeleport * obj) const;
+	bool addTeleportOneWay(const CGTeleport * obj) const;
+	bool addTeleportOneWayRandom(const CGTeleport * obj) const;
+	bool addTeleportWhirlpool(const CGWhirlpool * obj) const;
+	bool canMoveBetween(const int3 & a, const int3 & b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility)
+
+	void getNeighbours(const TerrainTile & srct, const int3 & tile, std::vector<int3> & vec, const boost::logic::tribool & onLand, const bool limitCoastSailing);
 	
-	static int getMovementCost(
-		const CGHeroInstance * h, 
+	int getMovementCost(
 		const int3 & src, 
 		const int3 & dst, 
 		const TerrainTile * ct,
 		const TerrainTile * dt,
 		const int remainingMovePoints =- 1, 
-		const TurnInfo * ti = nullptr, 
 		const bool checkLast = true);
 
-	static int getMovementCost(
-		const CGHeroInstance * h,
+	int getMovementCost(
 		const CPathNodeInfo & src,
 		const CPathNodeInfo & dst,
 		const int remainingMovePoints = -1,
-		const TurnInfo * ti = nullptr,
 		const bool checkLast = true)
 	{
 		return getMovementCost(
-			h,
 			src.coord,
 			dst.coord,
 			src.tile,
 			dst.tile,
 			remainingMovePoints,
-			ti,
 			checkLast
 		);
 	}
-
-	static int getMovementCost(const CGHeroInstance * h, const int3 & dst);
-
-private:
-	int turn;
-	const CGHeroInstance * hero;
-	std::vector<TurnInfo *> turnsInfo;
-	const CPathfinder::PathfinderOptions & options;
 };

+ 10 - 6
server/CGameHandler.cpp

@@ -2158,10 +2158,14 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
 	tmh.movePoints = h->movement;
 
 	//check if destination tile is available
-	auto ti = make_unique<TurnInfo>(h);
-	const bool canFly = ti->hasBonusOfType(Bonus::FLYING_MOVEMENT);
-	const bool canWalkOnSea = ti->hasBonusOfType(Bonus::WATER_WALKING);
-	const int cost = CPathfinderHelper::getMovementCost(h, h->getPosition(), hmpos, nullptr, nullptr, h->movement, ti.get());
+	auto pathfinderHelper = make_unique<CPathfinderHelper>(gs, h, PathfinderOptions());
+
+	pathfinderHelper->updateTurnInfo(0);
+	auto ti = pathfinderHelper->getTurnInfo();
+
+	const bool canFly = pathfinderHelper->hasBonusOfType(Bonus::FLYING_MOVEMENT);
+	const bool canWalkOnSea = pathfinderHelper->hasBonusOfType(Bonus::WATER_WALKING);
+	const int cost = pathfinderHelper->getMovementCost(h->getPosition(), hmpos, nullptr, nullptr, h->movement);
 
 	//it's a rock or blocked and not visitable tile
 	//OR hero is on land and dest is water and (there is not present only one object - boat)
@@ -2253,14 +2257,14 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
 
 	if (!transit && embarking)
 	{
-		tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, false, ti.get());
+		tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, false, ti);
 		return doMove(TryMoveHero::EMBARK, IGNORE_GUARDS, DONT_VISIT_DEST, LEAVING_TILE);
 		// In H3 embark ignore guards
 	}
 
 	if (disembarking)
 	{
-		tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, true, ti.get());
+		tmh.movePoints = h->movementPointsAfterEmbark(h->movement, cost, true, ti);
 		return doMove(TryMoveHero::DISEMBARK, CHECK_FOR_GUARDS, VISIT_DEST, LEAVING_TILE);
 	}