2021-05-15 20:56:31 +02:00
/*
* ExecuteHeroChain . cpp , part of VCMI engine
*
* Authors : listed in file AUTHORS in main folder
*
* License : GNU General Public License v2 .0 or later
* Full text of license available in license . txt file , in main folder
*
*/
# include "StdInc.h"
# include "ExecuteHeroChain.h"
2021-05-16 14:39:38 +02:00
# include "../AIGateway.h"
2021-05-15 20:56:31 +02:00
# include "../Engine/Nullkiller.h"
2022-09-26 20:01:07 +02:00
namespace NKAI
{
2021-05-15 20:56:31 +02:00
using namespace Goals ;
ExecuteHeroChain : : ExecuteHeroChain ( const AIPath & path , const CGObjectInstance * obj )
2021-05-16 13:56:27 +02:00
: ElementarGoal ( Goals : : EXECUTE_HERO_CHAIN ) , chainPath ( path ) , closestWayRatio ( 1 )
2021-05-15 20:56:31 +02:00
{
hero = path . targetHero ;
2021-05-15 20:57:27 +02:00
tile = path . targetTile ( ) ;
2024-08-24 14:54:00 +02:00
closestWayRatio = 1 ;
2021-05-15 20:57:27 +02:00
if ( obj )
{
objid = obj - > id . getNum ( ) ;
2024-07-18 12:37:18 +02:00
# if NKAI_TRACE_LEVEL >= 1
targetName = obj - > getObjectName ( ) + tile . toString ( ) ;
# else
2024-10-13 15:05:50 +02:00
targetName = obj - > getTypeName ( ) + tile . toString ( ) ;
2024-07-18 12:37:18 +02:00
# endif
2021-05-15 20:57:27 +02:00
}
else
{
targetName = " tile " + tile . toString ( ) ;
}
2021-05-15 20:56:31 +02:00
}
bool ExecuteHeroChain : : operator = = ( const ExecuteHeroChain & other ) const
{
2021-05-16 13:38:53 +02:00
return tile = = other . tile
& & chainPath . targetHero = = other . chainPath . targetHero
& & chainPath . nodes . size ( ) = = other . chainPath . nodes . size ( )
& & chainPath . chainMask = = other . chainPath . chainMask ;
2021-05-15 20:56:31 +02:00
}
2024-04-14 14:23:44 +02:00
std : : vector < ObjectInstanceID > ExecuteHeroChain : : getAffectedObjects ( ) const
{
std : : vector < ObjectInstanceID > affectedObjects = { chainPath . targetHero - > id } ;
if ( objid ! = - 1 )
affectedObjects . push_back ( ObjectInstanceID ( objid ) ) ;
for ( auto & node : chainPath . nodes )
{
if ( node . targetHero )
affectedObjects . push_back ( node . targetHero - > id ) ;
}
vstd : : removeDuplicates ( affectedObjects ) ;
return affectedObjects ;
}
bool ExecuteHeroChain : : isObjectAffected ( ObjectInstanceID id ) const
{
if ( chainPath . targetHero - > id = = id | | objid = = id )
return true ;
for ( auto & node : chainPath . nodes )
{
if ( node . targetHero & & node . targetHero - > id = = id )
return true ;
}
return false ;
}
2021-05-16 14:39:38 +02:00
void ExecuteHeroChain : : accept ( AIGateway * ai )
2021-05-15 20:56:31 +02:00
{
2021-05-15 20:57:27 +02:00
logAi - > debug ( " Executing hero chain towards %s. Path %s " , targetName , chainPath . toString ( ) ) ;
2021-05-15 20:56:31 +02:00
2021-05-16 13:38:26 +02:00
ai - > nullkiller - > setActive ( chainPath . targetHero , tile ) ;
2023-02-28 09:07:59 +02:00
ai - > nullkiller - > setTargetObject ( objid ) ;
2024-09-01 23:58:47 +02:00
ai - > nullkiller - > objectClusterizer - > reset ( ) ;
2021-05-16 13:38:26 +02:00
2023-06-04 15:02:02 +02:00
auto targetObject = ai - > myCb - > getObj ( static_cast < ObjectInstanceID > ( objid ) , false ) ;
if ( chainPath . turn ( ) = = 0 & & targetObject & & targetObject - > ID = = Obj : : TOWN )
{
auto relations = ai - > myCb - > getPlayerRelations ( ai - > playerID , targetObject - > getOwner ( ) ) ;
if ( relations = = PlayerRelations : : ENEMIES )
{
ai - > nullkiller - > armyFormation - > rearrangeArmyForSiege (
dynamic_cast < const CGTownInstance * > ( targetObject ) ,
chainPath . targetHero ) ;
}
}
2021-05-15 20:56:31 +02:00
std : : set < int > blockedIndexes ;
for ( int i = chainPath . nodes . size ( ) - 1 ; i > = 0 ; i - - )
{
2024-03-28 13:39:15 +02:00
auto * node = & chainPath . nodes [ i ] ;
2021-05-15 20:56:31 +02:00
2024-03-28 13:39:15 +02:00
const CGHeroInstance * hero = node - > targetHero ;
2019-12-15 15:47:21 +02:00
HeroPtr heroPtr = hero ;
2021-05-15 20:56:31 +02:00
2024-04-20 09:33:37 +02:00
if ( ! heroPtr . validAndSet ( ) )
{
2024-07-03 19:01:38 +02:00
logAi - > error ( " Hero %s was lost. Exit hero chain. " , heroPtr . name ( ) ) ;
2024-04-20 09:33:37 +02:00
return ;
}
2024-03-28 13:39:15 +02:00
if ( node - > parentIndex > = i )
2021-05-16 14:39:38 +02:00
{
2024-03-28 13:39:15 +02:00
logAi - > error ( " Invalid parentIndex while executing node " + node - > coord . toString ( ) ) ;
2021-05-16 14:39:38 +02:00
}
2021-05-15 20:56:31 +02:00
if ( vstd : : contains ( blockedIndexes , i ) )
{
2024-03-28 13:39:15 +02:00
blockedIndexes . insert ( node - > parentIndex ) ;
2019-12-15 15:47:21 +02:00
ai - > nullkiller - > lockHero ( hero , HeroLockedReason : : HERO_CHAIN ) ;
2021-05-15 20:56:31 +02:00
continue ;
}
2024-03-28 13:39:15 +02:00
logAi - > debug ( " Executing chain node %d. Moving hero %s to %s " , i , hero - > getNameTranslated ( ) , node - > coord . toString ( ) ) ;
2021-05-15 20:56:31 +02:00
try
{
2023-06-21 19:38:26 +02:00
if ( hero - > movementPointsRemaining ( ) > 0 )
2021-05-15 21:02:27 +02:00
{
2024-03-28 13:39:15 +02:00
ai - > nullkiller - > setActive ( hero , node - > coord ) ;
2021-05-15 20:56:31 +02:00
2024-03-28 13:39:15 +02:00
if ( node - > specialAction )
2021-05-15 21:02:57 +02:00
{
2024-03-28 13:39:15 +02:00
if ( node - > actionIsBlocked )
2021-05-16 13:07:54 +02:00
{
2019-12-15 15:47:21 +02:00
throw cannotFulfillGoalException ( " Path is nondeterministic. " ) ;
2021-05-16 13:07:54 +02:00
}
2019-12-15 15:47:21 +02:00
2024-03-31 17:39:00 +02:00
node - > specialAction - > execute ( ai , hero ) ;
2021-05-16 13:38:53 +02:00
2019-12-15 15:47:21 +02:00
if ( ! heroPtr . validAndSet ( ) )
2021-05-16 13:19:44 +02:00
{
2024-07-03 19:01:38 +02:00
logAi - > error ( " Hero %s was lost trying to execute special action. Exit hero chain. " , heroPtr . name ( ) ) ;
2021-05-16 13:19:44 +02:00
return ;
}
2021-05-15 21:02:57 +02:00
}
2024-05-19 09:04:45 +02:00
else if ( i > 0 & & ai - > nullkiller - > isObjectGraphAllowed ( ) )
2024-03-28 13:39:15 +02:00
{
auto chainMask = i < chainPath . nodes . size ( ) - 1 ? chainPath . nodes [ i + 1 ] . chainMask : node - > chainMask ;
for ( auto j = i - 1 ; j > = 0 ; j - - )
{
auto & nextNode = chainPath . nodes [ j ] ;
if ( nextNode . specialAction | | nextNode . chainMask ! = chainMask )
break ;
auto targetNode = cb - > getPathsInfo ( hero ) - > getPathInfo ( nextNode . coord ) ;
if ( ! targetNode - > reachable ( )
| | targetNode - > getCost ( ) > nextNode . cost )
break ;
i = j ;
node = & nextNode ;
if ( targetNode - > action = = EPathNodeAction : : BATTLE | | targetNode - > action = = EPathNodeAction : : TELEPORT_BATTLE )
break ;
}
}
2021-05-15 21:02:57 +02:00
2024-03-28 13:39:15 +02:00
if ( node - > turns = = 0 & & node - > coord ! = hero - > visitablePos ( ) )
2021-05-16 13:09:49 +02:00
{
2024-03-28 13:39:15 +02:00
auto targetNode = cb - > getPathsInfo ( hero ) - > getPathInfo ( node - > coord ) ;
2021-05-16 13:09:49 +02:00
2023-06-21 14:38:57 +02:00
if ( targetNode - > accessible = = EPathAccessibility : : NOT_SET
| | targetNode - > accessible = = EPathAccessibility : : BLOCKED
| | targetNode - > accessible = = EPathAccessibility : : FLYABLE
2021-05-16 13:19:44 +02:00
| | targetNode - > turns ! = 0 )
2021-05-16 13:09:49 +02:00
{
logAi - > error (
2022-09-14 16:23:13 +02:00
" Unable to complete chain. Expected hero %s to arrive to %s in 0 turns but he cannot do this " ,
2023-01-02 13:27:03 +02:00
hero - > getNameTranslated ( ) ,
2024-03-28 13:39:15 +02:00
node - > coord . toString ( ) ) ;
2021-05-16 13:09:49 +02:00
return ;
}
}
2024-07-20 17:36:07 +02:00
auto findWhirlpool = [ & ai ] ( const int3 & pos ) - > ObjectInstanceID
{
auto objs = ai - > myCb - > getVisitableObjs ( pos ) ;
auto whirlpool = std : : find_if ( objs . begin ( ) , objs . end ( ) , [ ] ( const CGObjectInstance * o ) - > bool
{
return o - > ID = = Obj : : WHIRLPOOL ;
} ) ;
return whirlpool ! = objs . end ( ) ? dynamic_cast < const CGWhirlpool * > ( * whirlpool ) - > id : ObjectInstanceID ( - 1 ) ;
} ;
auto sourceWhirlpool = findWhirlpool ( hero - > visitablePos ( ) ) ;
auto targetWhirlpool = findWhirlpool ( node - > coord ) ;
if ( i ! = chainPath . nodes . size ( ) - 1 & & sourceWhirlpool . hasValue ( ) & & sourceWhirlpool = = targetWhirlpool )
{
logAi - > trace ( " AI exited whirlpool at %s but expected at %s " , hero - > visitablePos ( ) . toString ( ) , node - > coord . toString ( ) ) ;
continue ;
}
2023-06-21 19:38:26 +02:00
if ( hero - > movementPointsRemaining ( ) )
2021-05-16 13:13:40 +02:00
{
2021-05-16 13:13:48 +02:00
try
2021-05-16 13:13:40 +02:00
{
2024-03-31 17:39:00 +02:00
if ( moveHeroToTile ( ai , hero , node - > coord ) )
2021-05-16 13:38:26 +02:00
{
continue ;
}
2021-05-16 13:13:48 +02:00
}
2022-12-07 18:05:47 +02:00
catch ( const cannotFulfillGoalException & )
2021-05-16 13:13:48 +02:00
{
2019-12-15 15:47:21 +02:00
if ( ! heroPtr . validAndSet ( ) )
2021-05-16 13:19:44 +02:00
{
2024-07-03 19:01:38 +02:00
logAi - > error ( " Hero %s was lost. Exit hero chain. " , heroPtr . name ( ) ) ;
2021-05-16 13:19:44 +02:00
return ;
}
2023-06-21 19:38:26 +02:00
if ( hero - > movementPointsRemaining ( ) > 0 )
2021-05-16 13:13:40 +02:00
{
2021-05-16 13:13:48 +02:00
CGPath path ;
2024-03-28 13:39:15 +02:00
bool isOk = cb - > getPathsInfo ( hero ) - > getPath ( path , node - > coord ) ;
2021-05-16 13:13:48 +02:00
if ( isOk & & path . nodes . back ( ) . turns > 0 )
{
2024-03-28 13:39:15 +02:00
logAi - > warn ( " Hero %s has %d mp which is not enough to continue his way towards %s. " , hero - > getNameTranslated ( ) , hero - > movementPointsRemaining ( ) , node - > coord . toString ( ) ) ;
2021-05-16 13:13:40 +02:00
2019-12-15 15:47:21 +02:00
ai - > nullkiller - > lockHero ( hero , HeroLockedReason : : HERO_CHAIN ) ;
2021-05-16 13:13:48 +02:00
return ;
}
2021-05-16 13:13:40 +02:00
}
2021-05-16 13:13:48 +02:00
throw ;
}
2021-05-16 13:13:40 +02:00
}
2021-05-15 21:02:27 +02:00
}
2021-05-15 20:57:27 +02:00
2024-03-28 13:39:15 +02:00
if ( node - > coord = = hero - > visitablePos ( ) )
2021-05-16 13:19:07 +02:00
continue ;
2024-03-28 13:39:15 +02:00
if ( node - > turns = = 0 )
2021-05-16 13:09:49 +02:00
{
logAi - > error (
2024-06-24 03:23:26 +02:00
" Unable to complete chain. Expected hero %s to arrive to %s but he is at %s " ,
2023-01-02 13:27:03 +02:00
hero - > getNameTranslated ( ) ,
2024-03-28 13:39:15 +02:00
node - > coord . toString ( ) ,
2021-05-16 13:09:49 +02:00
hero - > visitablePos ( ) . toString ( ) ) ;
return ;
}
2021-05-16 13:13:56 +02:00
2023-03-06 13:19:49 +02:00
// no exception means we were not able to reach the tile
2019-12-15 15:47:21 +02:00
ai - > nullkiller - > lockHero ( hero , HeroLockedReason : : HERO_CHAIN ) ;
2024-03-28 13:39:15 +02:00
blockedIndexes . insert ( node - > parentIndex ) ;
2021-05-15 20:56:31 +02:00
}
2022-12-07 18:05:47 +02:00
catch ( const goalFulfilledException & )
2021-05-15 20:56:31 +02:00
{
2019-12-15 15:47:21 +02:00
if ( ! heroPtr . validAndSet ( ) )
2021-05-15 20:56:31 +02:00
{
2024-07-03 19:01:38 +02:00
logAi - > debug ( " Hero %s was killed while attempting to reach %s " , heroPtr . name ( ) , node - > coord . toString ( ) ) ;
2021-05-15 20:56:31 +02:00
return ;
}
}
}
}
2021-05-16 13:38:26 +02:00
std : : string ExecuteHeroChain : : toString ( ) const
2021-05-15 20:56:31 +02:00
{
2024-07-18 12:37:18 +02:00
# if NKAI_TRACE_LEVEL >= 1
return " ExecuteHeroChain " + targetName + " by " + chainPath . toString ( ) ;
# else
2023-01-02 13:27:03 +02:00
return " ExecuteHeroChain " + targetName + " by " + chainPath . targetHero - > getNameTranslated ( ) ;
2024-07-18 12:37:18 +02:00
# endif
2021-05-16 13:38:26 +02:00
}
2024-03-31 17:39:00 +02:00
bool ExecuteHeroChain : : moveHeroToTile ( AIGateway * ai , const CGHeroInstance * hero , const int3 & tile )
2021-05-16 13:38:26 +02:00
{
2024-03-31 17:39:00 +02:00
if ( tile = = hero - > visitablePos ( ) & & ai - > myCb - > getVisitableObjs ( hero - > visitablePos ( ) ) . size ( ) < 2 )
2021-05-16 13:38:26 +02:00
{
2023-01-02 13:27:03 +02:00
logAi - > warn ( " Why do I want to move hero %s to tile %s? Already standing on that tile! " , hero - > getNameTranslated ( ) , tile . toString ( ) ) ;
2021-05-16 13:38:26 +02:00
return true ;
}
return ai - > moveHeroToTile ( tile , hero ) ;
2022-09-26 20:01:07 +02:00
}
}