2023-06-21 12:46:09 +02:00
/*
* 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
{
2023-06-22 13:42:24 +02:00
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 )
2023-06-21 12:46:09 +02:00
{
2023-06-22 13:42:24 +02:00
// 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
2023-06-21 12:46:09 +02:00
2023-06-22 13:42:24 +02:00
destinationCost + = static_cast < float > ( currentMovePointsLeft ) / sourceLayerMaxMovePoints ;
destTurnsUsed + = 1 ;
destMovePointsLeft = sourceLayerMaxMovePoints ;
2023-06-21 12:46:09 +02:00
2023-06-22 13:42:24 +02:00
// update move cost - it might have changed since hero now makes next turn and replenished his pool
moveCostPoints = pathfinderHelper - > getMovementCost ( source , destination , destMovePointsLeft ) ;
2023-06-21 12:46:09 +02:00
2023-06-22 13:42:24 +02:00
pathfinderHelper - > updateTurnInfo ( destTurnsUsed ) ;
2023-06-21 12:46:09 +02:00
}
2023-06-21 14:38:57 +02:00
if ( destination . action = = EPathNodeAction : : EMBARK | | destination . action = = EPathNodeAction : : DISEMBARK )
2023-06-21 12:46:09 +02:00
{
2023-06-22 13:42:24 +02:00
// 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 ;
2023-06-21 12:46:09 +02:00
}
2023-06-22 13:42:24 +02:00
// pathfinder / priority queue does not supports negative costs
assert ( destinationCost > = currentCost ) ;
2023-06-21 12:46:09 +02:00
2023-06-22 13:42:24 +02:00
destination . cost = destinationCost ;
destination . turn = destTurnsUsed ;
destination . movementLeft = destMovePointsLeft ;
2023-06-21 12:46:09 +02:00
if ( destination . isBetterWay ( ) & &
2023-06-22 13:42:24 +02:00
( ( source . node - > turns = = destTurnsUsed & & destMovePointsLeft ) | | pathfinderHelper - > passOneTurnLimitCheck ( source ) ) )
2023-06-21 12:46:09 +02:00
{
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
{
2023-06-21 14:38:57 +02:00
if ( destination . action ! = EPathNodeAction : : UNKNOWN )
2023-06-21 12:46:09 +02:00
{
# ifdef VCMI_TRACE_PATHFINDER
logAi - > trace ( " Accepted precalculated action at %s " , destination . coord . toString ( ) ) ;
# endif
return ;
}
2023-06-21 14:38:57 +02:00
EPathNodeAction action = EPathNodeAction : : NORMAL ;
2023-06-21 12:46:09 +02:00
const auto * hero = pathfinderHelper - > hero ;
2023-11-02 16:29:59 +02:00
switch ( destination . node - > layer . toEnum ( ) )
2023-06-21 12:46:09 +02:00
{
case EPathfindingLayer : : LAND :
if ( source . node - > layer = = EPathfindingLayer : : SAIL )
{
// TODO: Handle dismebark into guarded areaa
2023-06-21 14:38:57 +02:00
action = EPathNodeAction : : DISEMBARK ;
2023-06-21 12:46:09 +02:00
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 )
2023-06-21 14:38:57 +02:00
action = EPathNodeAction : : EMBARK ;
2023-06-21 12:46:09 +02:00
else if ( destination . nodeHero )
{
if ( destination . heroRelations = = PlayerRelations : : ENEMIES )
2023-06-21 14:38:57 +02:00
action = EPathNodeAction : : BATTLE ;
2023-06-21 12:46:09 +02:00
else
2023-06-21 14:38:57 +02:00
action = EPathNodeAction : : BLOCKING_VISIT ;
2023-06-21 12:46:09 +02:00
}
else if ( destination . nodeObject - > ID = = Obj : : TOWN )
{
if ( destination . nodeObject - > passableFor ( hero - > tempOwner ) )
2023-06-21 14:38:57 +02:00
action = EPathNodeAction : : VISIT ;
2023-06-21 12:46:09 +02:00
else if ( objRel = = PlayerRelations : : ENEMIES )
2023-06-21 14:38:57 +02:00
action = EPathNodeAction : : BATTLE ;
2023-06-21 12:46:09 +02:00
}
else if ( destination . nodeObject - > ID = = Obj : : GARRISON | | destination . nodeObject - > ID = = Obj : : GARRISON2 )
{
if ( destination . nodeObject - > passableFor ( hero - > tempOwner ) )
{
if ( destination . guarded )
2023-06-21 14:38:57 +02:00
action = EPathNodeAction : : BATTLE ;
2023-06-21 12:46:09 +02:00
}
else if ( objRel = = PlayerRelations : : ENEMIES )
2023-06-21 14:38:57 +02:00
action = EPathNodeAction : : BATTLE ;
2023-06-21 12:46:09 +02:00
}
else if ( destination . nodeObject - > ID = = Obj : : BORDER_GATE )
{
if ( destination . nodeObject - > passableFor ( hero - > tempOwner ) )
{
if ( destination . guarded )
2023-06-21 14:38:57 +02:00
action = EPathNodeAction : : BATTLE ;
2023-06-21 12:46:09 +02:00
}
else
2023-06-21 14:38:57 +02:00
action = EPathNodeAction : : BLOCKING_VISIT ;
2023-06-21 12:46:09 +02:00
}
else if ( destination . isGuardianTile )
2023-06-21 14:38:57 +02:00
action = EPathNodeAction : : BATTLE ;
2023-06-21 15:49:44 +02:00
else if ( destination . nodeObject - > isBlockedVisitable ( ) & & ! ( pathfinderConfig - > options . useCastleGate & & destination . nodeObject - > ID = = Obj : : TOWN ) )
2023-06-21 14:38:57 +02:00
action = EPathNodeAction : : BLOCKING_VISIT ;
2023-06-21 12:46:09 +02:00
2023-06-21 14:38:57 +02:00
if ( action = = EPathNodeAction : : NORMAL )
2023-06-21 12:46:09 +02:00
{
if ( destination . guarded )
2023-06-21 14:38:57 +02:00
action = EPathNodeAction : : BATTLE ;
2023-06-21 12:46:09 +02:00
else
2023-06-21 14:38:57 +02:00
action = EPathNodeAction : : VISIT ;
2023-06-21 12:46:09 +02:00
}
}
else if ( destination . guarded )
2023-06-21 14:38:57 +02:00
action = EPathNodeAction : : BATTLE ;
2023-06-21 12:46:09 +02:00
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 ) ;
2023-06-21 14:38:57 +02:00
if ( blocker = = BlockingReason : : DESTINATION_GUARDED & & destination . action = = EPathNodeAction : : BATTLE )
2023-06-21 12:46:09 +02:00
{
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
2023-06-21 14:38:57 +02:00
case EPathNodeAction : : VISIT :
2023-06-21 12:46:09 +02:00
{
/// 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 ;
}
2023-06-21 14:38:57 +02:00
case EPathNodeAction : : BLOCKING_VISIT :
2023-12-23 15:53:51 +02:00
return BlockingReason : : DESTINATION_BLOCKVIS ;
2023-06-21 12:46:09 +02:00
2023-06-21 14:38:57 +02:00
case EPathNodeAction : : NORMAL :
2023-06-21 12:46:09 +02:00
return BlockingReason : : NONE ;
2023-06-21 14:38:57 +02:00
case EPathNodeAction : : EMBARK :
2023-06-21 12:46:09 +02:00
if ( pathfinderHelper - > options . useEmbarkAndDisembark )
return BlockingReason : : NONE ;
return BlockingReason : : DESTINATION_BLOCKED ;
2023-06-21 14:38:57 +02:00
case EPathNodeAction : : DISEMBARK :
2023-06-21 12:46:09 +02:00
if ( pathfinderHelper - > options . useEmbarkAndDisembark )
return destination . guarded ? BlockingReason : : DESTINATION_GUARDED : BlockingReason : : NONE ;
return BlockingReason : : DESTINATION_BLOCKED ;
2023-06-21 14:38:57 +02:00
case EPathNodeAction : : BATTLE :
2024-05-23 20:46:48 +02:00
if ( config - > options . originalFlyRules & & destination . nodeObject & & source . node - > layer = = EPathfindingLayer : : AIR )
return BlockingReason : : DESTINATION_BLOCKED ;
2023-06-21 12:46:09 +02:00
/// Movement after BATTLE action only possible from guarded tile to guardian tile
if ( destination . guarded )
2024-01-13 19:44:37 +02:00
{
if ( pathfinderHelper - > options . ignoreGuards )
return BlockingReason : : NONE ;
2024-01-24 07:20:46 +02:00
else
return BlockingReason : : DESTINATION_GUARDED ;
2024-01-13 19:44:37 +02:00
}
2023-06-21 12:46:09 +02:00
break ;
}
return BlockingReason : : DESTINATION_BLOCKED ;
}
PathfinderBlockingRule : : BlockingReason MovementToDestinationRule : : getBlockingReason (
const PathNodeInfo & source ,
const CDestinationNodeInfo & destination ,
const PathfinderConfig * pathfinderConfig ,
const CPathfinderHelper * pathfinderHelper ) const
{
2023-06-21 14:38:57 +02:00
if ( destination . node - > accessible = = EPathAccessibility : : BLOCKED )
2023-06-21 12:46:09 +02:00
return BlockingReason : : DESTINATION_BLOCKED ;
2023-11-02 16:29:59 +02:00
switch ( destination . node - > layer . toEnum ( ) )
2023-06-21 12:46:09 +02:00
{
case EPathfindingLayer : : LAND :
if ( ! pathfinderHelper - > canMoveBetween ( source . coord , destination . coord ) )
return BlockingReason : : DESTINATION_BLOCKED ;
if ( source . guarded )
{
2024-02-07 20:27:02 +02:00
if ( ! ( pathfinderConfig - > options . originalFlyRules & & source . node - > layer = = EPathfindingLayer : : AIR )
2024-01-13 19:44:37 +02:00
& & ! pathfinderConfig - > options . ignoreGuards
2023-09-16 11:29:35 +02:00
& & ( ! destination . isGuardianTile | | pathfinderHelper - > getGuardiansCount ( source . coord ) > 1 ) ) // Can step into tile of guard
2023-06-21 12:46:09 +02:00
{
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
2023-06-21 14:38:57 +02:00
if ( source . node - > action ! = EPathNodeAction : : EMBARK & & ! destination . isGuardianTile )
2023-06-21 12:46:09 +02:00
return BlockingReason : : SOURCE_GUARDED ;
}
if ( source . node - > layer = = EPathfindingLayer : : LAND )
{
if ( ! destination . isNodeObjectVisitable ( ) )
return BlockingReason : : DESTINATION_BLOCKED ;
2023-06-21 15:49:44 +02:00
if ( ! destination . nodeHero & & ! destination . nodeObject - > isCoastVisitable ( ) )
2023-06-21 12:46:09 +02:00
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 :
2024-05-23 20:47:13 +02:00
if ( ! pathfinderHelper - > canMoveBetween ( source . coord , destination . coord ) )
return BlockingReason : : DESTINATION_BLOCKED ;
if ( destination . node - > accessible ! = EPathAccessibility : : ACCESSIBLE )
2023-06-21 12:46:09 +02:00
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 ;
2023-11-02 16:29:59 +02:00
switch ( source . node - > layer . toEnum ( ) )
2023-06-21 12:46:09 +02:00
{
case EPathfindingLayer : : LAND :
if ( destination . node - > layer = = EPathfindingLayer : : SAIL )
{
/// Cannot enter empty water tile from land -> it has to be visitable
2023-06-21 14:38:57 +02:00
if ( destination . node - > accessible = = EPathAccessibility : : ACCESSIBLE )
2023-06-21 12:46:09 +02:00
destination . blocked = true ;
}
break ;
case EPathfindingLayer : : SAIL :
//tile must be accessible -> exception: unblocked blockvis tiles -> clear but guarded by nearby monster coast
2024-05-23 20:46:03 +02:00
if ( ( destination . node - > accessible ! = EPathAccessibility : : ACCESSIBLE & & destination . node - > accessible ! = EPathAccessibility : : GUARDED )
2023-06-21 12:46:09 +02:00
| | 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 :
2024-02-07 20:27:02 +02:00
if ( pathfinderConfig - > options . originalFlyRules )
2023-06-21 12:46:09 +02:00
{
2024-01-18 21:42:08 +02:00
if ( source . node - > accessible ! = EPathAccessibility : : ACCESSIBLE & &
source . node - > accessible ! = EPathAccessibility : : VISITABLE & &
destination . node - > accessible ! = EPathAccessibility : : VISITABLE & &
destination . node - > accessible ! = EPathAccessibility : : ACCESSIBLE )
2023-06-21 12:46:09 +02:00
{
2024-01-18 21:42:08 +02:00
if ( destination . node - > accessible = = EPathAccessibility : : BLOCKVIS )
{
2024-02-07 20:27:02 +02:00
if ( source . nodeObject | | ( source . tile - > blocked & & destination . tile - > blocked ) )
2024-01-18 21:42:08 +02:00
{
destination . blocked = true ;
}
}
else
destination . blocked = true ;
2023-06-21 12:46:09 +02:00
}
}
2023-06-21 14:38:57 +02:00
else if ( destination . node - > accessible ! = EPathAccessibility : : ACCESSIBLE )
2023-06-21 12:46:09 +02:00
{
/// Hero that fly can only land on accessible tiles
if ( destination . nodeObject )
destination . blocked = true ;
}
break ;
case EPathfindingLayer : : WATER :
2023-06-21 14:38:57 +02:00
if ( destination . node - > accessible ! = EPathAccessibility : : ACCESSIBLE & & destination . node - > accessible ! = EPathAccessibility : : VISITABLE )
2023-06-21 12:46:09 +02:00
{
/// 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