| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426 | 
							- /*
 
-  * 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
 
- {
 
- 	const float currentCost = destination.cost;
 
- 	const int currentTurnsUsed = destination.turn;
 
- 	const int currentMovePointsLeft = destination.movementLeft;
 
- 	const int sourceLayerMaxMovePoints = pathfinderHelper->getMaxMovePoints(source.node->layer);
 
- 	int moveCostPoints = pathfinderHelper->getMovementCost(source, destination, currentMovePointsLeft);
 
- 	float destinationCost = currentCost;
 
- 	int destTurnsUsed = currentTurnsUsed;
 
- 	int destMovePointsLeft = currentMovePointsLeft;
 
- 	if(currentMovePointsLeft < moveCostPoints)
 
- 	{
 
- 		// occurs rarely, when hero with low movepoints tries to leave the road
 
- 		// in this case, all remaining movement points from current turn are spent
 
- 		// and actual movement will happen on next turn, spending points from next turn pool
 
- 		destinationCost += static_cast<float>(currentMovePointsLeft) / sourceLayerMaxMovePoints;
 
- 		destTurnsUsed += 1;
 
- 		destMovePointsLeft = sourceLayerMaxMovePoints;
 
- 		// update move cost - it might have changed since hero now makes next turn and replenished his pool
 
- 		moveCostPoints = pathfinderHelper->getMovementCost(source, destination, destMovePointsLeft);
 
- 		pathfinderHelper->updateTurnInfo(destTurnsUsed);
 
- 	}
 
- 	if(destination.action == EPathNodeAction::EMBARK || destination.action == EPathNodeAction::DISEMBARK)
 
- 	{
 
- 		// FREE_SHIP_BOARDING bonus only remove additional penalty
 
- 		// land <-> sail transition still cost movement points as normal movement
 
- 		const int movementPointsAfterEmbark = pathfinderHelper->movementPointsAfterEmbark(destMovePointsLeft, moveCostPoints, (destination.action == EPathNodeAction::DISEMBARK));
 
- 		const int destinationLayerMaxMovePoints = pathfinderHelper->getMaxMovePoints(destination.node->layer);
 
- 		const float costBeforeConversion = static_cast<float>(destMovePointsLeft) / sourceLayerMaxMovePoints;
 
- 		const float costAfterConversion = static_cast<float>(movementPointsAfterEmbark) / destinationLayerMaxMovePoints;
 
- 		const float costDelta = costBeforeConversion - costAfterConversion;
 
- 		assert(costDelta >= 0);
 
- 		destMovePointsLeft = movementPointsAfterEmbark;
 
- 		destinationCost += costDelta;
 
- 	}
 
- 	else
 
- 	{
 
- 		// Standard movement
 
- 		assert(destMovePointsLeft >= moveCostPoints);
 
- 		destMovePointsLeft -= moveCostPoints;
 
- 		destinationCost += static_cast<float>(moveCostPoints) / sourceLayerMaxMovePoints;
 
- 	}
 
- 	// pathfinder / priority queue does not supports negative costs
 
- 	assert(destinationCost >= currentCost);
 
- 	destination.cost = destinationCost;
 
- 	destination.turn = destTurnsUsed;
 
- 	destination.movementLeft = destMovePointsLeft;
 
- 	if(destination.isBetterWay() &&
 
- 		((source.node->turns == destTurnsUsed && destMovePointsLeft) || pathfinderHelper->passOneTurnLimitCheck(source)))
 
- 	{
 
- 		pathfinderConfig->nodeStorage->commit(destination, source);
 
- 		return;
 
- 	}
 
- 	destination.blocked = true;
 
- }
 
- void PathfinderBlockingRule::process(
 
- 	const PathNodeInfo & source,
 
- 	CDestinationNodeInfo & destination,
 
- 	const PathfinderConfig * pathfinderConfig,
 
- 	CPathfinderHelper * pathfinderHelper) const
 
- {
 
- 	auto blockingReason = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper);
 
- 	destination.blocked = blockingReason != BlockingReason::NONE;
 
- }
 
- void DestinationActionRule::process(
 
- 	const PathNodeInfo & source,
 
- 	CDestinationNodeInfo & destination,
 
- 	const PathfinderConfig * pathfinderConfig,
 
- 	CPathfinderHelper * pathfinderHelper) const
 
- {
 
- 	if(destination.action != EPathNodeAction::UNKNOWN)
 
- 	{
 
- #ifdef VCMI_TRACE_PATHFINDER
 
- 		logAi->trace("Accepted precalculated action at %s", destination.coord.toString());
 
- #endif
 
- 		return;
 
- 	}
 
- 	EPathNodeAction action = EPathNodeAction::NORMAL;
 
- 	const auto * hero = pathfinderHelper->hero;
 
- 	switch(destination.node->layer.toEnum())
 
- 	{
 
- 	case EPathfindingLayer::LAND:
 
- 		if(source.node->layer == EPathfindingLayer::SAIL)
 
- 		{
 
- 			// TODO: Handle dismebark into guarded areaa
 
- 			action = EPathNodeAction::DISEMBARK;
 
- 			break;
 
- 		}
 
- 		/// don't break - next case shared for both land and sail layers
 
- 		[[fallthrough]];
 
- 	case EPathfindingLayer::SAIL:
 
- 		if(destination.isNodeObjectVisitable())
 
- 		{
 
- 			auto objRel = destination.objectRelations;
 
- 			if(destination.nodeObject->ID == Obj::BOAT)
 
- 				action = EPathNodeAction::EMBARK;
 
- 			else if(destination.nodeHero)
 
- 			{
 
- 				if(destination.heroRelations == PlayerRelations::ENEMIES)
 
- 					action = EPathNodeAction::BATTLE;
 
- 				else
 
- 					action = EPathNodeAction::BLOCKING_VISIT;
 
- 			}
 
- 			else if(destination.nodeObject->ID == Obj::TOWN)
 
- 			{
 
- 				if(destination.nodeObject->passableFor(hero->tempOwner))
 
- 					action = EPathNodeAction::VISIT;
 
- 				else if(objRel == PlayerRelations::ENEMIES)
 
- 					action = EPathNodeAction::BATTLE;
 
- 			}
 
- 			else if(destination.nodeObject->ID == Obj::GARRISON || destination.nodeObject->ID == Obj::GARRISON2)
 
- 			{
 
- 				if(destination.nodeObject->passableFor(hero->tempOwner))
 
- 				{
 
- 					if(destination.guarded)
 
- 						action = EPathNodeAction::BATTLE;
 
- 				}
 
- 				else if(objRel == PlayerRelations::ENEMIES)
 
- 					action = EPathNodeAction::BATTLE;
 
- 			}
 
- 			else if(destination.nodeObject->ID == Obj::BORDER_GATE)
 
- 			{
 
- 				if(destination.nodeObject->passableFor(hero->tempOwner))
 
- 				{
 
- 					if(destination.guarded)
 
- 						action = EPathNodeAction::BATTLE;
 
- 				}
 
- 				else
 
- 					action = EPathNodeAction::BLOCKING_VISIT;
 
- 			}
 
- 			else if(destination.isGuardianTile)
 
- 				action = EPathNodeAction::BATTLE;
 
- 			else if(destination.nodeObject->isBlockedVisitable() && !(pathfinderConfig->options.useCastleGate && destination.nodeObject->ID == Obj::TOWN))
 
- 				action = EPathNodeAction::BLOCKING_VISIT;
 
- 			if(action == EPathNodeAction::NORMAL)
 
- 			{
 
- 				if(destination.guarded)
 
- 					action = EPathNodeAction::BATTLE;
 
- 				else
 
- 					action = EPathNodeAction::VISIT;
 
- 			}
 
- 		}
 
- 		else if(destination.guarded)
 
- 			action = EPathNodeAction::BATTLE;
 
- 		break;
 
- 	}
 
- 	destination.action = action;
 
- }
 
- void MovementAfterDestinationRule::process(
 
- 	const PathNodeInfo & source,
 
- 	CDestinationNodeInfo & destination,
 
- 	const PathfinderConfig * config,
 
- 	CPathfinderHelper * pathfinderHelper) const
 
- {
 
- 	auto blocker = getBlockingReason(source, destination, config, pathfinderHelper);
 
- 	if(blocker == BlockingReason::DESTINATION_GUARDED && destination.action == EPathNodeAction::BATTLE)
 
- 	{
 
- 		return; // allow bypass guarded tile but only in direction of guard, a bit UI related thing
 
- 	}
 
- 	destination.blocked = blocker != BlockingReason::NONE;
 
- }
 
- PathfinderBlockingRule::BlockingReason MovementAfterDestinationRule::getBlockingReason(
 
- 	const PathNodeInfo & source,
 
- 	const CDestinationNodeInfo & destination,
 
- 	const PathfinderConfig * config,
 
- 	const CPathfinderHelper * pathfinderHelper) const
 
- {
 
- 	switch(destination.action)
 
- 	{
 
- 	/// TODO: Investigate what kind of limitation is possible to apply on movement from visitable tiles
 
- 	/// Likely in many cases we don't need to add visitable tile to queue when hero doesn't fly
 
- 	case EPathNodeAction::VISIT:
 
- 	{
 
- 		/// For now we only add visitable tile into queue when it's teleporter that allow transit
 
- 		/// Movement from visitable tile when hero is standing on it is possible into any layer
 
- 		const auto * objTeleport = dynamic_cast<const CGTeleport *>(destination.nodeObject);
 
- 		if(pathfinderHelper->isAllowedTeleportEntrance(objTeleport))
 
- 		{
 
- 			/// For now we'll always allow transit over teleporters
 
- 			/// Transit over whirlpools only allowed when hero is protected
 
- 			return BlockingReason::NONE;
 
- 		}
 
- 		else if(destination.nodeObject->ID == Obj::GARRISON
 
- 			|| destination.nodeObject->ID == Obj::GARRISON2
 
- 			|| destination.nodeObject->ID == Obj::BORDER_GATE)
 
- 		{
 
- 			/// Transit via unguarded garrisons is always possible
 
- 			return BlockingReason::NONE;
 
- 		}
 
- 		return BlockingReason::DESTINATION_VISIT;
 
- 	}
 
- 	case EPathNodeAction::BLOCKING_VISIT:
 
- 		return BlockingReason::DESTINATION_BLOCKVIS;
 
- 	case EPathNodeAction::NORMAL:
 
- 		return BlockingReason::NONE;
 
- 	case EPathNodeAction::EMBARK:
 
- 		if(pathfinderHelper->options.useEmbarkAndDisembark)
 
- 			return BlockingReason::NONE;
 
- 		return BlockingReason::DESTINATION_BLOCKED;
 
- 	case EPathNodeAction::DISEMBARK:
 
- 		if(pathfinderHelper->options.useEmbarkAndDisembark)
 
- 			return destination.guarded ? BlockingReason::DESTINATION_GUARDED : BlockingReason::NONE;
 
- 		return BlockingReason::DESTINATION_BLOCKED;
 
- 	case EPathNodeAction::BATTLE:
 
- 		// H3 rule: do not allow direct attack on wandering monsters if hero lands on visitable object
 
- 		if (config->options.originalFlyRules && destination.nodeObject && source.node->layer == EPathfindingLayer::AIR)
 
- 			return BlockingReason::DESTINATION_BLOCKED;
 
- 		// Movement after BATTLE action only possible from guarded tile to guardian tile
 
- 		if(destination.guarded)
 
- 		{
 
- 			if (pathfinderHelper->options.ignoreGuards)
 
- 				return BlockingReason::NONE;
 
- 			else
 
- 				return BlockingReason::DESTINATION_GUARDED;
 
- 		}
 
- 		break;
 
- 	}
 
- 	return BlockingReason::DESTINATION_BLOCKED;
 
- }
 
- PathfinderBlockingRule::BlockingReason MovementToDestinationRule::getBlockingReason(
 
- 	const PathNodeInfo & source,
 
- 	const CDestinationNodeInfo & destination,
 
- 	const PathfinderConfig * pathfinderConfig,
 
- 	const CPathfinderHelper * pathfinderHelper) const
 
- {
 
- 	if(destination.node->accessible == EPathAccessibility::BLOCKED)
 
- 		return BlockingReason::DESTINATION_BLOCKED;
 
- 	switch(destination.node->layer.toEnum())
 
- 	{
 
- 	case EPathfindingLayer::LAND:
 
- 		if(!pathfinderHelper->canMoveBetween(source.coord, destination.coord))
 
- 			return BlockingReason::DESTINATION_BLOCKED;
 
- 		if(source.guarded)
 
- 		{
 
- 			if(source.node->layer != EPathfindingLayer::AIR // zone of control is ignored when flying
 
- 				&& !pathfinderConfig->options.ignoreGuards
 
- 				&&	(!destination.isGuardianTile || pathfinderHelper->getGuardiansCount(source.coord) > 1)) // Can step into tile of guard
 
- 			{
 
- 				return BlockingReason::SOURCE_GUARDED;
 
- 			}
 
- 		}
 
- 		break;
 
- 	case EPathfindingLayer::SAIL:
 
- 		if(!pathfinderHelper->canMoveBetween(source.coord, destination.coord))
 
- 			return BlockingReason::DESTINATION_BLOCKED;
 
- 		if(source.guarded)
 
- 		{
 
- 			// Hero embarked a boat standing on a guarded tile -> we must allow to move away from that tile
 
- 			if(source.node->action != EPathNodeAction::EMBARK && !destination.isGuardianTile)
 
- 				return BlockingReason::SOURCE_GUARDED;
 
- 		}
 
- 		if(source.node->layer == EPathfindingLayer::LAND)
 
- 		{
 
- 			if(!destination.isNodeObjectVisitable())
 
- 				return BlockingReason::DESTINATION_BLOCKED;
 
- 			if(!destination.nodeHero && !destination.nodeObject->isCoastVisitable())
 
- 				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))
 
- 			return BlockingReason::DESTINATION_BLOCKED;
 
- 		if (destination.node->accessible != EPathAccessibility::ACCESSIBLE)
 
- 			return BlockingReason::DESTINATION_BLOCKED;
 
- 		if(destination.guarded)
 
- 			return BlockingReason::DESTINATION_BLOCKED;
 
- 		break;
 
- 	}
 
- 	return BlockingReason::NONE;
 
- }
 
- void LayerTransitionRule::process(
 
- 	const PathNodeInfo & source,
 
- 	CDestinationNodeInfo & destination,
 
- 	const PathfinderConfig * pathfinderConfig,
 
- 	CPathfinderHelper * pathfinderHelper) const
 
- {
 
- 	if(source.node->layer == destination.node->layer)
 
- 		return;
 
- 	switch(source.node->layer.toEnum())
 
- 	{
 
- 	case EPathfindingLayer::LAND:
 
- 		if(destination.node->layer == EPathfindingLayer::SAIL)
 
- 		{
 
- 			/// Cannot enter empty water tile from land -> it has to be visitable
 
- 			if(destination.node->accessible == EPathAccessibility::ACCESSIBLE)
 
- 				destination.blocked = true;
 
- 		}
 
- 		break;
 
- 	case EPathfindingLayer::SAIL:
 
- 		// have to disembark first before visiting objects on land
 
- 		if (destination.tile->visitable())
 
- 			destination.blocked = true;
 
- 		//can disembark only on accessible tiles or tiles guarded by nearby monster
 
- 		if((destination.node->accessible != EPathAccessibility::ACCESSIBLE && destination.node->accessible != EPathAccessibility::GUARDED))
 
- 			destination.blocked = true;
 
- 		break;
 
- 	case EPathfindingLayer::AIR:
 
- 		if(pathfinderConfig->options.originalFlyRules)
 
- 		{
 
- 			if(source.node->accessible != EPathAccessibility::ACCESSIBLE && source.node->accessible != EPathAccessibility::VISITABLE)
 
- 			{
 
- 				if (destination.node->accessible == EPathAccessibility::BLOCKVIS)
 
- 				{
 
- 					// Can't visit 'blockvisit' objects on coast if hero will end up on water terrain
 
- 					if (source.tile->blocked() || !destination.tile->entrableTerrain(source.tile))
 
- 						destination.blocked = true;
 
- 				}
 
- 			}
 
- 		}
 
- 		else
 
- 		{
 
- 			// Hero that fly can only land on accessible tiles
 
- 			if(destination.node->accessible != EPathAccessibility::ACCESSIBLE && destination.nodeObject)
 
- 				destination.blocked = true;
 
- 		}
 
- 		break;
 
- 	case EPathfindingLayer::WATER:
 
- 		if(destination.node->accessible != EPathAccessibility::ACCESSIBLE && destination.node->accessible != EPathAccessibility::VISITABLE)
 
- 		{
 
- 			/// Hero that walking on water can transit to accessible and visitable tiles
 
- 			/// Though hero can't interact with blocking visit objects while standing on water
 
- 			destination.blocked = true;
 
- 		}
 
- 		break;
 
- 	}
 
- }
 
- VCMI_LIB_NAMESPACE_END
 
 
  |