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"
/*
* 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
*
*/
CPathfinder : : PathfinderOptions : : PathfinderOptions ( )
{
useFlying = false ;
useWaterWalking = false ;
useEmbarkAndDisembark = true ;
useTeleportTwoWay = true ;
useTeleportOneWay = true ;
useTeleportOneWayRandom = false ;
useTeleportWhirlpool = false ;
2015-11-04 14:05:22 +02:00
2015-11-08 09:06:24 +02:00
useCastleGate = false ;
2015-11-04 14:05:22 +02:00
lightweightFlyingMode = false ;
2015-11-08 06:44:00 +02:00
oneTurnSpecialLayersLimit = true ;
2015-11-10 20:07:27 +02:00
originalMovementRules = true ;
2015-10-27 02:34:47 +02:00
}
2015-11-11 21:08:15 +02:00
CPathfinder : : CPathfinder ( CPathsInfo & _out , CGameState * _gs , const CGHeroInstance * _hero )
2015-11-05 14:04:56 +02:00
: CGameInfoCallback ( _gs , boost : : optional < PlayerColor > ( ) ) , out ( _out ) , hero ( _hero )
2015-10-27 02:34:47 +02:00
{
assert ( hero ) ;
assert ( hero = = getHero ( hero - > id ) ) ;
out . hero = hero ;
out . hpos = hero - > getPosition ( false ) ;
if ( ! gs - > map - > isInTheMap ( out . hpos ) /* || !gs->map->isInTheMap(dest)*/ ) //check input
{
logGlobal - > errorStream ( ) < < " CGameState::calculatePaths: Hero outside the gs->map? How dare you... " ;
throw std : : runtime_error ( " Wrong checksum " ) ;
}
2015-11-10 18:15:59 +02:00
hlp = make_unique < CPathfinderHelper > ( hero ) ;
2015-11-12 13:04:33 +02:00
if ( hlp - > hasBonusOfType ( Bonus : : FLYING_MOVEMENT ) )
2015-10-27 02:34:47 +02:00
options . useFlying = true ;
2015-11-12 13:04:33 +02:00
if ( hlp - > hasBonusOfType ( Bonus : : WATER_WALKING ) )
2015-10-27 02:34:47 +02:00
options . useWaterWalking = true ;
2015-11-12 13:04:33 +02:00
if ( hlp - > hasBonusOfType ( Bonus : : WHIRLPOOL_PROTECTION ) )
2015-10-27 02:34:47 +02:00
options . useTeleportWhirlpool = true ;
2015-11-03 00:29:43 +02:00
initializeGraph ( ) ;
2015-10-27 02:34:47 +02:00
neighbours . reserve ( 16 ) ;
}
void CPathfinder : : calculatePaths ( )
{
2015-11-08 06:44:00 +02:00
auto passOneTurnLimitCheck = [ & ] ( bool shouldCheck ) - > bool
{
if ( options . oneTurnSpecialLayersLimit & & shouldCheck )
{
2015-11-08 07:27:51 +02:00
if ( ( cp - > layer = = ELayer : : AIR | | cp - > layer = = ELayer : : WATER )
2015-11-08 06:44:00 +02:00
& & cp - > accessible ! = CGPathNode : : ACCESSIBLE )
{
return false ;
}
}
return true ;
} ;
2015-10-27 02:34:47 +02:00
auto isBetterWay = [ & ] ( int remains , int turn ) - > bool
{
if ( dp - > turns = = 0xff ) //we haven't been here before
return true ;
else if ( dp - > turns > turn )
return true ;
2015-11-11 21:08:15 +02:00
else if ( dp - > turns > = turn & & dp - > moveRemains < remains ) //this route is faster
2015-10-27 02:34:47 +02:00
return true ;
return false ;
} ;
//logGlobal->infoStream() << boost::format("Calculating paths for hero %s (adress %d) of player %d") % hero->name % hero % hero->tempOwner;
//initial tile - set cost on 0 and add to the queue
2015-11-11 21:08:15 +02:00
CGPathNode * initialNode = out . getNode ( out . hpos , hero - > boat ? ELayer : : SAIL : ELayer : : LAND ) ;
2015-11-03 00:29:43 +02:00
initialNode - > turns = 0 ;
initialNode - > moveRemains = hero - > movement ;
2015-11-07 20:11:07 +02:00
pq . push ( initialNode ) ;
2015-10-27 02:34:47 +02:00
2015-11-07 20:11:07 +02:00
while ( ! pq . empty ( ) )
2015-10-27 02:34:47 +02:00
{
2015-11-07 20:11:07 +02:00
cp = pq . top ( ) ;
pq . pop ( ) ;
cp - > locked = true ;
2015-11-11 18:51:08 +02:00
ct = & gs - > map - > getTile ( cp - > coord ) ;
cObj = ct - > topVisitableObj ( cp - > coord = = out . hpos ) ;
2015-10-27 02:34:47 +02:00
int movement = cp - > moveRemains , turn = cp - > turns ;
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 ) ;
2015-11-12 13:04:33 +02:00
movement = hlp - > getMaxMovePoints ( cp - > layer ) ;
2015-10-27 02:34:47 +02:00
}
//add accessible neighbouring nodes to the queue
addNeighbours ( cp - > coord ) ;
for ( auto & neighbour : neighbours )
{
dt = & gs - > map - > getTile ( neighbour ) ;
2015-11-11 18:51:08 +02:00
dObj = dt - > topVisitableObj ( ) ;
2015-11-08 07:27:51 +02:00
for ( ELayer i = ELayer : : LAND ; i < = ELayer : : AIR ; i . advance ( 1 ) )
2015-10-27 02:34:47 +02:00
{
2015-11-02 15:03:03 +02:00
dp = out . getNode ( neighbour , i ) ;
2015-11-03 02:25:12 +02:00
if ( dp - > accessible = = CGPathNode : : NOT_SET )
continue ;
2015-11-07 20:11:07 +02:00
if ( dp - > locked )
continue ;
2015-11-08 06:44:00 +02:00
if ( ! passOneTurnLimitCheck ( cp - > turns ! = turn ) )
continue ;
2015-11-12 13:39:22 +02:00
if ( ! hlp - > isLayerAvailable ( i ) )
2015-11-09 18:57:26 +02:00
continue ;
2015-11-03 02:25:12 +02:00
if ( cp - > layer ! = i & & ! isLayerTransitionPossible ( ) )
2015-11-02 15:03:03 +02:00
continue ;
2015-11-04 10:47:43 +02:00
if ( ! isMovementToDestPossible ( ) )
2015-11-02 15:03:03 +02:00
continue ;
2015-11-03 02:25:12 +02:00
2015-11-11 18:51:08 +02:00
destAction = getDestAction ( ) ;
2015-11-12 13:04:33 +02:00
int cost = CPathfinderHelper : : getMovementCost ( hero , cp - > coord , dp - > coord , movement , hlp - > getTurnInfo ( ) ) ;
2015-11-02 15:03:03 +02:00
int remains = movement - cost ;
2015-11-07 23:26:41 +02:00
if ( destAction = = CGPathNode : : EMBARK | | destAction = = CGPathNode : : DISEMBARK )
2015-11-02 15:03:03 +02:00
{
2015-11-12 13:04:33 +02:00
remains = hero - > movementPointsAfterEmbark ( movement , cost , destAction - 1 , hlp - > getTurnInfo ( ) ) ;
2015-11-02 15:03:03 +02:00
cost = movement - remains ;
}
int turnAtNextTile = turn ;
if ( remains < 0 )
{
//occurs rarely, when hero with low movepoints tries to leave the road
2015-11-10 13:26:45 +02:00
hlp - > updateTurnInfo ( + + turnAtNextTile ) ;
2015-11-12 13:04:33 +02:00
int moveAtNextTile = hlp - > getMaxMovePoints ( i ) ;
cost = CPathfinderHelper : : getMovementCost ( hero , cp - > coord , dp - > coord , moveAtNextTile , hlp - > getTurnInfo ( ) ) ; //cost must be updated, movement points changed :(
2015-11-02 15:03:03 +02:00
remains = moveAtNextTile - cost ;
}
2015-10-27 02:34:47 +02:00
2015-11-08 06:44:00 +02:00
if ( isBetterWay ( remains , turnAtNextTile )
& & passOneTurnLimitCheck ( cp - > turns ! = turnAtNextTile | | ! remains ) )
2015-11-02 15:03:03 +02:00
{
assert ( dp ! = cp - > theNodeBefore ) ; //two tiles can't point to each other
dp - > moveRemains = remains ;
dp - > turns = turnAtNextTile ;
dp - > theNodeBefore = cp ;
2015-11-07 23:26:41 +02:00
dp - > action = destAction ;
2015-10-27 02:34:47 +02:00
2015-11-04 10:47:43 +02:00
if ( isMovementAfterDestPossible ( ) )
2015-11-07 20:11:07 +02:00
pq . push ( dp ) ;
2015-11-02 15:03:03 +02:00
}
2015-10-27 02:34:47 +02:00
}
} //neighbours loop
//just add all passable teleport exits
2015-11-11 18:51:08 +02:00
if ( cObj & & canVisitObject ( ) )
2015-10-27 02:34:47 +02:00
{
addTeleportExits ( ) ;
for ( auto & neighbour : neighbours )
{
2015-11-02 15:03:03 +02:00
dp = out . getNode ( neighbour , cp - > layer ) ;
2015-11-07 20:11:07 +02:00
if ( dp - > locked )
continue ;
2015-10-27 02:34:47 +02:00
if ( isBetterWay ( movement , turn ) )
{
dp - > moveRemains = movement ;
dp - > turns = turn ;
dp - > theNodeBefore = cp ;
2015-11-07 23:26:41 +02:00
dp - > action = CGPathNode : : NORMAL ;
2015-11-07 20:11:07 +02:00
pq . push ( dp ) ;
2015-10-27 02:34:47 +02:00
}
}
}
} //queue loop
}
2015-11-11 21:08:15 +02:00
void CPathfinder : : addNeighbours ( const int3 & coord )
2015-10-27 02:34:47 +02:00
{
neighbours . clear ( ) ;
std : : vector < int3 > tiles ;
2015-11-10 01:15:27 +02:00
CPathfinderHelper : : getNeighbours ( gs , * ct , coord , tiles , boost : : logic : : indeterminate , cp - > layer = = ELayer : : SAIL ) ; // TODO: find out if we still need "limitCoastSailing" option
2015-11-02 10:06:06 +02:00
if ( canVisitObject ( ) )
2015-10-27 02:34:47 +02:00
{
2015-11-11 18:51:08 +02:00
if ( cObj )
2015-10-27 02:34:47 +02:00
{
2015-11-02 10:06:06 +02:00
for ( int3 tile : tiles )
{
2015-11-11 18:51:08 +02:00
if ( canMoveBetween ( tile , cObj - > visitablePos ( ) ) )
2015-11-02 10:06:06 +02:00
neighbours . push_back ( tile ) ;
}
2015-10-27 02:34:47 +02:00
}
2015-11-02 10:06:06 +02:00
else
vstd : : concatenate ( neighbours , tiles ) ;
2015-10-27 02:34:47 +02:00
}
else
vstd : : concatenate ( neighbours , tiles ) ;
}
void CPathfinder : : addTeleportExits ( bool noTeleportExcludes )
{
2015-11-11 18:51:08 +02:00
assert ( cObj ) ;
2015-10-27 02:34:47 +02:00
neighbours . clear ( ) ;
auto isAllowedTeleportEntrance = [ & ] ( const CGTeleport * obj ) - > bool
{
if ( ! gs - > isTeleportEntrancePassable ( obj , hero - > tempOwner ) )
return false ;
if ( noTeleportExcludes )
return true ;
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 ;
} ;
2015-11-11 21:08:15 +02:00
const CGTeleport * sTileTeleport = dynamic_cast < const CGTeleport * > ( cObj ) ;
2015-10-27 02:34:47 +02:00
if ( isAllowedTeleportEntrance ( sTileTeleport ) )
{
for ( auto objId : gs - > getTeleportChannelExits ( sTileTeleport - > channel , hero - > tempOwner ) )
{
auto obj = getObj ( objId ) ;
2015-11-12 00:16:06 +02:00
if ( dynamic_cast < const CGWhirlpool * > ( obj ) )
{
auto pos = obj - > getBlockedPos ( ) ;
for ( auto p : pos )
{
if ( gs - > getTile ( p ) - > topVisitableId ( ) = = obj - > ID )
neighbours . push_back ( p ) ;
}
}
else if ( CGTeleport : : isExitPassable ( gs , hero , obj ) )
2015-10-27 02:34:47 +02:00
neighbours . push_back ( obj - > visitablePos ( ) ) ;
}
}
2015-11-08 09:06:24 +02:00
if ( options . useCastleGate
2015-11-11 18:51:08 +02:00
& & ( cObj - > ID = = Obj : : TOWN & & cObj - > subID = = ETownType : : INFERNO
& & getPlayerRelations ( hero - > tempOwner , cObj - > tempOwner ) ! = 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
auto towns = gs - > getPlayer ( hero - > tempOwner ) - > towns ;
for ( const auto & town : towns )
{
2015-11-11 18:51:08 +02:00
if ( town - > id ! = cObj - > id & & town - > visitingHero = = nullptr
2015-11-08 09:06:24 +02:00
& & town - > hasBuilt ( BuildingID : : CASTLE_GATE , ETownType : : INFERNO ) )
{
neighbours . push_back ( town - > visitablePos ( ) ) ;
}
}
}
2015-10-27 02:34:47 +02:00
}
2015-11-08 07:39:00 +02:00
bool CPathfinder : : isLayerTransitionPossible ( ) const
2015-11-04 11:29:51 +02:00
{
2015-11-10 18:15:59 +02:00
/// No layer transition allowed when previous node action is BATTLE
if ( cp - > action = = CGPathNode : : BATTLE )
return false ;
2015-11-11 21:08:15 +02:00
switch ( cp - > layer )
2015-11-10 18:15:59 +02:00
{
2015-11-11 21:08:15 +02:00
case ELayer : : LAND :
if ( options . lightweightFlyingMode & & dp - > layer = = ELayer : : AIR )
{
if ( ! isSourceInitialPosition ( ) )
return false ;
}
else if ( dp - > layer = = ELayer : : SAIL )
{
/// Cannot enter empty water tile from land -> it has to be visitable
if ( dp - > accessible = = CGPathNode : : ACCESSIBLE )
return false ;
}
break ;
case ELayer : : SAIL :
if ( dp - > layer ! = ELayer : : LAND )
return false ;
if ( ! dt - > isCoastal ( ) )
return false ;
//tile must be accessible -> exception: unblocked blockvis tiles -> clear but guarded by nearby monster coast
if ( ( dp - > accessible ! = CGPathNode : : ACCESSIBLE & & ( dp - > accessible ! = CGPathNode : : BLOCKVIS | | dt - > blocked ) )
| | dt - > visitable ) //TODO: passableness problem -> town says it's passable (thus accessible) but we obviously can't disembark onto town gate
{
return false ;
}
2015-11-11 21:29:20 +02:00
2015-11-11 21:08:15 +02:00
break ;
case ELayer : : AIR :
if ( dp - > layer ! = ELayer : : LAND )
return false ;
2015-11-10 20:07:27 +02:00
if ( options . originalMovementRules )
2015-11-10 18:15:59 +02:00
{
2015-11-11 21:08:15 +02:00
if ( ( cp - > accessible ! = CGPathNode : : ACCESSIBLE & &
2015-11-10 20:07:27 +02:00
cp - > accessible ! = CGPathNode : : VISITABLE ) & &
( dp - > accessible ! = CGPathNode : : VISITABLE & &
dp - > accessible ! = CGPathNode : : ACCESSIBLE ) )
{
return false ;
}
}
else if ( cp - > accessible ! = CGPathNode : : ACCESSIBLE & & dp - > accessible ! = CGPathNode : : ACCESSIBLE )
{
/// Hero that fly can only land on accessible tiles
2015-11-10 18:15:59 +02:00
return false ;
}
2015-11-04 11:29:51 +02:00
2015-11-11 21:08:15 +02:00
break ;
case ELayer : : WATER :
if ( dp - > layer ! = ELayer : : LAND )
2015-11-04 11:29:51 +02:00
return false ;
2015-11-11 21:08:15 +02:00
break ;
2015-11-04 11:29:51 +02:00
}
2015-11-11 21:08:15 +02:00
2015-11-04 11:29:51 +02:00
return true ;
}
2015-11-11 18:51:08 +02:00
bool CPathfinder : : isMovementToDestPossible ( ) const
2015-10-27 02:34:47 +02:00
{
2015-11-05 14:04:56 +02:00
switch ( dp - > layer )
2015-10-27 02:34:47 +02:00
{
2015-11-11 21:08:15 +02:00
case ELayer : : LAND :
if ( ! canMoveBetween ( cp - > coord , dp - > coord ) | | dp - > accessible = = CGPathNode : : BLOCKED )
return false ;
if ( isSourceGuarded ( ) )
{
if ( ! ( options . originalMovementRules & & cp - > layer = = ELayer : : AIR ) & &
! isDestinationGuardian ( ) ) // Can step into tile of guard
2015-11-10 20:07:27 +02:00
{
2015-10-27 02:34:47 +02:00
return false ;
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
2015-11-11 21:08:15 +02:00
case ELayer : : SAIL :
if ( ! canMoveBetween ( cp - > coord , dp - > coord ) | | dp - > accessible = = CGPathNode : : BLOCKED )
return false ;
2015-11-11 21:29:20 +02:00
if ( isSourceGuarded ( ) )
{
// Hero embarked a boat standing on a guarded tile -> we must allow to move away from that tile
if ( cp - > action ! = CGPathNode : : EMBARK & & ! isDestinationGuardian ( ) )
return false ;
}
2015-10-27 02:34:47 +02:00
2015-11-11 21:08:15 +02:00
if ( cp - > layer = = ELayer : : LAND )
{
if ( ! dObj )
2015-11-02 15:03:03 +02:00
return false ;
2015-11-11 21:08:15 +02:00
if ( dObj - > ID ! = Obj : : BOAT & & dObj - > ID ! = Obj : : HERO )
2015-11-02 15:03:03 +02:00
return false ;
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 ;
2015-11-02 15:03:03 +02:00
2015-11-11 21:08:15 +02:00
case ELayer : : WATER :
if ( ! canMoveBetween ( cp - > coord , dp - > coord ) | | dp - > accessible ! = CGPathNode : : ACCESSIBLE )
return false ;
if ( isDestinationGuarded ( ) )
return false ;
break ;
2015-10-27 02:34:47 +02:00
}
return true ;
}
2015-11-08 07:39:00 +02:00
bool CPathfinder : : isMovementAfterDestPossible ( ) const
2015-10-27 02:34:47 +02:00
{
2015-11-08 03:41:06 +02:00
switch ( destAction )
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-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
2015-11-11 21:08:15 +02:00
if ( CGTeleport : : isTeleport ( dObj ) )
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
2015-11-11 21:08:15 +02:00
auto whirlpool = dynamic_cast < const CGWhirlpool * > ( dObj ) ;
2015-11-08 03:41:06 +02:00
if ( ! whirlpool | | options . useTeleportWhirlpool )
2015-11-04 10:53:52 +02:00
return true ;
2015-11-08 03:41:06 +02:00
}
2015-11-11 21:08:15 +02:00
break ;
2015-11-08 03:41:06 +02:00
case CGPathNode : : NORMAL :
return true ;
2015-11-04 10:53:52 +02:00
2015-11-08 03:41:06 +02:00
case CGPathNode : : EMBARK :
if ( options . useEmbarkAndDisembark )
return true ;
2015-11-04 10:53:52 +02:00
2015-11-11 21:08:15 +02:00
break ;
2015-11-08 03:41:06 +02:00
case CGPathNode : : DISEMBARK :
if ( options . useEmbarkAndDisembark & & ! isDestinationGuarded ( ) )
return true ;
2015-11-10 18:15:59 +02:00
2015-11-11 21:08:15 +02:00
break ;
2015-11-10 18:15:59 +02:00
case CGPathNode : : BATTLE :
/// Movement after BATTLE action only possible to guardian tile
2015-11-11 21:08:15 +02:00
if ( isDestinationGuardian ( ) )
2015-11-10 18:15:59 +02:00
return true ;
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
return false ;
}
2015-11-11 18:51:08 +02:00
CGPathNode : : ENodeAction CPathfinder : : getDestAction ( ) const
{
CGPathNode : : ENodeAction action = CGPathNode : : NORMAL ;
switch ( dp - > layer )
{
case ELayer : : LAND :
if ( cp - > layer = = ELayer : : SAIL )
{
// TODO: Handle dismebark into guarded areaa
action = CGPathNode : : DISEMBARK ;
break ;
}
case ELayer : : SAIL :
if ( dObj )
{
auto objRel = getPlayerRelations ( dObj - > tempOwner , hero - > tempOwner ) ;
if ( dObj - > ID = = Obj : : BOAT )
action = CGPathNode : : EMBARK ;
else if ( dObj - > ID = = Obj : : HERO )
{
if ( objRel = = PlayerRelations : : ENEMIES )
action = CGPathNode : : BATTLE ;
else
action = CGPathNode : : BLOCKING_VISIT ;
}
else if ( dObj - > ID = = Obj : : TOWN & & objRel = = PlayerRelations : : ENEMIES )
{
const CGTownInstance * townObj = dynamic_cast < const CGTownInstance * > ( dObj ) ;
2015-11-11 21:08:15 +02:00
if ( townObj - > armedGarrison ( ) )
2015-11-11 18:51:08 +02:00
action = CGPathNode : : BATTLE ;
}
else if ( dObj - > ID = = Obj : : GARRISON | | dObj - > ID = = Obj : : GARRISON2 )
{
const CGGarrison * garrisonObj = dynamic_cast < const CGGarrison * > ( dObj ) ;
if ( ( garrisonObj - > stacksCount ( ) & & objRel = = PlayerRelations : : ENEMIES ) | | isDestinationGuarded ( true ) )
action = CGPathNode : : BATTLE ;
}
else if ( isDestinationGuardian ( ) )
action = CGPathNode : : BATTLE ;
else if ( dObj - > blockVisit & & ( ! options . useCastleGate | | dObj - > ID ! = Obj : : TOWN ) )
action = CGPathNode : : BLOCKING_VISIT ;
if ( action = = CGPathNode : : NORMAL )
{
if ( options . originalMovementRules & & isDestinationGuarded ( ) )
action = CGPathNode : : BATTLE ;
else
action = CGPathNode : : VISIT ;
}
}
else if ( isDestinationGuarded ( ) )
action = CGPathNode : : BATTLE ;
break ;
}
return action ;
}
2015-11-08 07:39:00 +02:00
bool CPathfinder : : isSourceInitialPosition ( ) const
2015-11-04 14:38:15 +02:00
{
return cp - > coord = = out . hpos ;
}
2015-11-08 07:39:00 +02:00
int3 CPathfinder : : getSourceGuardPosition ( ) const
2015-10-27 02:34:47 +02:00
{
return gs - > map - > guardingCreaturePositions [ cp - > coord . x ] [ cp - > coord . y ] [ cp - > coord . z ] ;
}
2015-11-08 07:39:00 +02:00
bool CPathfinder : : isSourceGuarded ( ) const
2015-10-27 02:34:47 +02:00
{
2015-11-11 21:29:20 +02:00
/// Hero can move from guarded tile if movement started on that tile
/// It's possible at least in these cases:
/// - Map start with hero on guarded tile
/// - Dimention door used
/// TODO: check what happen when there is several guards
2015-11-04 14:38:15 +02:00
if ( getSourceGuardPosition ( ) ! = int3 ( - 1 , - 1 , - 1 ) & & ! isSourceInitialPosition ( ) )
2015-10-27 02:34:47 +02:00
{
2015-11-11 21:29:20 +02:00
return true ;
2015-10-27 02:34:47 +02:00
}
return false ;
}
2015-11-08 07:39:00 +02:00
bool CPathfinder : : isDestinationGuarded ( const bool ignoreAccessibility ) const
2015-10-27 02:34:47 +02:00
{
if ( gs - > map - > guardingCreaturePositions [ dp - > coord . x ] [ dp - > coord . y ] [ dp - > coord . z ] . valid ( )
2015-11-08 02:10:48 +02:00
& & ( ignoreAccessibility | | dp - > accessible = = CGPathNode : : BLOCKVIS ) )
2015-10-27 02:34:47 +02:00
{
return true ;
}
return false ;
}
2015-11-08 07:39:00 +02:00
bool CPathfinder : : isDestinationGuardian ( ) const
2015-10-27 02:34:47 +02:00
{
return getSourceGuardPosition ( ) = = dp - > coord ;
}
void CPathfinder : : initializeGraph ( )
{
2015-11-11 21:08:15 +02:00
auto updateNode = [ & ] ( int3 pos , ELayer layer , const TerrainTile * tinfo , bool blockNotAccessible )
2015-11-02 10:06:06 +02:00
{
2015-11-03 00:29:43 +02:00
auto node = out . getNode ( pos , layer ) ;
2015-11-08 06:44:00 +02:00
auto accessibility = evaluateAccessibility ( pos , tinfo ) ;
2015-11-10 18:15:59 +02:00
/// TODO: Probably this shouldn't be handled by initializeGraph
2015-11-08 06:44:00 +02:00
if ( blockNotAccessible
& & ( accessibility ! = CGPathNode : : ACCESSIBLE | | tinfo - > terType = = ETerrainType : : WATER ) )
2015-11-11 23:05:20 +02:00
{
2015-11-08 06:44:00 +02:00
accessibility = CGPathNode : : BLOCKED ;
2015-11-11 23:05:20 +02:00
}
node - > update ( pos , layer , accessibility ) ;
2015-11-02 10:06:06 +02:00
} ;
2015-11-02 15:03:03 +02:00
int3 pos ;
2015-10-27 02:34:47 +02:00
for ( pos . x = 0 ; pos . x < out . sizes . x ; + + pos . x )
{
for ( pos . y = 0 ; pos . y < out . sizes . y ; + + pos . y )
{
for ( pos . z = 0 ; pos . z < out . sizes . z ; + + pos . z )
{
2015-11-11 21:08:15 +02:00
const TerrainTile * tinfo = & gs - > map - > getTile ( pos ) ;
2015-11-05 14:04:56 +02:00
switch ( tinfo - > terType )
2015-11-02 10:06:06 +02:00
{
case ETerrainType : : ROCK :
break ;
case ETerrainType : : WATER :
2015-11-08 07:27:51 +02:00
updateNode ( pos , ELayer : : SAIL , tinfo , false ) ;
2015-11-03 02:25:12 +02:00
if ( options . useFlying )
2015-11-08 07:27:51 +02:00
updateNode ( pos , ELayer : : AIR , tinfo , true ) ;
2015-11-03 02:25:12 +02:00
if ( options . useWaterWalking )
2015-11-10 18:15:59 +02:00
updateNode ( pos , ELayer : : WATER , tinfo , false ) ;
2015-11-02 10:06:06 +02:00
break ;
default :
2015-11-08 07:27:51 +02:00
updateNode ( pos , ELayer : : LAND , tinfo , false ) ;
2015-11-03 02:25:12 +02:00
if ( options . useFlying )
2015-11-08 07:27:51 +02:00
updateNode ( pos , ELayer : : AIR , tinfo , true ) ;
2015-11-02 10:06:06 +02:00
break ;
}
2015-10-27 02:34:47 +02:00
}
}
}
}
2015-11-11 21:08:15 +02:00
CGPathNode : : EAccessibility CPathfinder : : evaluateAccessibility ( const int3 & pos , const TerrainTile * tinfo ) const
2015-10-27 02:34:47 +02:00
{
CGPathNode : : EAccessibility ret = ( tinfo - > blocked ? CGPathNode : : BLOCKED : CGPathNode : : ACCESSIBLE ) ;
2015-11-05 14:04:56 +02:00
if ( tinfo - > terType = = ETerrainType : : ROCK | | ! isVisible ( pos , hero - > tempOwner ) )
2015-10-27 02:34:47 +02:00
return CGPathNode : : BLOCKED ;
if ( tinfo - > visitable )
{
if ( tinfo - > visitableObjects . front ( ) - > ID = = Obj : : SANCTUARY & & tinfo - > visitableObjects . back ( ) - > ID = = Obj : : HERO & & tinfo - > visitableObjects . back ( ) - > tempOwner ! = hero - > tempOwner ) //non-owned hero stands on Sanctuary
{
return CGPathNode : : BLOCKED ;
}
else
{
2015-11-11 21:08:15 +02:00
for ( const CGObjectInstance * obj : tinfo - > visitableObjects )
2015-10-27 02:34:47 +02:00
{
if ( obj - > passableFor ( hero - > tempOwner ) )
{
ret = CGPathNode : : ACCESSIBLE ;
}
else if ( obj - > blockVisit )
{
return CGPathNode : : BLOCKVIS ;
}
else if ( obj - > ID ! = Obj : : EVENT ) //pathfinder should ignore placed events
{
2015-11-11 21:08:15 +02:00
ret = CGPathNode : : VISITABLE ;
2015-10-27 02:34:47 +02:00
}
}
}
}
else if ( gs - > map - > guardingCreaturePositions [ pos . x ] [ pos . y ] [ pos . z ] . valid ( )
& & ! tinfo - > blocked )
{
// Monster close by; blocked visit for battle.
return CGPathNode : : BLOCKVIS ;
}
return ret ;
}
2015-11-11 21:08:15 +02:00
bool CPathfinder : : canMoveBetween ( const int3 & a , const int3 & b ) const
2015-10-27 02:34:47 +02:00
{
return gs - > checkForVisitableDir ( a , b ) ;
}
bool CPathfinder : : addTeleportTwoWay ( const CGTeleport * obj ) const
{
return options . useTeleportTwoWay & & gs - > isTeleportChannelBidirectional ( obj - > channel , hero - > tempOwner ) ;
}
bool CPathfinder : : addTeleportOneWay ( const CGTeleport * obj ) const
{
if ( options . useTeleportOneWay & & isTeleportChannelUnidirectional ( obj - > channel , hero - > tempOwner ) )
{
auto passableExits = CGTeleport : : getPassableExits ( gs , hero , gs - > getTeleportChannelExits ( obj - > channel , hero - > tempOwner ) ) ;
if ( passableExits . size ( ) = = 1 )
return true ;
}
return false ;
}
bool CPathfinder : : addTeleportOneWayRandom ( const CGTeleport * obj ) const
{
if ( options . useTeleportOneWayRandom & & isTeleportChannelUnidirectional ( obj - > channel , hero - > tempOwner ) )
{
auto passableExits = CGTeleport : : getPassableExits ( gs , hero , gs - > getTeleportChannelExits ( obj - > channel , hero - > tempOwner ) ) ;
if ( passableExits . size ( ) > 1 )
return true ;
}
return false ;
}
bool CPathfinder : : addTeleportWhirlpool ( const CGWhirlpool * obj ) const
{
return options . useTeleportWhirlpool & & obj ;
}
2015-11-02 10:06:06 +02:00
bool CPathfinder : : canVisitObject ( ) const
{
//hero can't visit objects while walking on water or flying
2015-11-08 07:27:51 +02:00
return cp - > layer = = ELayer : : LAND | | cp - > layer = = ELayer : : SAIL ;
2015-11-02 10:06:06 +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
{
2015-11-12 13:04:33 +02:00
bonuses = hero - > getAllBonuses ( Selector : : days ( turn ) , nullptr ) ;
}
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
{
return bonuses - > getFirst ( Selector : : type ( type ) . And ( Selector : : subtype ( subtype ) ) ) ;
}
int TurnInfo : : valOfBonuses ( Bonus : : BonusType type , int subtype ) const
{
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 )
maxMovePointsLand = hero - > maxMovePoints ( true , this ) ;
if ( maxMovePointsWater = = - 1 )
maxMovePointsWater = hero - > maxMovePoints ( false , this ) ;
2015-11-12 04:20:32 +02:00
return layer = = EPathfindingLayer : : SAIL ? maxMovePointsWater : maxMovePointsLand ;
}
2015-11-10 13:26:45 +02:00
CPathfinderHelper : : CPathfinderHelper ( const CGHeroInstance * Hero )
2015-11-12 13:39:22 +02:00
: turn ( - 1 ) , hero ( Hero )
2015-11-10 13:26:45 +02:00
{
turnsInfo . reserve ( 16 ) ;
updateTurnInfo ( ) ;
}
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
{
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
}
2015-11-11 21:08:15 +02:00
void CPathfinderHelper : : getNeighbours ( CGameState * gs , const TerrainTile & srct , const int3 & tile , std : : vector < int3 > & vec , const boost : : logic : : tribool & onLand , const bool limitCoastSailing )
2015-11-10 01:15:27 +02:00
{
static const int3 dirs [ ] = { int3 ( 0 , 1 , 0 ) , int3 ( 0 , - 1 , 0 ) , int3 ( - 1 , 0 , 0 ) , int3 ( + 1 , 0 , 0 ) ,
int3 ( 1 , 1 , 0 ) , int3 ( - 1 , 1 , 0 ) , int3 ( 1 , - 1 , 0 ) , int3 ( - 1 , - 1 , 0 ) } ;
//vec.reserve(8); //optimization
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 ;
if ( ! gs - > isInTheMap ( hlp ) )
continue ;
2015-11-11 21:08:15 +02:00
const TerrainTile & hlpt = gs - > map - > getTile ( hlp ) ;
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;
// }
if ( srct . terType = = ETerrainType : : WATER & & limitCoastSailing & & hlpt . terType = = ETerrainType : : WATER & & dir . x & & dir . y ) //diagonal move through water
{
int3 hlp1 = tile ,
hlp2 = tile ;
hlp1 . x + = dir . x ;
hlp2 . y + = dir . y ;
if ( gs - > map - > getTile ( hlp1 ) . terType ! = ETerrainType : : WATER | | gs - > map - > getTile ( hlp2 ) . terType ! = ETerrainType : : WATER )
continue ;
}
2015-11-11 21:08:15 +02:00
if ( ( indeterminate ( onLand ) | | onLand = = ( hlpt . terType ! = ETerrainType : : WATER ) )
2015-11-10 01:15:27 +02:00
& & hlpt . terType ! = ETerrainType : : ROCK )
{
vec . push_back ( hlp ) ;
}
}
}
2015-11-11 21:08:15 +02:00
int CPathfinderHelper : : getMovementCost ( const CGHeroInstance * h , const int3 & src , const int3 & dst , const int remainingMovePoints , const TurnInfo * ti , const bool checkLast )
2015-11-10 01:15:27 +02:00
{
if ( src = = dst ) //same tile
return 0 ;
2015-11-10 13:26:45 +02:00
if ( ! ti )
2015-11-12 04:20:32 +02:00
ti = new TurnInfo ( h ) ;
2015-11-10 13:26:45 +02:00
2015-11-10 01:15:27 +02:00
auto s = h - > cb - > getTile ( src ) , d = h - > cb - > getTile ( dst ) ;
2015-11-10 13:26:45 +02:00
int ret = h - > getTileCost ( * d , * s , ti ) ;
2015-11-10 01:15:27 +02:00
2015-11-12 13:04:33 +02:00
if ( d - > blocked & & ti - > hasBonusOfType ( Bonus : : FLYING_MOVEMENT ) )
2015-11-10 01:15:27 +02:00
{
2015-11-12 13:04:33 +02:00
ret * = ( 100.0 + ti - > valOfBonuses ( Bonus : : FLYING_MOVEMENT ) ) / 100.0 ;
2015-11-10 01:15:27 +02:00
}
else if ( d - > terType = = ETerrainType : : WATER )
{
if ( h - > boat & & s - > hasFavourableWinds ( ) & & d - > hasFavourableWinds ( ) ) //Favourable Winds
ret * = 0.666 ;
2015-11-12 13:04:33 +02:00
else if ( ! h - > boat & & ti - > hasBonusOfType ( Bonus : : WATER_WALKING ) )
2015-11-10 01:15:27 +02:00
{
2015-11-12 13:04:33 +02:00
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 ;
ret * = 1.414213 ;
//diagonal move costs too much but normal move is possible - allow diagonal move for remaining move points
if ( ret > remainingMovePoints & & remainingMovePoints > = old )
return remainingMovePoints ;
}
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
getNeighbours ( h - > cb - > gameState ( ) , * d , dst , vec , s - > terType ! = ETerrainType : : WATER , true ) ;
for ( auto & elem : vec )
{
2015-11-10 13:26:45 +02:00
int fcost = getMovementCost ( h , dst , elem , left , ti , false ) ;
2015-11-10 01:15:27 +02:00
if ( fcost < = left )
return ret ;
}
ret = remainingMovePoints ;
}
return ret ;
}
2015-11-11 21:08:15 +02:00
int CPathfinderHelper : : getMovementCost ( const CGHeroInstance * h , const int3 & dst )
2015-11-10 01:15:27 +02:00
{
2015-11-10 01:30:05 +02:00
return getMovementCost ( h , h - > visitablePos ( ) , dst , h - > movement ) ;
2015-11-10 01:15:27 +02:00
}
2015-11-11 23:05:20 +02:00
CGPathNode : : CGPathNode ( )
: coord ( int3 ( - 1 , - 1 , - 1 ) ) , layer ( ELayer : : WRONG )
2015-11-07 21:16:45 +02:00
{
reset ( ) ;
}
void CGPathNode : : reset ( )
2015-10-27 02:34:47 +02:00
{
2015-11-07 20:11:07 +02:00
locked = false ;
2015-10-27 02:34:47 +02:00
accessible = NOT_SET ;
moveRemains = 0 ;
turns = 255 ;
theNodeBefore = nullptr ;
2015-11-07 23:26:41 +02:00
action = UNKNOWN ;
2015-10-27 02:34:47 +02:00
}
2015-11-11 23:05:20 +02:00
void CGPathNode : : update ( const int3 & Coord , const ELayer Layer , const EAccessibility Accessible )
{
if ( layer = = ELayer : : WRONG )
{
coord = Coord ;
layer = Layer ;
}
else
reset ( ) ;
accessible = Accessible ;
}
2015-10-27 02:34:47 +02:00
bool CGPathNode : : reachable ( ) const
{
return turns < 255 ;
}
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 ) ;
}
}
}
2015-11-11 21:08:15 +02:00
CPathsInfo : : CPathsInfo ( const int3 & Sizes )
2015-11-05 14:04:56 +02:00
: sizes ( Sizes )
2015-10-27 02:34:47 +02:00
{
hero = nullptr ;
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
}
CPathsInfo : : ~ CPathsInfo ( )
{
}
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
boost : : unique_lock < boost : : mutex > pathLock ( pathMx ) ;
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
{
boost : : unique_lock < boost : : mutex > pathLock ( pathMx ) ;
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
int CPathsInfo : : getDistance ( const int3 & tile ) const
2015-10-27 02:34:47 +02:00
{
boost : : unique_lock < boost : : mutex > pathLock ( pathMx ) ;
CGPath ret ;
2015-11-11 23:05:20 +02:00
if ( getPath ( ret , tile ) )
2015-10-27 02:34:47 +02:00
return ret . nodes . size ( ) ;
else
return 255 ;
}
2015-11-02 13:04:26 +02:00
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 ] ;
}
CGPathNode * CPathsInfo : : getNode ( const int3 & coord , const ELayer layer )
{
return & nodes [ coord . x ] [ coord . y ] [ coord . z ] [ layer ] ;
2015-11-02 13:04:26 +02:00
}