2017-07-13 10:26:03 +02:00
/*
* 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
*
*/
2015-10-27 02:34:47 +02:00
# include "StdInc.h"
# include "CPathfinder.h"
# include "CHeroHandler.h"
# include "mapping/CMap.h"
2015-10-27 16:42:31 +02:00
# include "CGameState.h"
# include "mapObjects/CGHeroInstance.h"
2015-10-27 02:34:47 +02:00
# include "GameConstants.h"
# include "CStopWatch.h"
2015-11-20 23:32:23 +02:00
# include "CConfigHandler.h"
2019-01-15 05:00:00 +02:00
# include "CPlayerState.h"
# include "PathfinderUtil.h"
2015-10-27 02:34:47 +02:00
2018-07-29 13:50:22 +02:00
bool canSeeObj ( const CGObjectInstance * obj )
{
/// Pathfinder should ignore placed events
return obj ! = nullptr & & obj - > ID ! = Obj : : EVENT ;
}
2021-05-16 19:53:11 +02:00
void NodeStorage : : initialize ( const PathfinderOptions & options , const CGameState * gs )
2019-01-15 05:00:00 +02:00
{
//TODO: fix this code duplication with AINodeStorage::initialize, problem is to keep `resetTile` inline
int3 pos ;
2021-05-16 19:53:11 +02:00
const PlayerColor player = out . hero - > tempOwner ;
2019-01-15 05:00:00 +02:00
const int3 sizes = gs - > getMapSize ( ) ;
2021-05-16 19:53:11 +02:00
const auto & fow = static_cast < const CGameInfoCallback * > ( gs ) - > getPlayerTeam ( player ) - > fogOfWarMap ;
2019-01-15 05:00:00 +02:00
//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 . x = 0 ; pos . x < sizes . x ; + + pos . x )
{
for ( pos . y = 0 ; pos . y < sizes . y ; + + pos . y )
{
for ( pos . z = 0 ; pos . z < sizes . z ; + + pos . z )
{
const TerrainTile * tile = & gs - > map - > getTile ( pos ) ;
2022-06-20 16:39:50 +02:00
if ( tile - > terType . isWater ( ) )
2019-01-15 05:00:00 +02:00
{
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 ) ) ;
2022-06-20 16:39:50 +02:00
}
if ( tile - > terType . isLand ( ) )
{
2019-01-15 05:00:00 +02:00
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 ) ) ;
}
}
}
}
}
2018-10-07 13:51:27 +02:00
std : : vector < CGPathNode * > NodeStorage : : calculateNeighbours (
const PathNodeInfo & source ,
const PathfinderConfig * pathfinderConfig ,
const CPathfinderHelper * pathfinderHelper )
2018-07-29 13:50:22 +02:00
{
2018-08-01 22:21:14 +02:00
std : : vector < CGPathNode * > neighbours ;
2018-12-16 16:46:48 +02:00
neighbours . reserve ( 16 ) ;
2018-08-12 13:31:31 +02:00
auto accessibleNeighbourTiles = pathfinderHelper - > getNeighbourTiles ( source ) ;
2018-07-29 13:50:22 +02:00
for ( auto & neighbour : accessibleNeighbourTiles )
{
for ( EPathfindingLayer i = EPathfindingLayer : : LAND ; i < = EPathfindingLayer : : AIR ; i . advance ( 1 ) )
{
2018-08-12 13:31:31 +02:00
auto node = getNode ( neighbour , i ) ;
2018-07-29 13:50:22 +02:00
if ( node - > accessible = = CGPathNode : : NOT_SET )
continue ;
neighbours . push_back ( node ) ;
}
}
return neighbours ;
}
2018-10-07 13:51:27 +02:00
std : : vector < CGPathNode * > NodeStorage : : calculateTeleportations (
const PathNodeInfo & source ,
const PathfinderConfig * pathfinderConfig ,
const CPathfinderHelper * pathfinderHelper )
2018-07-29 13:50:22 +02:00
{
2018-08-01 22:21:14 +02:00
std : : vector < CGPathNode * > neighbours ;
2019-01-07 23:33:31 +02:00
if ( ! source . isNodeObjectVisitable ( ) )
return neighbours ;
2018-08-12 13:31:31 +02:00
auto accessibleExits = pathfinderHelper - > getTeleportExits ( source ) ;
2018-08-01 22:21:14 +02:00
for ( auto & neighbour : accessibleExits )
{
2018-08-12 13:31:31 +02:00
auto node = getNode ( neighbour , source . node - > layer ) ;
2018-08-01 22:21:14 +02:00
neighbours . push_back ( node ) ;
}
return neighbours ;
}
2018-10-07 13:51:27 +02:00
std : : vector < int3 > CPathfinderHelper : : getNeighbourTiles ( const PathNodeInfo & source ) const
2018-08-01 22:21:14 +02:00
{
std : : vector < int3 > neighbourTiles ;
2018-12-16 16:46:48 +02:00
neighbourTiles . reserve ( 16 ) ;
2018-07-29 13:50:22 +02:00
2018-08-12 13:31:31 +02:00
getNeighbours (
2018-08-01 20:46:06 +02:00
* source . tile ,
source . node - > coord ,
neighbourTiles ,
boost : : logic : : indeterminate ,
source . node - > layer = = EPathfindingLayer : : SAIL ) ;
2018-07-29 13:50:22 +02:00
2018-08-01 20:46:06 +02:00
if ( source . isNodeObjectVisitable ( ) )
2018-07-29 13:50:22 +02:00
{
2018-08-01 22:21:14 +02:00
vstd : : erase_if ( neighbourTiles , [ & ] ( int3 tile ) - > bool
2018-07-29 13:50:22 +02:00
{
2018-08-12 13:31:31 +02:00
return ! canMoveBetween ( tile , source . nodeObject - > visitablePos ( ) ) ;
2018-08-01 22:21:14 +02:00
} ) ;
2018-07-29 13:50:22 +02:00
}
2019-01-15 05:00:00 +02:00
2018-08-01 22:21:14 +02:00
return neighbourTiles ;
2018-07-29 13:50:22 +02:00
}
2018-10-07 13:51:27 +02:00
NodeStorage : : NodeStorage ( CPathsInfo & pathsInfo , const CGHeroInstance * hero )
2018-08-12 13:31:31 +02:00
: out ( pathsInfo )
2018-07-29 13:50:22 +02:00
{
2018-08-12 13:31:31 +02:00
out . hero = hero ;
out . hpos = hero - > getPosition ( false ) ;
}
2018-07-29 13:50:22 +02:00
2018-10-07 13:51:27 +02:00
void NodeStorage : : resetTile (
2018-08-12 13:31:31 +02:00
const int3 & tile ,
EPathfindingLayer layer ,
CGPathNode : : EAccessibility accessibility )
{
getNode ( tile , layer ) - > update ( tile , layer , accessibility ) ;
}
2018-07-28 14:30:57 +02:00
2021-05-16 19:53:11 +02:00
std : : vector < CGPathNode * > NodeStorage : : getInitialNodes ( )
2018-08-12 13:31:31 +02:00
{
2021-05-16 19:53:11 +02:00
auto initialNode = getNode ( out . hpos , out . hero - > boat ? EPathfindingLayer : : SAIL : EPathfindingLayer : : LAND ) ;
2018-07-29 13:50:22 +02:00
2018-08-12 13:31:31 +02:00
initialNode - > turns = 0 ;
initialNode - > moveRemains = out . hero - > movement ;
2020-09-21 23:19:40 +02:00
initialNode - > setCost ( 0.0 ) ;
2018-07-29 13:50:22 +02:00
2021-05-16 19:53:11 +02:00
if ( ! initialNode - > coord . valid ( ) )
{
initialNode - > coord = out . hpos ;
}
return std : : vector < CGPathNode * > { initialNode } ;
2018-08-12 13:31:31 +02:00
}
2018-07-29 13:50:22 +02:00
2018-10-07 13:51:27 +02:00
void NodeStorage : : commit ( CDestinationNodeInfo & destination , const PathNodeInfo & source )
2018-08-12 13:31:31 +02:00
{
assert ( destination . node ! = source . node - > theNodeBefore ) ; //two tiles can't point to each other
2020-09-21 23:19:40 +02:00
destination . node - > setCost ( destination . cost ) ;
2018-08-12 13:31:31 +02:00
destination . node - > moveRemains = destination . movementLeft ;
destination . node - > turns = destination . turn ;
destination . node - > theNodeBefore = source . node ;
destination . node - > action = destination . action ;
}
2018-07-29 13:50:22 +02:00
2018-08-01 20:46:06 +02:00
PathfinderOptions : : PathfinderOptions ( )
2015-10-27 02:34:47 +02:00
{
2015-11-20 23:32:23 +02:00
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 ( ) ;
2015-11-04 14:05:22 +02:00
2015-11-20 23:32:23 +02:00
useCastleGate = settings [ " pathfinder " ] [ " teleports " ] [ " castleGate " ] . Bool ( ) ;
2015-11-08 09:06:24 +02:00
2015-11-20 23:32:23 +02:00
lightweightFlyingMode = settings [ " pathfinder " ] [ " lightweightFlyingMode " ] . Bool ( ) ;
oneTurnSpecialLayersLimit = settings [ " pathfinder " ] [ " oneTurnSpecialLayersLimit " ] . Bool ( ) ;
originalMovementRules = settings [ " pathfinder " ] [ " originalMovementRules " ] . Bool ( ) ;
2015-10-27 02:34:47 +02:00
}
2018-10-07 13:51:27 +02:00
void MovementCostRule : : process (
const PathNodeInfo & source ,
2018-08-11 21:39:42 +02:00
CDestinationNodeInfo & destination ,
2018-10-07 13:51:27 +02:00
const PathfinderConfig * pathfinderConfig ,
2018-08-12 13:31:31 +02:00
CPathfinderHelper * pathfinderHelper ) const
2018-08-08 21:24:18 +02:00
{
2019-01-15 07:52:55 +02:00
float costAtNextTile = destination . cost ;
int turnAtNextTile = destination . turn ;
int moveAtNextTile = destination . movementLeft ;
2018-08-11 21:39:42 +02:00
int cost = pathfinderHelper - > getMovementCost ( source , destination , moveAtNextTile ) ;
int remains = moveAtNextTile - cost ;
2021-07-26 22:56:47 +02:00
int sourceLayerMaxMovePoints = pathfinderHelper - > getMaxMovePoints ( source . node - > layer ) ;
2018-08-11 21:39:42 +02:00
if ( remains < 0 )
2018-08-08 21:24:18 +02:00
{
2018-08-11 21:39:42 +02:00
//occurs rarely, when hero with low movepoints tries to leave the road
2021-07-26 22:56:47 +02:00
costAtNextTile + = static_cast < float > ( moveAtNextTile ) / sourceLayerMaxMovePoints ; //we spent all points of current turn
2018-08-11 21:39:42 +02:00
pathfinderHelper - > updateTurnInfo ( + + turnAtNextTile ) ;
2019-01-15 07:52:55 +02:00
2021-07-26 22:56:47 +02:00
int destinationLayerMaxMovePoints = pathfinderHelper - > getMaxMovePoints ( destination . node - > layer ) ;
moveAtNextTile = destinationLayerMaxMovePoints ;
2019-01-15 07:52:55 +02:00
2018-08-11 21:39:42 +02:00
cost = pathfinderHelper - > getMovementCost ( source , destination , moveAtNextTile ) ; //cost must be updated, movement points changed :(
remains = moveAtNextTile - cost ;
}
2019-01-15 07:52:55 +02:00
2018-08-11 21:39:42 +02:00
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
2019-01-15 07:52:55 +02:00
remains = pathfinderHelper - > movementPointsAfterEmbark ( moveAtNextTile , cost , ( destination . action = = CGPathNode : : DISEMBARK ) ) ;
cost = moveAtNextTile - remains ;
2018-08-11 21:39:42 +02:00
}
2018-08-08 21:24:18 +02:00
2021-07-26 22:56:47 +02:00
costAtNextTile + = static_cast < float > ( cost ) / sourceLayerMaxMovePoints ;
2019-01-15 07:52:55 +02:00
destination . cost = costAtNextTile ;
2018-08-11 21:39:42 +02:00
destination . turn = turnAtNextTile ;
destination . movementLeft = remains ;
2018-08-08 21:24:18 +02:00
2018-08-11 21:39:42 +02:00
if ( destination . isBetterWay ( ) & &
( ( source . node - > turns = = turnAtNextTile & & remains ) | | pathfinderHelper - > passOneTurnLimitCheck ( source ) ) )
{
2018-08-12 13:31:31 +02:00
pathfinderConfig - > nodeStorage - > commit ( destination , source ) ;
2018-08-08 21:24:18 +02:00
2018-08-11 21:39:42 +02:00
return ;
2018-08-08 21:24:18 +02:00
}
2018-08-11 21:39:42 +02:00
destination . blocked = true ;
}
2018-08-08 21:24:18 +02:00
2018-10-07 13:51:27 +02:00
PathfinderConfig : : PathfinderConfig (
2018-08-12 13:31:31 +02:00
std : : shared_ptr < INodeStorage > nodeStorage ,
2018-08-08 21:24:18 +02:00
std : : vector < std : : shared_ptr < IPathfindingRule > > rules )
2018-08-12 13:31:31 +02:00
: nodeStorage ( nodeStorage ) , rules ( rules ) , options ( )
2018-08-08 21:24:18 +02:00
{
}
2018-07-29 13:50:22 +02:00
CPathfinder : : CPathfinder (
CPathsInfo & _out ,
CGameState * _gs ,
const CGHeroInstance * _hero )
2018-08-08 21:24:18 +02:00
: CPathfinder (
2018-07-29 13:50:22 +02:00
_gs ,
2021-05-16 19:53:11 +02:00
std : : make_shared < SingleHeroPathfinderConfig > ( _out , _gs , _hero ) )
{
}
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 . reset ( new CPathfinderHelper ( gs , hero , options ) ) ;
}
CPathfinderHelper * SingleHeroPathfinderConfig : : getOrCreatePathfinderHelper ( const PathNodeInfo & source , CGameState * gs )
2018-07-29 13:50:22 +02:00
{
2021-05-16 19:53:11 +02:00
return pathfinderHelper . get ( ) ;
2018-07-29 13:50:22 +02:00
}
CPathfinder : : CPathfinder (
CGameState * _gs ,
2018-10-07 13:51:27 +02:00
std : : shared_ptr < PathfinderConfig > config )
2018-07-29 13:50:22 +02:00
: CGameInfoCallback ( _gs , boost : : optional < PlayerColor > ( ) )
2018-08-08 21:24:18 +02:00
, config ( config )
2018-07-29 13:50:22 +02:00
, source ( )
, destination ( )
2015-10-27 02:34:47 +02:00
{
2015-11-03 00:29:43 +02:00
initializeGraph ( ) ;
2015-10-27 02:34:47 +02:00
}
2020-09-21 23:19:40 +02:00
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 ;
}
2015-10-27 02:34:47 +02:00
void CPathfinder : : calculatePaths ( )
{
2017-08-10 20:59:55 +02:00
//logGlobal->info("Calculating paths for hero %s (adress %d) of player %d", hero->name, hero , hero->tempOwner);
2015-10-27 02:34:47 +02:00
//initial tile - set cost on 0 and add to the queue
2021-05-16 19:53:11 +02:00
std : : vector < CGPathNode * > initialNodes = config - > nodeStorage - > getInitialNodes ( ) ;
int counter = 0 ;
2018-07-29 13:50:22 +02:00
2021-05-16 19:53:11 +02:00
for ( auto initialNode : initialNodes )
2018-07-29 13:50:22 +02:00
{
2021-05-16 19:53:11 +02:00
if ( ! 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 " ) ;
}
2018-07-29 13:50:22 +02:00
2021-05-16 19:53:11 +02:00
source . setNode ( gs , initialNode ) ;
auto hlp = config - > getOrCreatePathfinderHelper ( source , gs ) ;
2015-10-27 02:34:47 +02:00
2021-05-16 19:53:11 +02:00
if ( hlp - > isHeroPatrolLocked ( ) )
break ;
pq . push ( initialNode ) ;
}
2020-09-21 23:19:40 +02:00
2015-11-07 20:11:07 +02:00
while ( ! pq . empty ( ) )
2015-10-27 02:34:47 +02:00
{
2021-05-16 19:53:11 +02:00
counter + + ;
2020-09-21 23:19:40 +02:00
auto node = topAndPop ( ) ;
2018-07-29 13:50:22 +02:00
2021-05-16 19:53:11 +02:00
source . setNode ( gs , node ) ;
2018-07-29 13:50:22 +02:00
source . node - > locked = true ;
2015-10-27 02:34:47 +02:00
2019-01-15 07:52:55 +02:00
int movement = source . node - > moveRemains ;
uint8_t turn = source . node - > turns ;
2020-09-21 23:19:40 +02:00
float cost = source . node - > getCost ( ) ;
2019-01-15 07:52:55 +02:00
2021-05-16 19:53:11 +02:00
auto hlp = config - > getOrCreatePathfinderHelper ( source , gs ) ;
2015-11-10 13:26:45 +02:00
hlp - > updateTurnInfo ( turn ) ;
2015-10-27 02:34:47 +02:00
if ( ! movement )
{
2015-11-10 13:26:45 +02:00
hlp - > updateTurnInfo ( + + turn ) ;
2018-07-29 13:50:22 +02:00
movement = hlp - > getMaxMovePoints ( source . node - > layer ) ;
2018-08-08 21:24:18 +02:00
if ( ! hlp - > passOneTurnLimitCheck ( source ) )
2015-11-22 05:23:54 +02:00
continue ;
2015-10-27 02:34:47 +02:00
}
2021-05-16 19:53:11 +02:00
source . isInitialPosition = source . nodeHero = = hlp - > hero ;
source . updateInfo ( hlp , gs ) ;
2018-08-01 22:21:14 +02:00
2015-10-27 02:34:47 +02:00
//add accessible neighbouring nodes to the queue
2021-05-16 19:53:11 +02:00
auto neighbourNodes = config - > nodeStorage - > calculateNeighbours ( source , config . get ( ) , hlp ) ;
2018-08-01 22:21:14 +02:00
for ( CGPathNode * neighbour : neighbourNodes )
2015-10-27 02:34:47 +02:00
{
2018-08-12 13:31:31 +02:00
if ( neighbour - > locked )
2018-07-29 13:50:22 +02:00
continue ;
2015-11-22 05:16:16 +02:00
2018-08-12 13:31:31 +02:00
if ( ! hlp - > isLayerAvailable ( neighbour - > layer ) )
2018-07-29 13:50:22 +02:00
continue ;
2015-11-08 06:44:00 +02:00
2018-10-09 21:30:12 +02:00
destination . setNode ( gs , neighbour ) ;
2021-05-16 19:53:11 +02:00
hlp = config - > getOrCreatePathfinderHelper ( destination , gs ) ;
if ( ! hlp - > isPatrolMovementAllowed ( neighbour - > coord ) )
continue ;
2018-10-09 21:30:12 +02:00
2018-07-29 13:50:22 +02:00
/// Check transition without tile accessability rules
2018-10-09 21:30:12 +02:00
if ( source . node - > layer ! = neighbour - > layer & & ! isLayerTransitionPossible ( ) )
2018-07-29 13:50:22 +02:00
continue ;
2015-11-02 15:03:03 +02:00
2018-08-08 21:24:18 +02:00
destination . turn = turn ;
destination . movementLeft = movement ;
2019-01-15 07:52:55 +02:00
destination . cost = cost ;
2021-05-16 19:53:11 +02:00
destination . updateInfo ( hlp , gs ) ;
2018-08-12 13:31:31 +02:00
destination . isGuardianTile = destination . guarded & & isDestinationGuardian ( ) ;
2019-01-15 05:00:00 +02:00
2018-08-08 21:24:18 +02:00
for ( auto rule : config - > rules )
2018-07-29 13:50:22 +02:00
{
2021-05-16 19:53:11 +02:00
rule - > process ( source , destination , config . get ( ) , hlp ) ;
2018-07-29 13:50:22 +02:00
2018-08-08 21:24:18 +02:00
if ( destination . blocked )
break ;
2015-10-27 02:34:47 +02:00
}
2018-08-08 21:24:18 +02:00
2018-08-11 21:39:42 +02:00
if ( ! destination . blocked )
2020-09-21 23:19:40 +02:00
push ( destination . node ) ;
2019-01-15 05:00:00 +02:00
2015-10-27 02:34:47 +02:00
} //neighbours loop
//just add all passable teleport exits
2021-05-16 19:53:11 +02:00
hlp = config - > getOrCreatePathfinderHelper ( source , gs ) ;
2018-08-01 22:21:14 +02:00
/// For now we disable teleports usage for patrol movement
/// VCAI not aware about patrol and may stuck while attempt to use teleport
2021-05-16 19:53:11 +02:00
if ( hlp - > patrolState = = CPathfinderHelper : : PATROL_RADIUS )
2018-08-01 22:21:14 +02:00
continue ;
2021-05-16 19:53:11 +02:00
auto teleportationNodes = config - > nodeStorage - > calculateTeleportations ( source , config . get ( ) , hlp ) ;
2018-08-01 22:21:14 +02:00
for ( CGPathNode * teleportNode : teleportationNodes )
2015-10-27 02:34:47 +02:00
{
2018-07-29 13:50:22 +02:00
if ( teleportNode - > locked )
2015-11-16 16:36:58 +02:00
continue ;
2015-12-11 03:00:10 +02:00
/// 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.
2018-07-29 13:50:22 +02:00
if ( teleportNode - > accessible = = CGPathNode : : BLOCKED )
2015-12-11 03:00:10 +02:00
continue ;
2015-11-07 20:11:07 +02:00
2018-07-29 13:50:22 +02:00
destination . setNode ( gs , teleportNode ) ;
2018-08-08 21:24:18 +02:00
destination . turn = turn ;
destination . movementLeft = movement ;
2019-01-15 07:52:55 +02:00
destination . cost = cost ;
2018-07-29 13:50:22 +02:00
2018-08-08 21:24:18 +02:00
if ( destination . isBetterWay ( ) )
2015-11-16 16:36:58 +02:00
{
2018-08-12 13:31:31 +02:00
destination . action = getTeleportDestAction ( ) ;
config - > nodeStorage - > commit ( destination , source ) ;
2018-07-29 13:50:22 +02:00
if ( destination . node - > action = = CGPathNode : : TELEPORT_NORMAL )
2020-09-21 23:19:40 +02:00
push ( destination . node ) ;
2015-10-27 02:34:47 +02:00
}
}
} //queue loop
2021-05-16 19:53:11 +02:00
logAi - > trace ( " CPathfinder finished with %s iterations " , std : : to_string ( counter ) ) ;
2015-10-27 02:34:47 +02:00
}
2018-08-01 22:21:14 +02:00
std : : vector < int3 > CPathfinderHelper : : getAllowedTeleportChannelExits ( TeleportChannelID channelID ) const
2015-10-27 02:34:47 +02:00
{
2018-08-01 22:21:14 +02:00
std : : vector < int3 > allowedExits ;
2015-11-16 16:36:58 +02:00
2018-08-01 22:21:14 +02:00
for ( auto objId : getTeleportChannelExits ( channelID , hero - > tempOwner ) )
2015-10-27 02:34:47 +02:00
{
2018-08-01 22:21:14 +02:00
auto obj = getObj ( objId ) ;
if ( dynamic_cast < const CGWhirlpool * > ( obj ) )
2015-10-27 02:34:47 +02:00
{
2018-08-01 22:21:14 +02:00
auto pos = obj - > getBlockedPos ( ) ;
for ( auto p : pos )
2015-11-12 00:16:06 +02:00
{
2018-08-01 22:21:14 +02:00
if ( gs - > map - > getTile ( p ) . topVisitableId ( ) = = obj - > ID )
allowedExits . push_back ( p ) ;
2015-11-12 00:16:06 +02:00
}
2018-08-01 22:21:14 +02:00
}
else if ( CGTeleport : : isExitPassable ( gs , hero , obj ) )
allowedExits . push_back ( obj - > visitablePos ( ) ) ;
}
return allowedExits ;
}
2018-10-07 13:51:27 +02:00
std : : vector < int3 > CPathfinderHelper : : getCastleGates ( const PathNodeInfo & source ) const
2018-08-01 22:21:14 +02:00
{
std : : vector < int3 > allowedExits ;
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
auto towns = getPlayerState ( hero - > tempOwner ) - > towns ;
2018-08-01 22:21:14 +02:00
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 ( ) ) ;
2015-10-27 02:34:47 +02:00
}
}
2015-11-08 09:06:24 +02:00
2018-08-01 22:21:14 +02:00
return allowedExits ;
}
2018-10-07 13:51:27 +02:00
std : : vector < int3 > CPathfinderHelper : : getTeleportExits ( const PathNodeInfo & source ) const
2018-08-01 22:21:14 +02:00
{
std : : vector < int3 > teleportationExits ;
const CGTeleport * objTeleport = dynamic_cast < const CGTeleport * > ( source . nodeObject ) ;
2018-08-12 13:31:31 +02:00
if ( isAllowedTeleportEntrance ( objTeleport ) )
2018-08-01 22:21:14 +02:00
{
2018-08-12 13:31:31 +02:00
for ( auto exit : getAllowedTeleportChannelExits ( objTeleport - > channel ) )
2018-08-01 22:21:14 +02:00
{
teleportationExits . push_back ( exit ) ;
}
}
2018-08-12 13:31:31 +02:00
else if ( options . useCastleGate
2018-07-29 13:50:22 +02:00
& & ( source . nodeObject - > ID = = Obj : : TOWN & & source . nodeObject - > subID = = ETownType : : INFERNO
2018-08-01 22:21:14 +02:00
& & source . objectRelations ! = PlayerRelations : : ENEMIES ) )
2015-11-08 09:06:24 +02:00
{
/// TODO: Find way to reuse CPlayerSpecificInfoCallback::getTownsInfo
/// This may be handy if we allow to use teleportation to friendly towns
2018-08-12 13:31:31 +02:00
for ( auto exit : getCastleGates ( source ) )
2015-11-08 09:06:24 +02:00
{
2018-08-01 22:21:14 +02:00
teleportationExits . push_back ( exit ) ;
2015-11-08 09:06:24 +02:00
}
}
2018-08-01 22:21:14 +02:00
return teleportationExits ;
2015-10-27 02:34:47 +02:00
}
2021-05-16 19:53:11 +02:00
bool CPathfinderHelper : : isHeroPatrolLocked ( ) const
2015-11-28 19:34:50 +02:00
{
return patrolState = = PATROL_LOCKED ;
}
2021-05-16 19:53:11 +02:00
bool CPathfinderHelper : : isPatrolMovementAllowed ( const int3 & dst ) const
2015-11-28 19:34:50 +02:00
{
if ( patrolState = = PATROL_RADIUS )
{
if ( ! vstd : : contains ( patrolTiles , dst ) )
return false ;
}
return true ;
}
2018-10-09 21:30:12 +02:00
bool CPathfinder : : isLayerTransitionPossible ( ) const
2015-11-04 11:29:51 +02:00
{
2018-10-09 21:30:12 +02:00
ELayer destLayer = destination . node - > layer ;
2015-11-10 18:15:59 +02:00
/// No layer transition allowed when previous node action is BATTLE
2018-07-29 13:50:22 +02:00
if ( source . node - > action = = CGPathNode : : BATTLE )
2015-11-10 18:15:59 +02:00
return false ;
2018-07-29 13:50:22 +02:00
switch ( source . node - > layer )
2015-11-10 18:15:59 +02:00
{
2015-11-11 21:08:15 +02:00
case ELayer : : LAND :
2016-02-26 20:43:43 +02:00
if ( destLayer = = ELayer : : AIR )
{
2021-05-16 19:53:11 +02:00
if ( ! config - > options . lightweightFlyingMode | | source . isInitialPosition )
2016-02-26 20:43:43 +02:00
return true ;
}
else if ( destLayer = = ELayer : : SAIL )
{
2018-07-29 13:50:22 +02:00
if ( destination . tile - > isWater ( ) )
2016-02-26 20:43:43 +02:00
return true ;
}
else
2015-11-22 05:16:16 +02:00
return true ;
break ;
case ELayer : : SAIL :
2018-07-29 13:50:22 +02:00
if ( destLayer = = ELayer : : LAND & & ! destination . tile - > isWater ( ) )
2015-11-22 05:16:16 +02:00
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 ;
}
2018-10-07 13:51:27 +02:00
void LayerTransitionRule : : process (
const PathNodeInfo & source ,
2018-08-12 13:31:31 +02:00
CDestinationNodeInfo & destination ,
2018-10-07 13:51:27 +02:00
const PathfinderConfig * pathfinderConfig ,
2018-08-12 13:31:31 +02:00
CPathfinderHelper * pathfinderHelper ) const
2015-11-22 05:16:16 +02:00
{
2018-08-12 13:31:31 +02:00
if ( source . node - > layer = = destination . node - > layer )
return ;
2018-07-29 13:50:22 +02:00
switch ( source . node - > layer )
2015-11-22 05:16:16 +02:00
{
2018-08-12 13:31:31 +02:00
case EPathfindingLayer : : LAND :
if ( destination . node - > layer = = EPathfindingLayer : : SAIL )
2015-11-11 21:08:15 +02:00
{
/// Cannot enter empty water tile from land -> it has to be visitable
2018-07-29 13:50:22 +02:00
if ( destination . node - > accessible = = CGPathNode : : ACCESSIBLE )
2018-08-12 13:31:31 +02:00
destination . blocked = true ;
2015-11-11 21:08:15 +02:00
}
break ;
2018-08-12 13:31:31 +02:00
case EPathfindingLayer : : SAIL :
2015-11-11 21:08:15 +02:00
//tile must be accessible -> exception: unblocked blockvis tiles -> clear but guarded by nearby monster coast
2018-07-29 13:50:22 +02:00
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
2015-11-11 21:08:15 +02:00
{
2018-08-12 13:31:31 +02:00
destination . blocked = true ;
2015-11-11 21:08:15 +02:00
}
2015-11-11 21:29:20 +02:00
2015-11-11 21:08:15 +02:00
break ;
2018-08-12 13:31:31 +02:00
case EPathfindingLayer : : AIR :
if ( pathfinderConfig - > options . originalMovementRules )
2015-11-10 18:15:59 +02:00
{
2018-07-29 13:50:22 +02:00
if ( ( source . node - > accessible ! = CGPathNode : : ACCESSIBLE & &
source . node - > accessible ! = CGPathNode : : VISITABLE ) & &
( destination . node - > accessible ! = CGPathNode : : VISITABLE & &
destination . node - > accessible ! = CGPathNode : : ACCESSIBLE ) )
2015-11-10 20:07:27 +02:00
{
2018-08-12 13:31:31 +02:00
destination . blocked = true ;
2015-11-10 20:07:27 +02:00
}
}
2022-09-06 18:59:47 +02:00
else if ( destination . node - > accessible ! = CGPathNode : : ACCESSIBLE )
2015-11-10 20:07:27 +02:00
{
/// Hero that fly can only land on accessible tiles
2022-09-06 18:59:47 +02:00
if ( ! destination . isGuardianTile & & destination . nodeObject )
destination . blocked = true ;
2015-11-10 18:15:59 +02:00
}
2015-11-04 11:29:51 +02:00
2015-12-16 19:14:36 +02:00
break ;
2018-08-12 13:31:31 +02:00
case EPathfindingLayer : : WATER :
2018-07-29 13:50:22 +02:00
if ( destination . node - > accessible ! = CGPathNode : : ACCESSIBLE & & destination . node - > accessible ! = CGPathNode : : VISITABLE )
2015-12-16 19:14:36 +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
2018-08-12 13:31:31 +02:00
destination . blocked = true ;
2015-12-16 19:14:36 +02:00
}
2015-11-11 21:08:15 +02:00
break ;
2015-11-04 11:29:51 +02:00
}
}
2018-10-07 13:51:27 +02:00
PathfinderBlockingRule : : BlockingReason MovementToDestinationRule : : getBlockingReason (
const PathNodeInfo & source ,
const CDestinationNodeInfo & destination ,
const PathfinderConfig * pathfinderConfig ,
const CPathfinderHelper * pathfinderHelper ) const
2015-10-27 02:34:47 +02:00
{
2018-08-11 21:39:42 +02:00
2018-07-29 13:50:22 +02:00
if ( destination . node - > accessible = = CGPathNode : : BLOCKED )
2018-08-11 21:39:42 +02:00
return BlockingReason : : DESTINATION_BLOCKED ;
2015-11-17 02:59:02 +02:00
2018-07-29 13:50:22 +02:00
switch ( destination . node - > layer )
2015-10-27 02:34:47 +02:00
{
2018-08-11 21:39:42 +02:00
case EPathfindingLayer : : LAND :
if ( ! pathfinderHelper - > canMoveBetween ( source . coord , destination . coord ) )
return BlockingReason : : DESTINATION_BLOCKED ;
if ( source . guarded )
2015-11-11 21:08:15 +02:00
{
2018-08-11 21:39:42 +02:00
if ( ! ( pathfinderConfig - > options . originalMovementRules & & source . node - > layer = = EPathfindingLayer : : AIR ) & &
2018-08-12 13:31:31 +02:00
! destination . isGuardianTile ) // Can step into tile of guard
2015-11-10 20:07:27 +02:00
{
2018-08-11 21:39:42 +02:00
return BlockingReason : : SOURCE_GUARDED ;
2015-11-11 21:08:15 +02:00
}
}
2015-10-27 02:34:47 +02:00
2015-11-11 21:08:15 +02:00
break ;
2015-11-07 23:26:41 +02:00
2018-08-11 21:39:42 +02:00
case EPathfindingLayer : : SAIL :
if ( ! pathfinderHelper - > canMoveBetween ( source . coord , destination . coord ) )
return BlockingReason : : DESTINATION_BLOCKED ;
if ( source . guarded )
2015-11-11 21:29:20 +02:00
{
// Hero embarked a boat standing on a guarded tile -> we must allow to move away from that tile
2018-08-12 13:31:31 +02:00
if ( source . node - > action ! = CGPathNode : : EMBARK & & ! destination . isGuardianTile )
2018-08-11 21:39:42 +02:00
return BlockingReason : : SOURCE_GUARDED ;
2015-11-11 21:29:20 +02:00
}
2015-10-27 02:34:47 +02:00
2018-08-11 21:39:42 +02:00
if ( source . node - > layer = = EPathfindingLayer : : LAND )
2015-11-11 21:08:15 +02:00
{
2018-07-29 13:50:22 +02:00
if ( ! destination . isNodeObjectVisitable ( ) )
2018-08-11 21:39:42 +02:00
return BlockingReason : : DESTINATION_BLOCKED ;
2015-11-11 21:08:15 +02:00
2021-05-16 19:53:11 +02:00
if ( destination . nodeObject - > ID ! = Obj : : BOAT & & ! destination . nodeHero )
2018-08-11 21:39:42 +02:00
return BlockingReason : : DESTINATION_BLOCKED ;
2015-11-11 21:08:15 +02:00
}
2018-07-29 13:50:22 +02:00
else if ( destination . isNodeObjectVisitable ( ) & & destination . nodeObject - > ID = = Obj : : BOAT )
2015-11-13 09:28:44 +02:00
{
/// Hero in boat can't visit empty boats
2018-08-11 21:39:42 +02:00
return BlockingReason : : DESTINATION_BLOCKED ;
2015-11-13 09:28:44 +02:00
}
2015-11-11 21:29:20 +02:00
2015-11-11 21:08:15 +02:00
break ;
2015-11-02 15:03:03 +02:00
2018-08-11 21:39:42 +02:00
case EPathfindingLayer : : WATER :
if ( ! pathfinderHelper - > canMoveBetween ( source . coord , destination . coord )
| | destination . node - > accessible ! = CGPathNode : : ACCESSIBLE )
{
return BlockingReason : : DESTINATION_BLOCKED ;
}
if ( destination . guarded )
2018-08-12 13:31:31 +02:00
return BlockingReason : : DESTINATION_BLOCKED ;
2015-11-11 21:08:15 +02:00
break ;
2015-10-27 02:34:47 +02:00
}
2018-08-11 21:39:42 +02:00
return BlockingReason : : NONE ;
2015-10-27 02:34:47 +02:00
}
2018-08-11 21:39:42 +02:00
2018-10-07 13:51:27 +02:00
void MovementAfterDestinationRule : : process (
const PathNodeInfo & source ,
2018-08-11 21:39:42 +02:00
CDestinationNodeInfo & destination ,
2018-10-07 13:51:27 +02:00
const PathfinderConfig * config ,
2018-08-12 13:31:31 +02:00
CPathfinderHelper * pathfinderHelper ) const
2018-08-11 21:39:42 +02:00
{
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 ;
}
2018-10-07 13:51:27 +02:00
PathfinderBlockingRule : : BlockingReason MovementAfterDestinationRule : : getBlockingReason (
2019-01-15 05:00:00 +02:00
const PathNodeInfo & source ,
2018-10-07 13:51:27 +02:00
const CDestinationNodeInfo & destination ,
const PathfinderConfig * config ,
const CPathfinderHelper * pathfinderHelper ) const
2015-10-27 02:34:47 +02:00
{
2018-08-01 20:46:06 +02:00
switch ( destination . action )
2015-11-04 10:53:52 +02:00
{
2015-11-08 03:41:06 +02:00
/// 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
case CGPathNode : : VISIT :
2015-11-16 18:14:18 +02:00
{
2015-11-10 18:15:59 +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
2018-07-29 13:50:22 +02:00
const CGTeleport * objTeleport = dynamic_cast < const CGTeleport * > ( destination . nodeObject ) ;
2018-08-01 20:46:06 +02:00
if ( pathfinderHelper - > isAllowedTeleportEntrance ( objTeleport ) )
2015-11-08 03:41:06 +02:00
{
/// For now we'll always allow transit over teleporters
/// Transit over whirlpools only allowed when hero protected
2018-08-11 21:39:42 +02:00
return BlockingReason : : NONE ;
2015-11-08 03:41:06 +02:00
}
2018-08-01 20:46:06 +02:00
else if ( destination . nodeObject - > ID = = Obj : : GARRISON
| | destination . nodeObject - > ID = = Obj : : GARRISON2
2018-07-29 13:50:22 +02:00
| | destination . nodeObject - > ID = = Obj : : BORDER_GATE )
2015-11-17 23:07:25 +02:00
{
/// Transit via unguarded garrisons is always possible
2018-08-11 21:39:42 +02:00
return BlockingReason : : NONE ;
2015-11-17 23:07:25 +02:00
}
2015-11-11 21:08:15 +02:00
2018-08-11 21:39:42 +02:00
return BlockingReason : : DESTINATION_VISIT ;
2015-11-16 18:14:18 +02:00
}
2015-11-11 21:08:15 +02:00
2018-08-11 21:39:42 +02:00
case CGPathNode : : BLOCKING_VISIT :
return destination . guarded
? BlockingReason : : DESTINATION_GUARDED
: BlockingReason : : DESTINATION_BLOCKVIS ;
2015-11-08 03:41:06 +02:00
case CGPathNode : : NORMAL :
2018-08-11 21:39:42 +02:00
return BlockingReason : : NONE ;
2015-11-04 10:53:52 +02:00
2015-11-08 03:41:06 +02:00
case CGPathNode : : EMBARK :
2018-08-01 20:46:06 +02:00
if ( pathfinderHelper - > options . useEmbarkAndDisembark )
2018-08-11 21:39:42 +02:00
return BlockingReason : : NONE ;
2015-11-04 10:53:52 +02:00
2018-08-11 21:39:42 +02:00
return BlockingReason : : DESTINATION_BLOCKED ;
2015-11-11 21:08:15 +02:00
2015-11-08 03:41:06 +02:00
case CGPathNode : : DISEMBARK :
2018-08-12 13:31:31 +02:00
if ( pathfinderHelper - > options . useEmbarkAndDisembark )
return destination . guarded ? BlockingReason : : DESTINATION_GUARDED : BlockingReason : : NONE ;
2015-11-10 18:15:59 +02:00
2018-08-11 21:39:42 +02:00
return BlockingReason : : DESTINATION_BLOCKED ;
2015-11-11 21:08:15 +02:00
2015-11-10 18:15:59 +02:00
case CGPathNode : : BATTLE :
2015-11-13 22:07:56 +02:00
/// Movement after BATTLE action only possible from guarded tile to guardian tile
2018-08-01 20:46:06 +02:00
if ( destination . guarded )
2018-08-12 13:31:31 +02:00
return BlockingReason : : DESTINATION_GUARDED ;
2015-11-11 21:08:15 +02:00
break ;
2015-11-04 10:53:52 +02:00
}
2015-10-27 02:34:47 +02:00
2018-08-11 21:39:42 +02:00
return BlockingReason : : DESTINATION_BLOCKED ;
2015-10-27 02:34:47 +02:00
}
2018-10-07 13:51:27 +02:00
void DestinationActionRule : : process (
const PathNodeInfo & source ,
2018-08-12 13:31:31 +02:00
CDestinationNodeInfo & destination ,
2018-10-07 13:51:27 +02:00
const PathfinderConfig * pathfinderConfig ,
2018-08-12 13:31:31 +02:00
CPathfinderHelper * pathfinderHelper ) const
2015-11-11 18:51:08 +02:00
{
2018-08-12 13:31:31 +02:00
if ( destination . action ! = CGPathNode : : ENodeAction : : UNKNOWN )
{
2019-01-15 05:00:00 +02:00
# ifdef VCMI_TRACE_PATHFINDER
2018-10-09 21:31:44 +02:00
logAi - > trace ( " Accepted precalculated action at %s " , destination . coord . toString ( ) ) ;
2019-01-15 05:00:00 +02:00
# endif
2018-08-12 13:31:31 +02:00
return ;
}
2015-11-11 18:51:08 +02:00
CGPathNode : : ENodeAction action = CGPathNode : : NORMAL ;
2018-08-12 13:31:31 +02:00
auto hero = pathfinderHelper - > hero ;
2018-07-29 13:50:22 +02:00
switch ( destination . node - > layer )
2015-11-11 18:51:08 +02:00
{
2018-08-12 13:31:31 +02:00
case EPathfindingLayer : : LAND :
if ( source . node - > layer = = EPathfindingLayer : : SAIL )
2015-11-11 18:51:08 +02:00
{
// TODO: Handle dismebark into guarded areaa
action = CGPathNode : : DISEMBARK ;
break ;
}
2015-11-16 22:07:36 +02:00
/// don't break - next case shared for both land and sail layers
2018-02-09 12:02:44 +02:00
FALLTHROUGH
2015-11-16 22:07:36 +02:00
2018-08-12 13:31:31 +02:00
case EPathfindingLayer : : SAIL :
2018-07-29 13:50:22 +02:00
if ( destination . isNodeObjectVisitable ( ) )
2015-11-11 18:51:08 +02:00
{
2018-08-12 13:31:31 +02:00
auto objRel = destination . objectRelations ;
2015-11-11 18:51:08 +02:00
2018-07-29 13:50:22 +02:00
if ( destination . nodeObject - > ID = = Obj : : BOAT )
2015-11-11 18:51:08 +02:00
action = CGPathNode : : EMBARK ;
2021-05-16 19:53:11 +02:00
else if ( destination . nodeHero )
2015-11-11 18:51:08 +02:00
{
2021-05-16 19:53:11 +02:00
if ( destination . heroRelations = = PlayerRelations : : ENEMIES )
2015-11-11 18:51:08 +02:00
action = CGPathNode : : BATTLE ;
else
action = CGPathNode : : BLOCKING_VISIT ;
}
2018-07-29 13:50:22 +02:00
else if ( destination . nodeObject - > ID = = Obj : : TOWN )
2015-11-11 18:51:08 +02:00
{
2018-07-29 13:50:22 +02:00
if ( destination . nodeObject - > passableFor ( hero - > tempOwner ) )
2015-12-14 13:33:50 +02:00
action = CGPathNode : : VISIT ;
else if ( objRel = = PlayerRelations : : ENEMIES )
2015-11-11 18:51:08 +02:00
action = CGPathNode : : BATTLE ;
}
2018-07-29 13:50:22 +02:00
else if ( destination . nodeObject - > ID = = Obj : : GARRISON | | destination . nodeObject - > ID = = Obj : : GARRISON2 )
2015-11-11 18:51:08 +02:00
{
2018-07-29 13:50:22 +02:00
if ( destination . nodeObject - > passableFor ( hero - > tempOwner ) )
2015-12-14 13:33:50 +02:00
{
2018-08-12 13:31:31 +02:00
if ( destination . guarded )
2015-12-14 13:33:50 +02:00
action = CGPathNode : : BATTLE ;
}
else if ( objRel = = PlayerRelations : : ENEMIES )
2015-11-11 18:51:08 +02:00
action = CGPathNode : : BATTLE ;
}
2018-07-29 13:50:22 +02:00
else if ( destination . nodeObject - > ID = = Obj : : BORDER_GATE )
2015-12-14 13:33:50 +02:00
{
2018-07-29 13:50:22 +02:00
if ( destination . nodeObject - > passableFor ( hero - > tempOwner ) )
2015-12-14 13:33:50 +02:00
{
2018-08-12 13:31:31 +02:00
if ( destination . guarded )
2015-12-14 13:33:50 +02:00
action = CGPathNode : : BATTLE ;
}
else
action = CGPathNode : : BLOCKING_VISIT ;
}
2018-08-12 13:31:31 +02:00
else if ( destination . isGuardianTile )
2015-11-11 18:51:08 +02:00
action = CGPathNode : : BATTLE ;
2018-08-12 13:31:31 +02:00
else if ( destination . nodeObject - > blockVisit & & ! ( pathfinderConfig - > options . useCastleGate & & destination . nodeObject - > ID = = Obj : : TOWN ) )
2015-11-11 18:51:08 +02:00
action = CGPathNode : : BLOCKING_VISIT ;
if ( action = = CGPathNode : : NORMAL )
{
2017-07-02 22:24:01 +02:00
if ( destination . guarded )
2015-11-11 18:51:08 +02:00
action = CGPathNode : : BATTLE ;
else
action = CGPathNode : : VISIT ;
}
}
2018-08-12 13:31:31 +02:00
else if ( destination . guarded )
2015-11-11 18:51:08 +02:00
action = CGPathNode : : BATTLE ;
break ;
}
2018-08-12 13:31:31 +02:00
destination . action = action ;
2015-11-11 18:51:08 +02:00
}
2015-12-11 08:42:30 +02:00
CGPathNode : : ENodeAction CPathfinder : : getTeleportDestAction ( ) const
{
CGPathNode : : ENodeAction action = CGPathNode : : TELEPORT_NORMAL ;
2021-05-16 19:53:11 +02:00
if ( destination . isNodeObjectVisitable ( ) & & destination . nodeHero )
2015-12-11 08:42:30 +02:00
{
2021-05-16 19:53:11 +02:00
if ( destination . heroRelations = = PlayerRelations : : ENEMIES )
2015-12-11 08:42:30 +02:00
action = CGPathNode : : TELEPORT_BATTLE ;
else
action = CGPathNode : : TELEPORT_BLOCKING_VISIT ;
}
return action ;
}
2015-11-08 07:39:00 +02:00
bool CPathfinder : : isDestinationGuardian ( ) const
2015-10-27 02:34:47 +02:00
{
2018-07-29 13:50:22 +02:00
return gs - > guardingCreaturePosition ( source . node - > coord ) = = destination . node - > coord ;
2015-10-27 02:34:47 +02:00
}
2021-05-16 19:53:11 +02:00
void CPathfinderHelper : : initializePatrol ( )
2015-11-28 19:34:50 +02:00
{
auto state = PATROL_NONE ;
2021-05-16 19:53:11 +02:00
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
if ( hero - > patrol . patrolling & & ! getPlayerState ( hero - > tempOwner ) - > human )
2015-11-28 19:34:50 +02:00
{
2016-01-31 17:01:58 +02:00
if ( hero - > patrol . patrolRadius )
2015-11-28 19:34:50 +02:00
{
state = PATROL_RADIUS ;
2016-02-10 17:36:56 +02:00
gs - > getTilesInRange ( patrolTiles , hero - > patrol . initialPos , hero - > patrol . patrolRadius , boost : : optional < PlayerColor > ( ) , 0 , int3 : : DIST_MANHATTAN ) ;
2015-11-28 19:34:50 +02:00
}
else
state = PATROL_LOCKED ;
}
patrolState = state ;
}
2015-10-27 02:34:47 +02:00
void CPathfinder : : initializeGraph ( )
{
2019-01-15 05:00:00 +02:00
INodeStorage * nodeStorage = config - > nodeStorage . get ( ) ;
2021-05-16 19:53:11 +02:00
nodeStorage - > initialize ( config - > options , gs ) ;
2015-10-27 02:34:47 +02:00
}
2018-08-01 20:46:06 +02:00
bool CPathfinderHelper : : canMoveBetween ( const int3 & a , const int3 & b ) const
2015-10-27 02:34:47 +02:00
{
return gs - > checkForVisitableDir ( a , b ) ;
}
2018-08-01 20:46:06 +02:00
bool CPathfinderHelper : : isAllowedTeleportEntrance ( const CGTeleport * obj ) const
2015-11-16 18:14:18 +02:00
{
2015-11-16 20:22:11 +02:00
if ( ! obj | | ! isTeleportEntrancePassable ( obj , hero - > tempOwner ) )
2015-11-16 18:14:18 +02:00
return false ;
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 ;
}
2018-08-01 20:46:06 +02:00
bool CPathfinderHelper : : addTeleportTwoWay ( const CGTeleport * obj ) const
2015-10-27 02:34:47 +02:00
{
2015-11-16 20:22:11 +02:00
return options . useTeleportTwoWay & & isTeleportChannelBidirectional ( obj - > channel , hero - > tempOwner ) ;
2015-10-27 02:34:47 +02:00
}
2018-08-01 20:46:06 +02:00
bool CPathfinderHelper : : addTeleportOneWay ( const CGTeleport * obj ) const
2015-10-27 02:34:47 +02:00
{
if ( options . useTeleportOneWay & & isTeleportChannelUnidirectional ( obj - > channel , hero - > tempOwner ) )
{
2015-11-16 20:22:11 +02:00
auto passableExits = CGTeleport : : getPassableExits ( gs , hero , getTeleportChannelExits ( obj - > channel , hero - > tempOwner ) ) ;
2015-10-27 02:34:47 +02:00
if ( passableExits . size ( ) = = 1 )
return true ;
}
return false ;
}
2018-08-01 20:46:06 +02:00
bool CPathfinderHelper : : addTeleportOneWayRandom ( const CGTeleport * obj ) const
2015-10-27 02:34:47 +02:00
{
if ( options . useTeleportOneWayRandom & & isTeleportChannelUnidirectional ( obj - > channel , hero - > tempOwner ) )
{
2015-11-16 20:22:11 +02:00
auto passableExits = CGTeleport : : getPassableExits ( gs , hero , getTeleportChannelExits ( obj - > channel , hero - > tempOwner ) ) ;
2015-10-27 02:34:47 +02:00
if ( passableExits . size ( ) > 1 )
return true ;
}
return false ;
}
2018-08-01 20:46:06 +02:00
bool CPathfinderHelper : : addTeleportWhirlpool ( const CGWhirlpool * obj ) const
2015-10-27 02:34:47 +02:00
{
2018-08-01 20:46:06 +02:00
return options . useTeleportWhirlpool & & hasBonusOfType ( Bonus : : WHIRLPOOL_PROTECTION ) & & obj ;
2015-10-27 02:34:47 +02:00
}
2019-01-15 07:52:55 +02:00
int CPathfinderHelper : : movementPointsAfterEmbark ( int movement , int basicCost , bool disembark ) const
2018-08-08 21:24:18 +02:00
{
2019-01-15 07:52:55 +02:00
return hero - > movementPointsAfterEmbark ( movement , basicCost , disembark , getTurnInfo ( ) ) ;
2018-08-08 21:24:18 +02:00
}
2018-10-07 13:51:27 +02:00
bool CPathfinderHelper : : passOneTurnLimitCheck ( const PathNodeInfo & source ) const
2018-08-08 21:24:18 +02:00
{
if ( ! options . oneTurnSpecialLayersLimit )
return true ;
if ( source . node - > layer = = EPathfindingLayer : : WATER )
return false ;
if ( source . node - > layer = = EPathfindingLayer : : AIR )
{
if ( options . originalMovementRules & & source . node - > accessible = = CGPathNode : : ACCESSIBLE )
return true ;
else
return false ;
}
return true ;
}
2020-10-01 07:55:41 +02:00
TurnInfo : : BonusCache : : BonusCache ( TConstBonusListPtr bl )
2015-11-21 09:00:09 +02:00
{
2022-06-20 16:39:50 +02:00
for ( int i = 0 ; i < Terrain : : Manager : : terrains ( ) . size ( ) ; + + i )
2015-11-21 09:00:09 +02:00
{
2016-09-19 23:36:35 +02:00
noTerrainPenalty . push_back ( static_cast < bool > (
2020-11-11 21:43:40 +02:00
bl - > getFirst ( Selector : : type ( ) ( Bonus : : NO_TERRAIN_PENALTY ) . And ( Selector : : subtype ( ) ( i ) ) ) ) ) ;
2015-11-21 09:00:09 +02:00
}
2020-11-11 21:43:40 +02:00
freeShipBoarding = static_cast < bool > ( bl - > getFirst ( Selector : : type ( ) ( Bonus : : FREE_SHIP_BOARDING ) ) ) ;
flyingMovement = static_cast < bool > ( bl - > getFirst ( Selector : : type ( ) ( Bonus : : FLYING_MOVEMENT ) ) ) ;
flyingMovementVal = bl - > valOfBonuses ( Selector : : type ( ) ( Bonus : : FLYING_MOVEMENT ) ) ;
waterWalking = static_cast < bool > ( bl - > getFirst ( Selector : : type ( ) ( Bonus : : WATER_WALKING ) ) ) ;
waterWalkingVal = bl - > valOfBonuses ( Selector : : type ( ) ( Bonus : : WATER_WALKING ) ) ;
2022-09-14 15:16:12 +02:00
pathfindingVal = bl - > valOfBonuses ( Selector : : typeSubtype ( Bonus : : SECONDARY_SKILL_PREMY , SecondarySkill : : PATHFINDING ) ) ;
2015-11-21 09:00:09 +02:00
}
2015-11-12 13:04:33 +02:00
TurnInfo : : TurnInfo ( const CGHeroInstance * Hero , const int turn )
: hero ( Hero ) , maxMovePointsLand ( - 1 ) , maxMovePointsWater ( - 1 )
2015-11-12 04:20:32 +02:00
{
2019-01-15 05:00:00 +02:00
bonuses = hero - > getAllBonuses ( Selector : : days ( turn ) , Selector : : all , nullptr , " " ) ;
2015-11-21 09:00:09 +02:00
bonusCache = make_unique < BonusCache > ( bonuses ) ;
2015-11-21 12:30:39 +02:00
nativeTerrain = hero - > getNativeTerrain ( ) ;
2015-11-12 13:04:33 +02:00
}
2015-11-12 13:39:22 +02:00
bool TurnInfo : : isLayerAvailable ( const EPathfindingLayer layer ) const
{
switch ( layer )
{
case EPathfindingLayer : : AIR :
if ( ! hasBonusOfType ( Bonus : : FLYING_MOVEMENT ) )
return false ;
break ;
case EPathfindingLayer : : WATER :
if ( ! hasBonusOfType ( Bonus : : WATER_WALKING ) )
return false ;
break ;
}
return true ;
}
2015-11-12 13:04:33 +02:00
bool TurnInfo : : hasBonusOfType ( Bonus : : BonusType type , int subtype ) const
{
2015-11-21 09:00:09 +02:00
switch ( type )
{
case Bonus : : FREE_SHIP_BOARDING :
return bonusCache - > freeShipBoarding ;
case Bonus : : FLYING_MOVEMENT :
return bonusCache - > flyingMovement ;
case Bonus : : WATER_WALKING :
return bonusCache - > waterWalking ;
case Bonus : : NO_TERRAIN_PENALTY :
return bonusCache - > noTerrainPenalty [ subtype ] ;
}
2016-09-19 23:36:35 +02:00
return static_cast < bool > (
2020-11-11 21:43:40 +02:00
bonuses - > getFirst ( Selector : : type ( ) ( type ) . And ( Selector : : subtype ( ) ( subtype ) ) ) ) ;
2015-11-12 13:04:33 +02:00
}
int TurnInfo : : valOfBonuses ( Bonus : : BonusType type , int subtype ) const
{
2015-11-21 09:00:09 +02:00
switch ( type )
{
case Bonus : : FLYING_MOVEMENT :
return bonusCache - > flyingMovementVal ;
case Bonus : : WATER_WALKING :
return bonusCache - > waterWalkingVal ;
2022-09-14 15:16:12 +02:00
case Bonus : : SECONDARY_SKILL_PREMY :
if ( subtype = = SecondarySkill : : PATHFINDING )
return bonusCache - > pathfindingVal ;
2015-11-21 09:00:09 +02:00
}
2020-11-11 21:43:40 +02:00
return bonuses - > valOfBonuses ( Selector : : type ( ) ( type ) . And ( Selector : : subtype ( ) ( subtype ) ) ) ;
2015-11-12 04:20:32 +02:00
}
int TurnInfo : : getMaxMovePoints ( const EPathfindingLayer layer ) const
{
2015-11-12 13:04:33 +02:00
if ( maxMovePointsLand = = - 1 )
2019-01-15 07:52:55 +02:00
maxMovePointsLand = hero - > maxMovePointsCached ( true , this ) ;
2015-11-12 13:04:33 +02:00
if ( maxMovePointsWater = = - 1 )
2019-01-15 07:52:55 +02:00
maxMovePointsWater = hero - > maxMovePointsCached ( false , this ) ;
2015-11-12 13:04:33 +02:00
2015-11-12 04:20:32 +02:00
return layer = = EPathfindingLayer : : SAIL ? maxMovePointsWater : maxMovePointsLand ;
}
2018-08-01 20:46:06 +02:00
CPathfinderHelper : : CPathfinderHelper ( CGameState * gs , const CGHeroInstance * Hero , const PathfinderOptions & Options )
2021-05-16 19:53:11 +02:00
: CGameInfoCallback ( gs , boost : : optional < PlayerColor > ( ) ) , turn ( - 1 ) , hero ( Hero ) , options ( Options ) , owner ( Hero - > tempOwner )
2015-11-10 13:26:45 +02:00
{
turnsInfo . reserve ( 16 ) ;
updateTurnInfo ( ) ;
2021-05-16 19:53:11 +02:00
initializePatrol ( ) ;
2015-11-10 13:26:45 +02:00
}
2016-08-16 13:59:16 +02:00
CPathfinderHelper : : ~ CPathfinderHelper ( )
{
for ( auto ti : turnsInfo )
delete ti ;
}
2015-11-12 13:04:33 +02:00
void CPathfinderHelper : : updateTurnInfo ( const int Turn )
2015-11-10 13:26:45 +02:00
{
2015-11-12 13:04:33 +02:00
if ( turn ! = Turn )
2015-11-10 13:26:45 +02:00
{
2015-11-12 13:04:33 +02:00
turn = Turn ;
if ( turn > = turnsInfo . size ( ) )
2015-11-10 13:26:45 +02:00
{
2015-11-12 13:04:33 +02:00
auto ti = new TurnInfo ( hero , turn ) ;
2015-11-10 13:26:45 +02:00
turnsInfo . push_back ( ti ) ;
}
}
}
2015-11-12 13:39:22 +02:00
bool CPathfinderHelper : : isLayerAvailable ( const EPathfindingLayer layer ) const
{
2015-11-16 17:43:02 +02:00
switch ( layer )
{
case EPathfindingLayer : : AIR :
if ( ! options . useFlying )
return false ;
break ;
case EPathfindingLayer : : WATER :
if ( ! options . useWaterWalking )
return false ;
break ;
}
2015-11-12 13:39:22 +02:00
return turnsInfo [ turn ] - > isLayerAvailable ( layer ) ;
}
2015-11-12 13:04:33 +02:00
const TurnInfo * CPathfinderHelper : : getTurnInfo ( ) const
2015-11-10 13:26:45 +02:00
{
2015-11-12 04:20:32 +02:00
return turnsInfo [ turn ] ;
2015-11-10 13:26:45 +02:00
}
2015-11-12 13:04:33 +02:00
bool CPathfinderHelper : : hasBonusOfType ( const Bonus : : BonusType type , const int subtype ) const
{
return turnsInfo [ turn ] - > hasBonusOfType ( type , subtype ) ;
}
int CPathfinderHelper : : getMaxMovePoints ( const EPathfindingLayer layer ) const
2015-11-10 13:26:45 +02:00
{
2015-11-12 04:20:32 +02:00
return turnsInfo [ turn ] - > getMaxMovePoints ( layer ) ;
2015-11-10 13:26:45 +02:00
}
2018-08-12 13:31:31 +02:00
void CPathfinderHelper : : getNeighbours (
2019-01-15 05:00:00 +02:00
const TerrainTile & srct ,
2018-08-12 13:31:31 +02:00
const int3 & tile ,
std : : vector < int3 > & vec ,
2019-01-15 05:00:00 +02:00
const boost : : logic : : tribool & onLand ,
2018-08-12 13:31:31 +02:00
const bool limitCoastSailing ) const
2015-11-10 01:15:27 +02:00
{
2018-08-01 20:46:06 +02:00
CMap * map = gs - > map ;
2015-11-17 01:41:31 +02:00
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 )
} ;
2015-11-10 01:15:27 +02:00
2015-11-11 21:08:15 +02:00
for ( auto & dir : dirs )
2015-11-10 01:15:27 +02:00
{
const int3 hlp = tile + dir ;
2015-11-16 20:22:11 +02:00
if ( ! map - > isInTheMap ( hlp ) )
2015-11-10 01:15:27 +02:00
continue ;
2015-11-16 20:22:11 +02:00
const TerrainTile & hlpt = map - > getTile ( hlp ) ;
2022-06-20 16:39:50 +02:00
if ( ! hlpt . terType . isPassable ( ) )
2015-11-17 01:41:31 +02:00
continue ;
2015-11-10 01:15:27 +02:00
// //we cannot visit things from blocked tiles
// if(srct.blocked && !srct.visitable && hlpt.visitable && srct.blockingObjects.front()->ID != HEROI_TYPE)
// {
// continue;
// }
2015-11-17 01:41:31 +02:00
/// Following condition let us avoid diagonal movement over coast when sailing
2022-06-20 16:39:50 +02:00
if ( srct . terType . isWater ( ) & & limitCoastSailing & & hlpt . terType . isWater ( ) & & dir . x & & dir . y ) //diagonal move through water
2015-11-10 01:15:27 +02:00
{
int3 hlp1 = tile ,
hlp2 = tile ;
hlp1 . x + = dir . x ;
hlp2 . y + = dir . y ;
2022-06-20 16:39:50 +02:00
if ( map - > getTile ( hlp1 ) . terType . isLand ( ) | | map - > getTile ( hlp2 ) . terType . isLand ( ) )
2015-11-10 01:15:27 +02:00
continue ;
}
2022-06-20 16:39:50 +02:00
if ( indeterminate ( onLand ) | | onLand = = hlpt . terType . isLand ( ) )
2015-11-10 01:15:27 +02:00
{
vec . push_back ( hlp ) ;
}
}
}
2018-08-12 13:31:31 +02:00
int CPathfinderHelper : : getMovementCost (
const int3 & src ,
2019-01-15 05:00:00 +02:00
const int3 & dst ,
2018-08-12 13:31:31 +02:00
const TerrainTile * ct ,
const TerrainTile * dt ,
2019-01-15 05:00:00 +02:00
const int remainingMovePoints ,
2018-08-12 13:31:31 +02:00
const bool checkLast ) const
2015-11-10 01:15:27 +02:00
{
if ( src = = dst ) //same tile
return 0 ;
2018-08-01 20:46:06 +02:00
auto ti = getTurnInfo ( ) ;
2015-11-10 13:26:45 +02:00
2015-11-21 13:31:30 +02:00
if ( ct = = nullptr | | dt = = nullptr )
{
2018-08-01 20:46:06 +02:00
ct = hero - > cb - > getTile ( src ) ;
dt = hero - > cb - > getTile ( dst ) ;
2015-11-21 13:31:30 +02:00
}
2015-11-16 22:07:36 +02:00
/// 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.
2021-08-21 11:05:28 +02:00
ui32 ret = hero - > getTileCost ( * dt , * ct , ti ) ;
2015-11-16 22:07:36 +02:00
/// 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.
2015-11-10 01:15:27 +02:00
2015-11-21 13:31:30 +02:00
if ( dt - > blocked & & ti - > hasBonusOfType ( Bonus : : FLYING_MOVEMENT ) )
2015-11-10 01:15:27 +02:00
{
2020-10-01 10:38:06 +02:00
ret = static_cast < int > ( ret * ( 100.0 + ti - > valOfBonuses ( Bonus : : FLYING_MOVEMENT ) ) / 100.0 ) ;
2015-11-10 01:15:27 +02:00
}
2022-06-20 16:39:50 +02:00
else if ( dt - > terType . isWater ( ) )
2015-11-10 01:15:27 +02:00
{
2018-08-01 20:46:06 +02:00
if ( hero - > boat & & ct - > hasFavorableWinds ( ) & & dt - > hasFavorableWinds ( ) )
2020-10-01 10:38:06 +02:00
ret = static_cast < int > ( ret * 0.666 ) ;
2018-08-01 20:46:06 +02:00
else if ( ! hero - > boat & & ti - > hasBonusOfType ( Bonus : : WATER_WALKING ) )
2015-11-10 01:15:27 +02:00
{
2020-10-01 10:38:06 +02:00
ret = static_cast < int > ( ret * ( 100.0 + ti - > valOfBonuses ( Bonus : : WATER_WALKING ) ) / 100.0 ) ;
2015-11-10 01:15:27 +02:00
}
}
if ( src . x ! = dst . x & & src . y ! = dst . y ) //it's diagonal move
{
int old = ret ;
2020-10-01 10:38:06 +02:00
ret = static_cast < int > ( ret * 1.414213 ) ;
2015-11-10 01:15:27 +02:00
//diagonal move costs too much but normal move is possible - allow diagonal move for remaining move points
if ( ret > remainingMovePoints & & remainingMovePoints > = old )
2016-08-16 14:47:21 +02:00
{
2015-11-10 01:15:27 +02:00
return remainingMovePoints ;
2016-08-16 14:47:21 +02:00
}
2015-11-10 01:15:27 +02:00
}
2015-11-16 22:07:36 +02:00
/// TODO: This part need rework in order to work properly with flying and water walking
/// Currently it's only work properly for normal movement or sailing
2015-11-10 01:15:27 +02:00
int left = remainingMovePoints - ret ;
if ( checkLast & & left > 0 & & remainingMovePoints - ret < 250 ) //it might be the last tile - if no further move possible we take all move points
{
std : : vector < int3 > vec ;
vec . reserve ( 8 ) ; //optimization
2022-06-20 16:39:50 +02:00
getNeighbours ( * dt , dst , vec , ct - > terType . isLand ( ) , true ) ;
2015-11-10 01:15:27 +02:00
for ( auto & elem : vec )
{
2018-08-01 20:46:06 +02:00
int fcost = getMovementCost ( dst , elem , nullptr , nullptr , left , false ) ;
2015-11-10 01:15:27 +02:00
if ( fcost < = left )
2016-08-16 14:47:21 +02:00
{
2015-11-10 01:15:27 +02:00
return ret ;
2016-08-16 14:47:21 +02:00
}
2015-11-10 01:15:27 +02:00
}
ret = remainingMovePoints ;
}
2016-08-16 14:47:21 +02:00
2015-11-10 01:15:27 +02:00
return ret ;
}
2015-10-27 02:34:47 +02:00
int3 CGPath : : startPos ( ) const
{
return nodes [ nodes . size ( ) - 1 ] . coord ;
}
int3 CGPath : : endPos ( ) const
{
return nodes [ 0 ] . coord ;
}
2015-11-05 14:04:56 +02:00
void CGPath : : convert ( ui8 mode )
2015-10-27 02:34:47 +02:00
{
if ( mode = = 0 )
{
for ( auto & elem : nodes )
{
elem . coord = CGHeroInstance : : convertPosition ( elem . coord , true ) ;
}
}
}
2019-01-15 05:00:00 +02:00
CPathsInfo : : CPathsInfo ( const int3 & Sizes , const CGHeroInstance * hero_ )
: sizes ( Sizes ) , hero ( hero_ )
2015-10-27 02:34:47 +02:00
{
2015-11-08 07:27:51 +02:00
nodes . resize ( boost : : extents [ sizes . x ] [ sizes . y ] [ sizes . z ] [ ELayer : : NUM_LAYERS ] ) ;
2015-10-27 02:34:47 +02:00
}
2019-01-15 05:00:00 +02:00
CPathsInfo : : ~ CPathsInfo ( ) = default ;
2015-10-27 02:34:47 +02:00
2015-11-11 23:05:20 +02:00
const CGPathNode * CPathsInfo : : getPathInfo ( const int3 & tile ) const
2015-10-27 02:34:47 +02:00
{
2015-11-11 23:05:20 +02:00
assert ( vstd : : iswithin ( tile . x , 0 , sizes . x ) ) ;
assert ( vstd : : iswithin ( tile . y , 0 , sizes . y ) ) ;
assert ( vstd : : iswithin ( tile . z , 0 , sizes . z ) ) ;
2015-10-27 02:34:47 +02:00
2015-11-11 23:05:20 +02:00
return getNode ( tile ) ;
2015-10-27 02:34:47 +02:00
}
2015-11-11 23:05:20 +02:00
bool CPathsInfo : : getPath ( CGPath & out , const int3 & dst ) const
2015-10-27 02:34:47 +02:00
{
out . nodes . clear ( ) ;
2015-11-11 23:05:20 +02:00
const CGPathNode * curnode = getNode ( dst ) ;
2015-10-27 02:34:47 +02:00
if ( ! curnode - > theNodeBefore )
return false ;
while ( curnode )
{
2015-11-11 23:05:20 +02:00
const CGPathNode cpn = * curnode ;
2015-10-27 02:34:47 +02:00
curnode = curnode - > theNodeBefore ;
out . nodes . push_back ( cpn ) ;
}
return true ;
}
2015-11-11 23:05:20 +02:00
const CGPathNode * CPathsInfo : : getNode ( const int3 & coord ) const
2015-11-02 13:04:26 +02:00
{
2015-11-11 23:05:20 +02:00
auto landNode = & nodes [ coord . x ] [ coord . y ] [ coord . z ] [ ELayer : : LAND ] ;
if ( landNode - > reachable ( ) )
2015-11-02 13:04:26 +02:00
return landNode ;
else
2015-11-11 23:05:20 +02:00
return & nodes [ coord . x ] [ coord . y ] [ coord . z ] [ ELayer : : SAIL ] ;
}
2018-10-07 13:51:27 +02:00
PathNodeInfo : : PathNodeInfo ( )
2021-05-16 19:53:11 +02:00
: node ( nullptr ) , nodeObject ( nullptr ) , tile ( nullptr ) , coord ( - 1 , - 1 , - 1 ) , guarded ( false ) , isInitialPosition ( false )
2018-07-29 13:50:22 +02:00
{
}
2021-05-16 19:53:11 +02:00
void PathNodeInfo : : setNode ( CGameState * gs , CGPathNode * n )
2018-07-29 13:50:22 +02:00
{
node = n ;
if ( coord ! = node - > coord )
{
assert ( node - > coord . valid ( ) ) ;
coord = node - > coord ;
tile = gs - > getTile ( coord ) ;
2021-05-16 19:53:11 +02:00
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 ;
}
2018-07-29 13:50:22 +02:00
}
2018-08-01 20:46:06 +02:00
guarded = false ;
}
2021-05-16 19:53:11 +02:00
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 ) ;
}
}
2018-08-01 20:46:06 +02:00
CDestinationNodeInfo : : CDestinationNodeInfo ( )
2019-01-15 07:52:55 +02:00
: PathNodeInfo ( ) ,
blocked ( false ) ,
action ( CGPathNode : : ENodeAction : : UNKNOWN )
2018-08-01 20:46:06 +02:00
{
}
2021-05-16 19:53:11 +02:00
void CDestinationNodeInfo : : setNode ( CGameState * gs , CGPathNode * n )
2018-08-01 20:46:06 +02:00
{
2021-05-16 19:53:11 +02:00
PathNodeInfo : : setNode ( gs , n ) ;
2018-08-01 20:46:06 +02:00
2018-07-29 13:50:22 +02:00
blocked = false ;
2018-08-01 20:46:06 +02:00
action = CGPathNode : : ENodeAction : : UNKNOWN ;
2018-07-29 13:50:22 +02:00
}
2018-08-12 13:31:31 +02:00
bool CDestinationNodeInfo : : isBetterWay ( ) const
2018-08-08 21:24:18 +02:00
{
if ( node - > turns = = 0xff ) //we haven't been here before
return true ;
2019-01-15 07:52:55 +02:00
else
2020-09-21 23:19:40 +02:00
return cost < node - > getCost ( ) ; //this route is faster
2018-08-08 21:24:18 +02:00
}
2018-10-07 13:51:27 +02:00
bool PathNodeInfo : : isNodeObjectVisitable ( ) const
2018-07-29 13:50:22 +02:00
{
/// Hero can't visit objects while walking on water or flying
2021-05-16 19:53:11 +02:00
return ( node - > layer = = EPathfindingLayer : : LAND | | node - > layer = = EPathfindingLayer : : SAIL )
& & ( canSeeObj ( nodeObject ) | | canSeeObj ( nodeHero ) ) ;
2018-07-29 13:50:22 +02:00
}