2013-10-18 23:17:25 +03:00
/*
* Goals . 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
*
*/
2017-07-13 10:26:03 +02:00
# include "StdInc.h"
# include "Goals.h"
# include "VCAI.h"
# include "Fuzzy.h"
# include "../../lib/mapping/CMap.h" //for victory conditions
# include "../../lib/CPathfinder.h"
2013-10-18 23:17:25 +03:00
extern boost : : thread_specific_ptr < CCallback > cb ;
extern boost : : thread_specific_ptr < VCAI > ai ;
2013-12-20 12:43:12 +03:00
extern FuzzyHelper * fh ; //TODO: this logic should be moved inside VCAI
2013-10-18 23:17:25 +03:00
2013-10-19 08:52:30 +03:00
using namespace Goals ;
2013-10-18 23:17:25 +03:00
2013-11-23 21:16:25 +03:00
TSubgoal Goals : : sptr ( const AbstractGoal & tmp )
{
2015-12-29 04:43:33 +02:00
std : : shared_ptr < AbstractGoal > ptr ;
2013-11-23 21:16:25 +03:00
ptr . reset ( tmp . clone ( ) ) ;
return ptr ;
}
2013-11-15 20:30:16 +03:00
std : : string Goals : : AbstractGoal : : name ( ) const //TODO: virtualize
2013-10-18 23:17:25 +03:00
{
2013-12-23 23:46:01 +03:00
std : : string desc ;
2013-10-18 23:17:25 +03:00
switch ( goalType )
{
case INVALID :
return " INVALID " ;
case WIN :
return " WIN " ;
case DO_NOT_LOSE :
return " DO NOT LOOSE " ;
case CONQUER :
return " CONQUER " ;
case BUILD :
return " BUILD " ;
case EXPLORE :
2013-12-23 23:46:01 +03:00
desc = " EXPLORE " ;
break ;
2013-10-18 23:17:25 +03:00
case GATHER_ARMY :
2013-12-23 23:46:01 +03:00
desc = " GATHER ARMY " ;
break ;
2013-10-18 23:17:25 +03:00
case BOOST_HERO :
2013-12-23 23:46:01 +03:00
desc = " BOOST_HERO (unsupported) " ;
break ;
2013-10-18 23:17:25 +03:00
case RECRUIT_HERO :
return " RECRUIT HERO " ;
case BUILD_STRUCTURE :
return " BUILD STRUCTURE " ;
case COLLECT_RES :
2013-12-23 23:46:01 +03:00
desc = " COLLECT RESOURCE " ;
break ;
2013-10-18 23:17:25 +03:00
case GATHER_TROOPS :
2013-12-23 23:46:01 +03:00
desc = " GATHER TROOPS " ;
break ;
2013-10-18 23:17:25 +03:00
case GET_OBJ :
2013-12-26 12:53:37 +03:00
{
auto obj = cb - > getObjInstance ( ObjectInstanceID ( objid ) ) ;
if ( obj )
2014-06-24 20:39:36 +03:00
desc = " GET OBJ " + obj - > getObjectName ( ) ;
2013-12-26 12:53:37 +03:00
}
2013-10-18 23:17:25 +03:00
case FIND_OBJ :
2013-12-23 23:46:01 +03:00
desc = " FIND OBJ " + boost : : lexical_cast < std : : string > ( objid ) ;
break ;
2013-10-18 23:17:25 +03:00
case VISIT_HERO :
2013-12-26 12:53:37 +03:00
{
auto obj = cb - > getObjInstance ( ObjectInstanceID ( objid ) ) ;
if ( obj )
2014-06-24 20:39:36 +03:00
desc = " VISIT HERO " + obj - > getObjectName ( ) ;
2013-12-26 12:53:37 +03:00
}
2013-12-23 23:46:01 +03:00
break ;
2013-10-18 23:17:25 +03:00
case GET_ART_TYPE :
2013-12-23 23:46:01 +03:00
desc = " GET ARTIFACT OF TYPE " + VLC - > arth - > artifacts [ aid ] - > Name ( ) ;
break ;
2013-10-18 23:17:25 +03:00
case ISSUE_COMMAND :
return " ISSUE COMMAND (unsupported) " ;
case VISIT_TILE :
2017-08-11 19:03:05 +02:00
desc = " VISIT TILE " + tile . toString ( ) ;
2013-12-23 23:46:01 +03:00
break ;
2013-10-18 23:17:25 +03:00
case CLEAR_WAY_TO :
2017-08-11 19:03:05 +02:00
desc = " CLEAR WAY TO " + tile . toString ( ) ;
2013-12-23 23:46:01 +03:00
break ;
2013-10-18 23:17:25 +03:00
case DIG_AT_TILE :
2017-08-11 19:03:05 +02:00
desc = " DIG AT TILE " + tile . toString ( ) ;
2013-12-23 23:46:01 +03:00
break ;
2013-10-18 23:17:25 +03:00
default :
return boost : : lexical_cast < std : : string > ( goalType ) ;
}
2013-12-24 00:22:42 +03:00
if ( hero . get ( true ) ) //FIXME: used to crash when we lost hero and failed goal
2013-12-23 23:46:01 +03:00
desc + = " ( " + hero - > name + " ) " ;
return desc ;
2013-10-18 23:17:25 +03:00
}
2014-02-19 19:23:47 +03:00
//TODO: virtualize if code gets complex?
bool Goals : : AbstractGoal : : operator = = ( AbstractGoal & g )
{
if ( g . goalType ! = goalType )
return false ;
if ( g . isElementar ! = isElementar ) //elementar goals fulfill long term non-elementar goals (VisitTile)
return false ;
switch ( goalType )
{
//no parameters
case INVALID :
case WIN :
case DO_NOT_LOSE :
case RECRUIT_HERO : //recruit any hero, as yet
return true ;
break ;
//assigned to hero, no parameters
case CONQUER :
case EXPLORE :
case GATHER_ARMY : //actual value is indifferent
case BOOST_HERO :
return g . hero . h = = hero . h ; //how comes HeroPtrs are equal for different heroes?
break ;
//assigned hero and tile
case VISIT_TILE :
case CLEAR_WAY_TO :
return ( g . hero . h = = hero . h & & g . tile = = tile ) ;
break ;
//assigned hero and object
case GET_OBJ :
case FIND_OBJ : //TODO: use subtype?
case VISIT_HERO :
case GET_ART_TYPE :
case DIG_AT_TILE :
return ( g . hero . h = = hero . h & & g . objid = = objid ) ;
break ;
//no check atm
case COLLECT_RES :
case GATHER_TROOPS :
case ISSUE_COMMAND :
case BUILD : //TODO: should be decomposed to build specific structures
case BUILD_STRUCTURE :
default :
return false ;
}
}
2013-11-30 10:52:08 +03:00
//TODO: find out why the following are not generated automatically on MVS?
2016-01-19 19:25:15 +02:00
namespace Goals
{
2013-11-30 10:52:08 +03:00
template < >
void CGoal < Win > : : accept ( VCAI * ai )
{
ai - > tryRealize ( static_cast < Win & > ( * this ) ) ;
}
template < >
void CGoal < Build > : : accept ( VCAI * ai )
{
ai - > tryRealize ( static_cast < Build & > ( * this ) ) ;
}
2013-12-19 23:21:21 +03:00
template < >
float CGoal < Win > : : accept ( FuzzyHelper * f )
{
return f - > evaluate ( static_cast < Win & > ( * this ) ) ;
}
template < >
float CGoal < Build > : : accept ( FuzzyHelper * f )
{
return f - > evaluate ( static_cast < Build & > ( * this ) ) ;
}
2013-11-30 10:52:08 +03:00
}
2013-11-23 21:16:25 +03:00
//TSubgoal AbstractGoal::whatToDoToAchieve()
//{
2016-08-13 16:44:37 +02:00
// logAi->debug("Decomposing goal of type %s",name());
2016-03-12 03:41:27 +02:00
// return sptr (Goals::Explore());
2013-11-23 21:16:25 +03:00
//}
2013-10-18 23:17:25 +03:00
TSubgoal Win : : whatToDoToAchieve ( )
{
2013-12-29 14:27:38 +03:00
auto toBool = [ = ] ( const EventCondition & )
{
// TODO: proper implementation
// Right now even already fulfilled goals will be included into generated list
// Proper check should test if event condition is already fulfilled
// Easiest way to do this is to call CGameState::checkForVictory but this function should not be
// used on client side or in AI code
return false ;
} ;
std : : vector < EventCondition > goals ;
2013-10-18 23:17:25 +03:00
2013-12-29 14:27:38 +03:00
for ( const TriggeredEvent & event : cb - > getMapHeader ( ) - > triggeredEvents )
2013-10-18 23:17:25 +03:00
{
2013-12-29 14:27:38 +03:00
//TODO: try to eliminate human player(s) using loss conditions that have isHuman element
if ( event . effect . type = = EventEffect : : VICTORY )
{
boost : : range : : copy ( event . trigger . getFulfillmentCandidates ( toBool ) , std : : back_inserter ( goals ) ) ;
}
2013-10-18 23:17:25 +03:00
}
2013-12-29 14:27:38 +03:00
//TODO: instead of returning first encountered goal AI should generate list of possible subgoals
for ( const EventCondition & goal : goals )
2013-10-18 23:17:25 +03:00
{
2013-12-29 14:27:38 +03:00
switch ( goal . condition )
2013-10-18 23:17:25 +03:00
{
2013-12-29 14:27:38 +03:00
case EventCondition : : HAVE_ARTIFACT :
return sptr ( Goals : : GetArtOfType ( goal . objectType ) ) ;
case EventCondition : : DESTROY :
2013-10-18 23:17:25 +03:00
{
2013-12-29 14:27:38 +03:00
if ( goal . object )
2013-10-18 23:17:25 +03:00
{
2015-04-13 18:45:43 +02:00
auto obj = cb - > getObj ( goal . object - > id ) ;
if ( obj )
if ( obj - > getOwner ( ) = = ai - > playerID ) //we can't capture our own object
return sptr ( Goals : : Conquer ( ) ) ;
2013-12-29 14:27:38 +03:00
return sptr ( Goals : : GetObj ( goal . object - > id . getNum ( ) ) ) ;
2013-10-18 23:17:25 +03:00
}
else
{
2013-12-29 14:27:38 +03:00
// TODO: destroy all objects of type goal.objectType
// This situation represents "kill all creatures" condition from H3
break ;
}
}
case EventCondition : : HAVE_BUILDING :
{
// TODO build other buildings apart from Grail
// goal.objectType = buidingID to build
// goal.object = optional, town in which building should be built
// Represents "Improve town" condition from H3 (but unlike H3 it consists from 2 separate conditions)
if ( goal . objectType = = BuildingID : : GRAIL )
{
if ( auto h = ai - > getHeroWithGrail ( ) )
2013-10-18 23:17:25 +03:00
{
2013-12-29 14:27:38 +03:00
//hero is in a town that can host Grail
if ( h - > visitedTown & & ! vstd : : contains ( h - > visitedTown - > forbiddenBuildings , BuildingID : : GRAIL ) )
{
const CGTownInstance * t = h - > visitedTown ;
return sptr ( Goals : : BuildThis ( BuildingID : : GRAIL , t ) ) ;
}
else
{
auto towns = cb - > getTownsInfo ( ) ;
towns . erase ( boost : : remove_if ( towns ,
[ ] ( const CGTownInstance * t ) - > bool
{
return vstd : : contains ( t - > forbiddenBuildings , BuildingID : : GRAIL ) ;
} ) ,
towns . end ( ) ) ;
2014-09-21 16:42:08 +03:00
boost : : sort ( towns , CDistanceSorter ( h . get ( ) ) ) ;
2013-12-29 14:27:38 +03:00
if ( towns . size ( ) )
{
return sptr ( Goals : : VisitTile ( towns . front ( ) - > visitablePos ( ) ) . sethero ( h ) ) ;
}
}
2013-10-18 23:17:25 +03:00
}
2013-12-29 14:27:38 +03:00
double ratio = 0 ;
// maybe make this check a bit more complex? For example:
2014-03-23 15:59:03 +03:00
// 0.75 -> dig randomly within 3 tiles radius
2013-12-29 14:27:38 +03:00
// 0.85 -> radius now 2 tiles
// 0.95 -> 1 tile radius, position is fully known
// AFAIK H3 AI does something like this
2016-01-20 09:44:13 +02:00
int3 grailPos = cb - > getGrailPos ( & ratio ) ;
2013-12-29 14:27:38 +03:00
if ( ratio > 0.99 )
{
return sptr ( Goals : : DigAtTile ( grailPos ) ) ;
} //TODO: use FIND_OBJ
else if ( const CGObjectInstance * obj = ai - > getUnvisitedObj ( objWithID < Obj : : OBELISK > ) ) //there are unvisited Obelisks
return sptr ( Goals : : GetObj ( obj - > id . getNum ( ) ) ) ;
else
return sptr ( Goals : : Explore ( ) ) ;
2013-10-18 23:17:25 +03:00
}
2013-12-29 14:27:38 +03:00
break ;
2013-10-18 23:17:25 +03:00
}
2013-12-29 14:27:38 +03:00
case EventCondition : : CONTROL :
2013-10-18 23:17:25 +03:00
{
2013-12-29 14:27:38 +03:00
if ( goal . object )
{
return sptr ( Goals : : GetObj ( goal . object - > id . getNum ( ) ) ) ;
}
else
{
//TODO: control all objects of type "goal.objectType"
// Represents H3 condition "Flag all mines"
break ;
}
}
case EventCondition : : HAVE_RESOURCES :
//TODO mines? piles? marketplace?
//save?
return sptr ( Goals : : CollectRes ( static_cast < Res : : ERes > ( goal . objectType ) , goal . value ) ) ;
case EventCondition : : HAVE_CREATURES :
return sptr ( Goals : : GatherTroops ( goal . objectType , goal . value ) ) ;
case EventCondition : : TRANSPORT :
2013-10-18 23:17:25 +03:00
{
2013-12-29 14:27:38 +03:00
//TODO. merge with bring Grail to town? So AI will first dig grail, then transport it using this goal and builds it
// Represents "transport artifact" condition:
// goal.objectType = type of artifact
// goal.object = destination-town where artifact should be transported
break ;
2013-10-18 23:17:25 +03:00
}
2013-12-29 14:27:38 +03:00
case EventCondition : : STANDARD_WIN :
return sptr ( Goals : : Conquer ( ) ) ;
// Conditions that likely don't need any implementation
case EventCondition : : DAYS_PASSED :
break ; // goal.value = number of days for condition to trigger
case EventCondition : : DAYS_WITHOUT_TOWN :
break ; // goal.value = number of days to trigger this
case EventCondition : : IS_HUMAN :
break ; // Should be only used in calculation of candidates (see toBool lambda)
2014-02-09 15:10:02 +03:00
case EventCondition : : CONST_VALUE :
break ;
2016-11-13 12:38:42 +02:00
case EventCondition : : HAVE_0 :
case EventCondition : : HAVE_BUILDING_0 :
case EventCondition : : DESTROY_0 :
//TODO: support new condition format
return sptr ( Goals : : Conquer ( ) ) ;
2013-12-29 14:27:38 +03:00
default :
assert ( 0 ) ;
2013-10-18 23:17:25 +03:00
}
}
2013-11-15 20:30:16 +03:00
return sptr ( Goals : : Invalid ( ) ) ;
2013-10-18 23:17:25 +03:00
}
TSubgoal FindObj : : whatToDoToAchieve ( )
{
const CGObjectInstance * o = nullptr ;
if ( resID > - 1 ) //specified
{
for ( const CGObjectInstance * obj : ai - > visitableObjs )
{
if ( obj - > ID = = objid & & obj - > subID = = resID )
{
o = obj ;
break ; //TODO: consider multiple objects and choose best
}
}
}
else
{
for ( const CGObjectInstance * obj : ai - > visitableObjs )
{
if ( obj - > ID = = objid )
{
o = obj ;
break ; //TODO: consider multiple objects and choose best
}
}
}
2014-09-21 17:03:20 +03:00
if ( o & & ai - > isAccessible ( o - > pos ) ) //we don't use isAccessibleForHero as we don't know which hero it is
2013-11-15 20:30:16 +03:00
return sptr ( Goals : : GetObj ( o - > id . getNum ( ) ) ) ;
2013-10-18 23:17:25 +03:00
else
2013-11-15 20:30:16 +03:00
return sptr ( Goals : : Explore ( ) ) ;
2013-10-18 23:17:25 +03:00
}
2013-12-20 12:43:12 +03:00
2013-11-24 11:21:51 +03:00
std : : string GetObj : : completeMessage ( ) const
{
2016-01-19 19:25:15 +02:00
return " hero " + hero . get ( ) - > name + " captured Object ID = " + boost : : lexical_cast < std : : string > ( objid ) ;
2013-11-24 11:21:51 +03:00
}
2013-10-18 23:17:25 +03:00
TSubgoal GetObj : : whatToDoToAchieve ( )
{
const CGObjectInstance * obj = cb - > getObj ( ObjectInstanceID ( objid ) ) ;
if ( ! obj )
2013-11-15 20:30:16 +03:00
return sptr ( Goals : : Explore ( ) ) ;
2014-12-21 12:33:53 +02:00
if ( obj - > tempOwner = = ai - > playerID ) //we can't capture our own object -> move to Win codition
throw cannotFulfillGoalException ( " Cannot capture my own object " + obj - > getObjectName ( ) ) ;
2013-10-18 23:17:25 +03:00
int3 pos = obj - > visitablePos ( ) ;
2013-12-25 01:01:16 +03:00
if ( hero )
{
if ( ai - > isAccessibleForHero ( pos , hero ) )
return sptr ( Goals : : VisitTile ( pos ) . sethero ( hero ) ) ;
}
else
{
2014-12-21 12:33:53 +02:00
for ( auto h : cb - > getHeroesInfo ( ) )
{
if ( ai - > isAccessibleForHero ( pos , h ) )
return sptr ( Goals : : VisitTile ( pos ) . sethero ( h ) ) ; //we must visit object with same hero, if any
}
2013-12-25 01:01:16 +03:00
}
return sptr ( Goals : : ClearWayTo ( pos ) . sethero ( hero ) ) ;
2013-10-18 23:17:25 +03:00
}
2013-12-25 01:01:16 +03:00
bool GetObj : : fulfillsMe ( TSubgoal goal )
2013-11-24 22:15:08 +03:00
{
2013-12-25 01:12:12 +03:00
if ( goal - > goalType = = Goals : : VISIT_TILE )
{
auto obj = cb - > getObj ( ObjectInstanceID ( objid ) ) ;
if ( obj & & obj - > visitablePos ( ) = = goal - > tile ) //object could be removed
return true ;
}
2013-12-25 16:38:20 +03:00
return false ;
2013-11-24 22:15:08 +03:00
}
2013-11-24 11:21:51 +03:00
std : : string VisitHero : : completeMessage ( ) const
{
2016-01-19 19:25:15 +02:00
return " hero " + hero . get ( ) - > name + " visited hero " + boost : : lexical_cast < std : : string > ( objid ) ;
2013-11-24 11:21:51 +03:00
}
2013-10-18 23:17:25 +03:00
TSubgoal VisitHero : : whatToDoToAchieve ( )
{
const CGObjectInstance * obj = cb - > getObj ( ObjectInstanceID ( objid ) ) ;
if ( ! obj )
2013-11-15 20:30:16 +03:00
return sptr ( Goals : : Explore ( ) ) ;
2013-10-18 23:17:25 +03:00
int3 pos = obj - > visitablePos ( ) ;
if ( hero & & ai - > isAccessibleForHero ( pos , hero , true ) & & isSafeToVisit ( hero , pos ) ) //enemy heroes can get reinforcements
2013-11-15 20:30:16 +03:00
{
2013-12-21 20:34:59 +03:00
if ( hero - > pos = = pos )
2016-08-13 16:44:37 +02:00
logAi - > error ( " Hero %s tries to visit himself. " , hero . name ) ;
2013-12-21 20:34:59 +03:00
else
{
2013-12-27 16:20:40 +03:00
//can't use VISIT_TILE here as tile appears blocked by target hero
//FIXME: elementar goal should not be abstract
return sptr ( Goals : : VisitHero ( objid ) . sethero ( hero ) . settile ( pos ) . setisElementar ( true ) ) ;
2013-12-21 20:34:59 +03:00
}
2013-11-15 20:30:16 +03:00
}
return sptr ( Goals : : Invalid ( ) ) ;
2013-10-18 23:17:25 +03:00
}
2013-12-25 01:01:16 +03:00
bool VisitHero : : fulfillsMe ( TSubgoal goal )
2013-11-24 22:15:08 +03:00
{
2016-01-19 19:25:15 +02:00
if ( goal - > goalType ! = Goals : : VISIT_TILE )
{
2013-11-24 22:15:08 +03:00
return false ;
2016-01-19 19:25:15 +02:00
}
auto obj = cb - > getObj ( ObjectInstanceID ( objid ) ) ;
if ( ! obj )
{
2017-08-11 19:03:05 +02:00
logAi - > error ( " Hero %s: VisitHero::fulfillsMe at %s: object %d not found " , hero . name , goal - > tile . toString ( ) , objid ) ;
2016-01-19 19:25:15 +02:00
return false ;
}
return obj - > visitablePos ( ) = = goal - > tile ;
2013-11-24 22:15:08 +03:00
}
2013-10-18 23:17:25 +03:00
TSubgoal GetArtOfType : : whatToDoToAchieve ( )
{
TSubgoal alternativeWay = CGoal : : lookForArtSmart ( aid ) ; //TODO: use
2013-11-10 00:29:46 +03:00
if ( alternativeWay - > invalid ( ) )
2013-11-15 20:30:16 +03:00
return sptr ( Goals : : FindObj ( Obj : : ARTIFACT , aid ) ) ;
return sptr ( Goals : : Invalid ( ) ) ;
2013-10-18 23:17:25 +03:00
}
TSubgoal ClearWayTo : : whatToDoToAchieve ( )
{
2013-12-23 23:46:01 +03:00
assert ( cb - > isInTheMap ( tile ) ) ; //set tile
2013-10-18 23:17:25 +03:00
if ( ! cb - > isVisible ( tile ) )
{
2016-08-13 16:44:37 +02:00
logAi - > error ( " Clear way should be used with visible tiles! " ) ;
2013-11-15 20:30:16 +03:00
return sptr ( Goals : : Explore ( ) ) ;
2013-10-18 23:17:25 +03:00
}
2016-01-19 19:25:15 +02:00
return ( fh - > chooseSolution ( getAllPossibleSubgoals ( ) ) ) ;
2013-12-23 23:46:01 +03:00
}
2013-10-18 23:17:25 +03:00
2013-12-23 23:46:01 +03:00
TGoalVec ClearWayTo : : getAllPossibleSubgoals ( )
{
TGoalVec ret ;
2014-02-20 23:18:49 +03:00
std : : vector < const CGHeroInstance * > heroes ;
if ( hero )
heroes . push_back ( hero . h ) ;
else
2013-12-23 23:46:01 +03:00
{
2014-02-20 23:18:49 +03:00
heroes = cb - > getHeroesInfo ( ) ;
}
2013-12-26 12:53:37 +03:00
2014-02-20 23:18:49 +03:00
for ( auto h : heroes )
{
//TODO: handle clearing way to allied heroes that are blocked
//if ((hero && hero->visitablePos() == tile && hero == *h) || //we can't free the way ourselves
// h->visitablePos() == tile) //we are already on that tile! what does it mean?
// continue;
2013-10-18 23:17:25 +03:00
2013-12-23 23:46:01 +03:00
//if our hero is trapped, make sure we request clearing the way from OUR perspective
2014-02-20 23:18:49 +03:00
2015-12-04 00:10:51 +02:00
auto sm = ai - > getCachedSectorMap ( h ) ;
2014-02-20 23:18:49 +03:00
2015-12-04 00:10:51 +02:00
int3 tileToHit = sm - > firstTileToGet ( h , tile ) ;
2014-02-15 19:38:51 +03:00
if ( ! tileToHit . valid ( ) )
continue ;
2013-10-18 23:17:25 +03:00
2013-12-23 23:46:01 +03:00
if ( isBlockedBorderGate ( tileToHit ) )
{ //FIXME: this way we'll not visit gate and activate quest :?
ret . push_back ( sptr ( Goals : : FindObj ( Obj : : KEYMASTER , cb - > getTile ( tileToHit ) - > visitableObjects . back ( ) - > subID ) ) ) ;
}
2013-10-18 23:17:25 +03:00
2014-02-17 20:28:39 +03:00
auto topObj = cb - > getTopObj ( tileToHit ) ;
2014-02-20 23:18:49 +03:00
if ( topObj )
2013-12-23 23:46:01 +03:00
{
2014-02-20 23:18:49 +03:00
if ( vstd : : contains ( ai - > reservedObjs , topObj ) & & ! vstd : : contains ( ai - > reservedHeroesMap [ h ] , topObj ) )
2014-02-23 19:55:42 +03:00
{
throw goalFulfilledException ( sptr ( Goals : : ClearWayTo ( tile , h ) ) ) ;
2014-02-20 23:18:49 +03:00
continue ; //do not capure object reserved by other hero
2014-02-23 19:55:42 +03:00
}
2014-02-20 23:18:49 +03:00
2013-12-25 01:01:16 +03:00
if ( topObj - > ID = = Obj : : HERO & & cb - > getPlayerRelations ( h - > tempOwner , topObj - > tempOwner ) ! = PlayerRelations : : ENEMIES )
2014-02-20 23:18:49 +03:00
if ( topObj ! = hero . get ( true ) ) //the hero we want to free
2016-08-13 16:44:37 +02:00
logAi - > error ( " %s stands in the way of %s " , topObj - > getObjectName ( ) , h - > getObjectName ( ) ) ;
2013-12-25 01:01:16 +03:00
if ( topObj - > ID = = Obj : : QUEST_GUARD | | topObj - > ID = = Obj : : BORDERGUARD )
{
if ( shouldVisit ( h , topObj ) )
{
//do NOT use VISIT_TILE, as tile with quets guard can't be visited
ret . push_back ( sptr ( Goals : : GetObj ( topObj - > id . getNum ( ) ) . sethero ( h ) ) ) ;
2014-02-20 23:18:49 +03:00
continue ; //do not try to visit tile or gather army
2013-12-25 01:01:16 +03:00
}
else
{
//TODO: we should be able to return apriopriate quest here (VCAI::striveToQuest)
2017-08-11 19:03:05 +02:00
logAi - > debug ( " Quest guard blocks the way to %s " , tile . toString ( ) ) ;
2015-10-25 09:39:03 +02:00
continue ; //do not access quets guard if we can't complete the quest
2013-12-25 01:01:16 +03:00
}
}
2013-12-23 23:46:01 +03:00
}
2014-02-20 23:18:49 +03:00
if ( isSafeToVisit ( h , tileToHit ) ) //this makes sense only if tile is guarded, but there i no quest object
{
2013-12-23 23:46:01 +03:00
ret . push_back ( sptr ( Goals : : VisitTile ( tileToHit ) . sethero ( h ) ) ) ;
2014-02-20 23:18:49 +03:00
}
else
{
ret . push_back ( sptr ( Goals : : GatherArmy ( evaluateDanger ( tileToHit , h ) * SAFE_ATTACK_CONSTANT ) .
sethero ( h ) . setisAbstract ( true ) ) ) ;
}
2013-12-23 23:46:01 +03:00
}
if ( ai - > canRecruitAnyHero ( ) )
ret . push_back ( sptr ( Goals : : RecruitHero ( ) ) ) ;
2013-10-18 23:17:25 +03:00
2013-12-23 23:46:01 +03:00
if ( ret . empty ( ) )
2013-12-25 01:01:16 +03:00
{
2017-08-11 19:03:05 +02:00
logAi - > warn ( " There is no known way to clear the way to tile %s " , tile . toString ( ) ) ;
2014-02-23 19:55:42 +03:00
throw goalFulfilledException ( sptr ( Goals : : ClearWayTo ( tile ) ) ) ; //make sure asigned hero gets unlocked
2013-12-25 01:01:16 +03:00
}
2013-10-18 23:17:25 +03:00
2013-12-23 23:46:01 +03:00
return ret ;
2013-10-18 23:17:25 +03:00
}
2013-11-24 11:21:51 +03:00
std : : string Explore : : completeMessage ( ) const
{
return " Hero " + hero . get ( ) - > name + " completed exploration " ;
2014-01-16 23:24:06 +03:00
}
2013-11-24 11:21:51 +03:00
2013-10-18 23:17:25 +03:00
TSubgoal Explore : : whatToDoToAchieve ( )
{
2013-12-20 19:49:51 +03:00
auto ret = fh - > chooseSolution ( getAllPossibleSubgoals ( ) ) ;
if ( hero ) //use best step for this hero
return ret ;
else
{
if ( ret - > hero . get ( true ) )
return sptr ( sethero ( ret - > hero . h ) . setisAbstract ( true ) ) ; //choose this hero and then continue with him
else
return ret ; //other solutions, like buying hero from tavern
}
2014-01-16 23:24:06 +03:00
}
2013-12-20 19:49:51 +03:00
TGoalVec Explore : : getAllPossibleSubgoals ( )
{
TGoalVec ret ;
2013-12-23 23:46:01 +03:00
std : : vector < const CGHeroInstance * > heroes ;
2014-02-19 19:23:47 +03:00
2013-12-20 19:49:51 +03:00
if ( hero )
2013-12-23 23:46:01 +03:00
heroes . push_back ( hero . h ) ;
2013-12-20 19:49:51 +03:00
else
{
2013-12-23 23:46:01 +03:00
//heroes = ai->getUnblockedHeroes();
heroes = cb - > getHeroesInfo ( ) ;
2015-12-29 01:14:08 +02:00
vstd : : erase_if ( heroes , [ ] ( const HeroPtr h )
2013-12-20 19:49:51 +03:00
{
2014-02-19 19:23:47 +03:00
if ( ai - > getGoal ( h ) - > goalType = = Goals : : EXPLORE ) //do not reassign hero who is already explorer
return true ;
2014-02-17 10:36:03 +03:00
2014-03-23 19:00:43 +03:00
if ( ! ai - > isAbleToExplore ( h ) )
return true ;
2013-12-21 00:10:43 +03:00
return ! h - > movement ; //saves time, immobile heroes are useless anyway
2013-12-20 19:49:51 +03:00
} ) ;
}
2013-12-21 20:34:59 +03:00
//try to use buildings that uncover map
std : : vector < const CGObjectInstance * > objs ;
for ( auto obj : ai - > visitableObjs )
2013-10-18 23:17:25 +03:00
{
2013-12-21 20:34:59 +03:00
if ( ! vstd : : contains ( ai - > alreadyVisited , obj ) )
2013-10-18 23:17:25 +03:00
{
2013-12-21 20:34:59 +03:00
switch ( obj - > ID . num )
{
2015-03-09 00:25:52 +02:00
case Obj : : REDWOOD_OBSERVATORY :
case Obj : : PILLAR_OF_FIRE :
case Obj : : CARTOGRAPHER :
objs . push_back ( obj ) ;
break ;
case Obj : : MONOLITH_ONE_WAY_ENTRANCE :
case Obj : : MONOLITH_TWO_WAY :
case Obj : : SUBTERRANEAN_GATE :
auto tObj = dynamic_cast < const CGTeleport * > ( obj ) ;
assert ( ai - > knownTeleportChannels . find ( tObj - > channel ) ! = ai - > knownTeleportChannels . end ( ) ) ;
if ( TeleportChannel : : IMPASSABLE ! = ai - > knownTeleportChannels [ tObj - > channel ] - > passability )
2013-12-21 20:34:59 +03:00
objs . push_back ( obj ) ;
2015-03-09 00:25:52 +02:00
break ;
2015-03-08 16:49:14 +02:00
}
}
else
{
switch ( obj - > ID . num )
{
2015-03-09 00:25:52 +02:00
case Obj : : MONOLITH_TWO_WAY :
case Obj : : SUBTERRANEAN_GATE :
auto tObj = dynamic_cast < const CGTeleport * > ( obj ) ;
if ( TeleportChannel : : IMPASSABLE = = ai - > knownTeleportChannels [ tObj - > channel ] - > passability )
break ;
for ( auto exit : ai - > knownTeleportChannels [ tObj - > channel ] - > exits )
2015-03-08 16:49:14 +02:00
{
2015-03-09 00:25:52 +02:00
if ( ! cb - > getObj ( exit ) )
{ // Always attempt to visit two-way teleports if one of channel exits is not visible
objs . push_back ( obj ) ;
2015-03-08 16:49:14 +02:00
break ;
}
}
2015-03-09 00:25:52 +02:00
break ;
2013-12-21 20:34:59 +03:00
}
2013-10-18 23:17:25 +03:00
}
2013-12-21 20:34:59 +03:00
}
2015-03-30 15:32:23 +02:00
2013-12-20 19:49:51 +03:00
for ( auto h : heroes )
2013-10-18 23:17:25 +03:00
{
2015-12-04 00:10:51 +02:00
auto sm = ai - > getCachedSectorMap ( h ) ;
2014-02-20 23:18:49 +03:00
2013-12-20 19:49:51 +03:00
for ( auto obj : objs ) //double loop, performance risk?
2013-10-18 23:17:25 +03:00
{
2015-12-04 00:10:51 +02:00
auto t = sm - > firstTileToGet ( h , obj - > visitablePos ( ) ) ; //we assume that no more than one tile on the way is guarded
2015-04-10 08:50:21 +02:00
if ( ai - > isTileNotReserved ( h , t ) )
2014-02-20 23:18:49 +03:00
ret . push_back ( sptr ( Goals : : ClearWayTo ( obj - > visitablePos ( ) , h ) . setisAbstract ( true ) ) ) ;
2013-10-18 23:17:25 +03:00
}
2013-12-20 19:49:51 +03:00
int3 t = whereToExplore ( h ) ;
2014-02-15 19:38:51 +03:00
if ( t . valid ( ) )
2014-02-16 00:32:49 +03:00
{
2013-12-21 20:34:59 +03:00
ret . push_back ( sptr ( Goals : : VisitTile ( t ) . sethero ( h ) ) ) ;
2014-02-16 00:32:49 +03:00
}
2014-03-23 19:00:43 +03:00
else
2014-02-17 10:36:03 +03:00
{
2014-03-23 19:00:43 +03:00
ai - > markHeroUnableToExplore ( h ) ; //there is no freely accessible tile, do not poll this hero anymore
//possible issues when gathering army to break
if ( hero . h = = h | | ( ! hero & & h = = ai - > primaryHero ( ) . h ) ) //check this only ONCE, high cost
{
t = ai - > explorationDesperate ( h ) ;
if ( t . valid ( ) ) //don't waste time if we are completely blocked
ret . push_back ( sptr ( Goals : : ClearWayTo ( t , h ) . setisAbstract ( true ) ) ) ;
}
2014-02-17 10:36:03 +03:00
}
2013-10-18 23:17:25 +03:00
}
2013-12-23 23:46:01 +03:00
//we either don't have hero yet or none of heroes can explore
if ( ( ! hero | | ret . empty ( ) ) & & ai - > canRecruitAnyHero ( ) )
2013-12-20 19:49:51 +03:00
ret . push_back ( sptr ( Goals : : RecruitHero ( ) ) ) ;
2013-10-18 23:17:25 +03:00
2013-12-20 19:49:51 +03:00
if ( ret . empty ( ) )
2013-12-26 12:53:37 +03:00
{
2014-02-07 11:23:31 +03:00
throw goalFulfilledException ( sptr ( Goals : : Explore ( ) . sethero ( hero ) ) ) ;
2013-12-26 12:53:37 +03:00
}
//throw cannotFulfillGoalException("Cannot explore - no possible ways found!");
2013-10-18 23:17:25 +03:00
2013-12-20 19:49:51 +03:00
return ret ;
2014-01-16 23:24:06 +03:00
}
2013-10-18 23:17:25 +03:00
2013-12-26 12:53:37 +03:00
bool Explore : : fulfillsMe ( TSubgoal goal )
{
if ( goal - > goalType = = Goals : : EXPLORE )
{
if ( goal - > hero )
return hero = = goal - > hero ;
else
return true ; //cancel ALL exploration
}
return false ;
}
2013-10-18 23:17:25 +03:00
TSubgoal RecruitHero : : whatToDoToAchieve ( )
{
const CGTownInstance * t = ai - > findTownWithTavern ( ) ;
if ( ! t )
2013-11-15 20:30:16 +03:00
return sptr ( Goals : : BuildThis ( BuildingID : : TAVERN ) ) ;
2013-10-18 23:17:25 +03:00
2015-12-06 23:13:58 +02:00
if ( cb - > getResourceAmount ( Res : : GOLD ) < GameConstants : : HERO_GOLD_COST )
return sptr ( Goals : : CollectRes ( Res : : GOLD , GameConstants : : HERO_GOLD_COST ) ) ;
2013-10-18 23:17:25 +03:00
2013-11-15 20:30:16 +03:00
return iAmElementar ( ) ;
2013-10-18 23:17:25 +03:00
}
2013-11-24 11:21:51 +03:00
std : : string VisitTile : : completeMessage ( ) const
{
2017-08-11 19:03:05 +02:00
return " Hero " + hero . get ( ) - > name + " visited tile " + tile . toString ( ) ;
2013-11-24 11:21:51 +03:00
}
2013-10-18 23:17:25 +03:00
TSubgoal VisitTile : : whatToDoToAchieve ( )
{
2013-12-20 16:07:58 +03:00
auto ret = fh - > chooseSolution ( getAllPossibleSubgoals ( ) ) ;
2013-10-18 23:17:25 +03:00
2017-07-15 00:15:08 +02:00
if ( ret - > hero )
2013-10-18 23:17:25 +03:00
{
2017-07-15 00:15:08 +02:00
if ( isSafeToVisit ( ret - > hero , tile ) & & ai - > isAccessibleForHero ( tile , ret - > hero ) )
2013-10-18 23:17:25 +03:00
{
2017-07-15 00:15:08 +02:00
if ( cb - > getTile ( tile ) - > topVisitableId ( ) . num = = Obj : : TOWN ) //if target is town, fuzzy system will use additional "estimatedReward" variable to increase priority a bit
ret - > objid = Obj : : TOWN ; //TODO: move to getObj eventually and add appropiate logic there
2013-12-20 12:43:12 +03:00
ret - > setisElementar ( true ) ;
return ret ;
2013-10-18 23:17:25 +03:00
}
else
{
2013-12-23 23:46:01 +03:00
return sptr ( Goals : : GatherArmy ( evaluateDanger ( tile , * ret - > hero ) * SAFE_ATTACK_CONSTANT )
. sethero ( ret - > hero ) . setisAbstract ( true ) ) ;
2013-10-18 23:17:25 +03:00
}
}
2013-12-20 12:43:12 +03:00
return ret ;
}
2013-12-19 23:21:21 +03:00
TGoalVec VisitTile : : getAllPossibleSubgoals ( )
{
2014-02-16 00:32:49 +03:00
assert ( cb - > isInTheMap ( tile ) ) ;
2013-12-19 23:21:21 +03:00
TGoalVec ret ;
if ( ! cb - > isVisible ( tile ) )
ret . push_back ( sptr ( Goals : : Explore ( ) ) ) ; //what sense does it make?
else
{
2013-12-21 10:37:23 +03:00
std : : vector < const CGHeroInstance * > heroes ;
if ( hero )
heroes . push_back ( hero . h ) ; //use assigned hero if any
else
heroes = cb - > getHeroesInfo ( ) ; //use most convenient hero
for ( auto h : heroes )
2013-12-19 23:21:21 +03:00
{
2013-12-20 12:43:12 +03:00
if ( ai - > isAccessibleForHero ( tile , h ) )
ret . push_back ( sptr ( Goals : : VisitTile ( tile ) . sethero ( h ) ) ) ;
2013-12-19 23:21:21 +03:00
}
if ( ai - > canRecruitAnyHero ( ) )
ret . push_back ( sptr ( Goals : : RecruitHero ( ) ) ) ;
}
2015-12-29 01:14:08 +02:00
if ( ret . empty ( ) )
2013-12-26 12:53:37 +03:00
{
2015-12-29 01:14:08 +02:00
auto obj = vstd : : frontOrNull ( cb - > getVisitableObjs ( tile ) ) ;
if ( obj & & obj - > ID = = Obj : : HERO & & obj - > tempOwner = = ai - > playerID ) //our own hero stands on that tile
2014-12-21 15:50:26 +02:00
{
if ( hero . get ( true ) & & hero - > id = = obj - > id ) //if it's assigned hero, visit tile. If it's different hero, we can't visit tile now
ret . push_back ( sptr ( Goals : : VisitTile ( tile ) . sethero ( dynamic_cast < const CGHeroInstance * > ( obj ) ) . setisElementar ( true ) ) ) ;
else
throw cannotFulfillGoalException ( " Tile is already occupied by another hero " ) ; //FIXME: we should give up this tile earlier
}
2013-12-26 12:53:37 +03:00
else
ret . push_back ( sptr ( Goals : : ClearWayTo ( tile ) ) ) ;
}
2013-12-23 23:46:01 +03:00
2013-12-20 12:43:12 +03:00
//important - at least one sub-goal must handle case which is impossible to fulfill (unreachable tile)
2013-12-19 23:21:21 +03:00
return ret ;
}
2013-10-18 23:17:25 +03:00
TSubgoal DigAtTile : : whatToDoToAchieve ( )
{
2015-12-29 01:14:08 +02:00
const CGObjectInstance * firstObj = vstd : : frontOrNull ( cb - > getVisitableObjs ( tile ) ) ;
2013-10-18 23:17:25 +03:00
if ( firstObj & & firstObj - > ID = = Obj : : HERO & & firstObj - > tempOwner = = ai - > playerID ) //we have hero at dest
{
const CGHeroInstance * h = dynamic_cast < const CGHeroInstance * > ( firstObj ) ;
2013-11-15 20:30:16 +03:00
sethero ( h ) . setisElementar ( true ) ;
return sptr ( * this ) ;
2013-10-18 23:17:25 +03:00
}
2013-11-15 20:30:16 +03:00
return sptr ( Goals : : VisitTile ( tile ) ) ;
2013-10-18 23:17:25 +03:00
}
TSubgoal BuildThis : : whatToDoToAchieve ( )
{
//TODO check res
//look for town
//prerequisites?
2013-11-15 20:30:16 +03:00
return iAmElementar ( ) ;
2013-10-18 23:17:25 +03:00
}
TSubgoal CollectRes : : whatToDoToAchieve ( )
{
std : : vector < const IMarket * > markets ;
std : : vector < const CGObjectInstance * > visObjs ;
ai - > retreiveVisitableObjs ( visObjs , true ) ;
for ( const CGObjectInstance * obj : visObjs )
{
if ( const IMarket * m = IMarket : : castFrom ( obj , false ) )
{
if ( obj - > ID = = Obj : : TOWN & & obj - > tempOwner = = ai - > playerID & & m - > allowsTrade ( EMarketMode : : RESOURCE_RESOURCE ) )
markets . push_back ( m ) ;
else if ( obj - > ID = = Obj : : TRADING_POST ) //TODO a moze po prostu test na pozwalanie handlu?
markets . push_back ( m ) ;
}
}
boost : : sort ( markets , [ ] ( const IMarket * m1 , const IMarket * m2 ) - > bool
{
return m1 - > getMarketEfficiency ( ) < m2 - > getMarketEfficiency ( ) ;
} ) ;
markets . erase ( boost : : remove_if ( markets , [ ] ( const IMarket * market ) - > bool
{
return ! ( market - > o - > ID = = Obj : : TOWN & & market - > o - > tempOwner = = ai - > playerID )
& & ! ai - > isAccessible ( market - > o - > visitablePos ( ) ) ;
} ) , markets . end ( ) ) ;
if ( ! markets . size ( ) )
{
for ( const CGTownInstance * t : cb - > getTownsInfo ( ) )
{
if ( cb - > canBuildStructure ( t , BuildingID : : MARKETPLACE ) = = EBuildingState : : ALLOWED )
2013-11-15 20:30:16 +03:00
return sptr ( Goals : : BuildThis ( BuildingID : : MARKETPLACE , t ) ) ;
2013-10-18 23:17:25 +03:00
}
}
else
{
const IMarket * m = markets . back ( ) ;
//attempt trade at back (best prices)
int howManyCanWeBuy = 0 ;
for ( Res : : ERes i = Res : : WOOD ; i < = Res : : GOLD ; vstd : : advance ( i , 1 ) )
{
if ( i = = resID ) continue ;
int toGive = - 1 , toReceive = - 1 ;
m - > getOffer ( i , resID , toGive , toReceive , EMarketMode : : RESOURCE_RESOURCE ) ;
assert ( toGive > 0 & & toReceive > 0 ) ;
howManyCanWeBuy + = toReceive * ( cb - > getResourceAmount ( i ) / toGive ) ;
}
if ( howManyCanWeBuy + cb - > getResourceAmount ( static_cast < Res : : ERes > ( resID ) ) > = value )
{
2014-02-17 20:28:39 +03:00
auto backObj = cb - > getTopObj ( m - > o - > visitablePos ( ) ) ; //it'll be a hero if we have one there; otherwise marketplace
2013-10-18 23:17:25 +03:00
assert ( backObj ) ;
2013-11-15 20:30:16 +03:00
if ( backObj - > tempOwner ! = ai - > playerID )
{
return sptr ( Goals : : GetObj ( m - > o - > id . getNum ( ) ) ) ;
}
else
{
return sptr ( Goals : : GetObj ( m - > o - > id . getNum ( ) ) . setisElementar ( true ) ) ;
}
2013-10-18 23:17:25 +03:00
}
}
2013-12-25 01:01:16 +03:00
return sptr ( setisElementar ( true ) ) ; //all the conditions for trade are met
2013-10-18 23:17:25 +03:00
}
TSubgoal GatherTroops : : whatToDoToAchieve ( )
{
std : : vector < const CGDwelling * > dwellings ;
for ( const CGTownInstance * t : cb - > getTownsInfo ( ) )
{
auto creature = VLC - > creh - > creatures [ objid ] ;
if ( t - > subID = = creature - > faction ) //TODO: how to force AI to build unupgraded creatures? :O
{
auto creatures = vstd : : tryAt ( t - > town - > creatures , creature - > level - 1 ) ;
if ( ! creatures )
2016-01-19 19:25:15 +02:00
continue ;
2013-10-18 23:17:25 +03:00
int upgradeNumber = vstd : : find_pos ( * creatures , creature - > idNumber ) ;
if ( upgradeNumber < 0 )
continue ;
BuildingID bid ( BuildingID : : DWELL_FIRST + creature - > level - 1 + upgradeNumber * GameConstants : : CREATURES_PER_TOWN ) ;
if ( t - > hasBuilt ( bid ) ) //this assumes only creatures with dwellings are assigned to faction
{
dwellings . push_back ( t ) ;
}
else
{
2013-11-15 20:30:16 +03:00
return sptr ( Goals : : BuildThis ( bid , t ) ) ;
2013-10-18 23:17:25 +03:00
}
}
}
for ( auto obj : ai - > visitableObjs )
{
if ( obj - > ID ! = Obj : : CREATURE_GENERATOR1 ) //TODO: what with other creature generators?
continue ;
auto d = dynamic_cast < const CGDwelling * > ( obj ) ;
for ( auto creature : d - > creatures )
{
if ( creature . first ) //there are more than 0 creatures avaliabe
{
for ( auto type : creature . second )
{
if ( type = = objid & & ai - > freeResources ( ) . canAfford ( VLC - > creh - > creatures [ type ] - > cost ) )
dwellings . push_back ( d ) ;
}
}
}
}
if ( dwellings . size ( ) )
{
2014-09-21 18:08:47 +03:00
typedef std : : map < const CGHeroInstance * , const CGDwelling * > TDwellMap ;
// sorted helper
auto comparator = [ ] ( const TDwellMap : : value_type & a , const TDwellMap : : value_type & b ) - > bool
{
const CGPathNode * ln = ai - > myCb - > getPathsInfo ( a . first ) - > getPathInfo ( a . second - > visitablePos ( ) ) ,
* rn = ai - > myCb - > getPathsInfo ( b . first ) - > getPathInfo ( b . second - > visitablePos ( ) ) ;
if ( ln - > turns ! = rn - > turns )
return ln - > turns < rn - > turns ;
return ( ln - > moveRemains > rn - > moveRemains ) ;
} ;
// for all owned heroes generate map <hero -> nearest dwelling>
TDwellMap nearestDwellings ;
for ( const CGHeroInstance * hero : cb - > getHeroesInfo ( true ) )
{
nearestDwellings [ hero ] = * boost : : range : : min_element ( dwellings , CDistanceSorter ( hero ) ) ;
}
2016-11-29 23:07:35 +02:00
if ( nearestDwellings . size ( ) )
{
// find hero who is nearest to a dwelling
const CGDwelling * nearest = boost : : range : : min_element ( nearestDwellings , comparator ) - > second ;
if ( ! nearest )
throw cannotFulfillGoalException ( " Cannot find nearest dwelling! " ) ;
2014-09-21 18:08:47 +03:00
2016-11-29 23:07:35 +02:00
return sptr ( Goals : : GetObj ( nearest - > id . getNum ( ) ) ) ;
}
else
return sptr ( Goals : : Explore ( ) ) ;
2013-10-18 23:17:25 +03:00
}
else
2013-11-15 20:30:16 +03:00
return sptr ( Goals : : Explore ( ) ) ;
2013-10-18 23:17:25 +03:00
//TODO: exchange troops between heroes
}
TSubgoal Conquer : : whatToDoToAchieve ( )
{
2013-12-20 16:01:44 +03:00
return fh - > chooseSolution ( getAllPossibleSubgoals ( ) ) ;
}
TGoalVec Conquer : : getAllPossibleSubgoals ( )
{
TGoalVec ret ;
2013-10-18 23:17:25 +03:00
2014-02-17 10:36:03 +03:00
auto conquerable = [ ] ( const CGObjectInstance * obj ) - > bool
2013-10-18 23:17:25 +03:00
{
2014-02-17 10:36:03 +03:00
if ( cb - > getPlayerRelations ( ai - > playerID , obj - > tempOwner ) = = PlayerRelations : : ENEMIES )
2014-02-15 19:38:51 +03:00
{
switch ( obj - > ID . num )
{
case Obj : : TOWN :
case Obj : : HERO :
case Obj : : CREATURE_GENERATOR1 :
case Obj : : MINE : //TODO: check ai->knownSubterraneanGates
2014-02-17 10:36:03 +03:00
return true ;
2014-02-15 19:38:51 +03:00
}
}
2014-02-17 10:36:03 +03:00
return false ;
} ;
std : : vector < const CGObjectInstance * > objs ;
2017-07-15 00:15:08 +02:00
for ( auto obj : ai - > visitableObjs )
2014-02-17 10:36:03 +03:00
{
2017-07-15 00:15:08 +02:00
if ( conquerable ( obj ) )
2014-02-17 10:36:03 +03:00
objs . push_back ( obj ) ;
2014-02-15 19:38:51 +03:00
}
2013-10-18 23:17:25 +03:00
2017-07-15 00:15:08 +02:00
for ( auto h : cb - > getHeroesInfo ( ) )
2013-10-18 23:17:25 +03:00
{
2015-12-04 00:10:51 +02:00
auto sm = ai - > getCachedSectorMap ( h ) ;
2014-02-17 10:36:03 +03:00
std : : vector < const CGObjectInstance * > ourObjs ( objs ) ; //copy common objects
2017-07-15 00:15:08 +02:00
for ( auto obj : ai - > reservedHeroesMap [ h ] ) //add objects reserved by this hero
2014-02-17 10:36:03 +03:00
{
2017-07-15 00:15:08 +02:00
if ( conquerable ( obj ) )
2014-02-17 10:36:03 +03:00
ourObjs . push_back ( obj ) ;
}
2017-07-15 00:15:08 +02:00
for ( auto obj : ourObjs )
2013-10-18 23:17:25 +03:00
{
2015-10-25 12:16:43 +02:00
int3 dest = obj - > visitablePos ( ) ;
2015-12-04 00:10:51 +02:00
auto t = sm - > firstTileToGet ( h , dest ) ; //we assume that no more than one tile on the way is guarded
2017-07-15 00:15:08 +02:00
if ( t . valid ( ) ) //we know any path at all
2015-10-25 12:16:43 +02:00
{
2017-07-15 00:15:08 +02:00
if ( ai - > isTileNotReserved ( h , t ) ) //no other hero wants to conquer that tile
2015-10-25 12:16:43 +02:00
{
2017-07-15 00:15:08 +02:00
if ( isSafeToVisit ( h , dest ) )
2015-10-25 12:16:43 +02:00
{
2017-07-15 00:15:08 +02:00
if ( dest ! = t ) //there is something blocking our way
2015-10-25 12:16:43 +02:00
ret . push_back ( sptr ( Goals : : ClearWayTo ( dest , h ) . setisAbstract ( true ) ) ) ;
else
{
2017-07-15 00:15:08 +02:00
if ( obj - > ID . num = = Obj : : HERO ) //enemy hero may move to other position
2017-07-19 02:42:26 +02:00
{
2015-10-25 12:16:43 +02:00
ret . push_back ( sptr ( Goals : : VisitHero ( obj - > id . getNum ( ) ) . sethero ( h ) . setisAbstract ( true ) ) ) ;
2017-07-19 02:42:26 +02:00
}
2015-10-25 12:16:43 +02:00
else //just visit that tile
2017-07-19 02:42:26 +02:00
{
2017-07-15 00:15:08 +02:00
if ( obj - > ID . num = = Obj : : TOWN )
//if target is town, fuzzy system will use additional "estimatedReward" variable to increase priority a bit
ret . push_back ( sptr ( Goals : : VisitTile ( dest ) . sethero ( h ) . setobjid ( obj - > ID . num ) . setisAbstract ( true ) ) ) ; //TODO: change to getObj eventually and and move appropiate logic there
else
ret . push_back ( sptr ( Goals : : VisitTile ( dest ) . sethero ( h ) . setisAbstract ( true ) ) ) ;
2017-07-19 02:42:26 +02:00
}
2015-10-25 12:16:43 +02:00
}
}
else //we need to get army in order to conquer that place
ret . push_back ( sptr ( Goals : : GatherArmy ( evaluateDanger ( dest , h ) * SAFE_ATTACK_CONSTANT ) . sethero ( h ) . setisAbstract ( true ) ) ) ;
}
}
2013-10-18 23:17:25 +03:00
}
}
2013-12-20 16:01:44 +03:00
if ( ! objs . empty ( ) & & ai - > canRecruitAnyHero ( ) ) //probably no point to recruit hero if we see no objects to capture
ret . push_back ( sptr ( Goals : : RecruitHero ( ) ) ) ;
2013-10-18 23:17:25 +03:00
2013-12-20 16:01:44 +03:00
if ( ret . empty ( ) )
ret . push_back ( sptr ( Goals : : Explore ( ) ) ) ; //we need to find an enemy
return ret ;
2013-10-18 23:17:25 +03:00
}
2013-12-20 16:01:44 +03:00
2013-10-18 23:17:25 +03:00
TSubgoal Build : : whatToDoToAchieve ( )
{
2013-11-15 20:30:16 +03:00
return iAmElementar ( ) ;
2013-10-18 23:17:25 +03:00
}
TSubgoal Invalid : : whatToDoToAchieve ( )
{
2013-11-15 20:30:16 +03:00
return iAmElementar ( ) ;
2013-10-18 23:17:25 +03:00
}
2013-11-24 11:21:51 +03:00
std : : string GatherArmy : : completeMessage ( ) const
{
return " Hero " + hero . get ( ) - > name + " gathered army of value " + boost : : lexical_cast < std : : string > ( value ) ;
2014-01-16 23:24:06 +03:00
}
2013-11-24 11:21:51 +03:00
2013-10-18 23:17:25 +03:00
TSubgoal GatherArmy : : whatToDoToAchieve ( )
{
//TODO: find hero if none set
2013-12-23 23:46:01 +03:00
assert ( hero . h ) ;
2013-10-18 23:17:25 +03:00
2013-12-23 23:46:01 +03:00
return fh - > chooseSolution ( getAllPossibleSubgoals ( ) ) ; //find dwelling. use current hero to prevent him from doing nothing.
}
2015-12-04 01:06:02 +02:00
static const BuildingID unitsSource [ ] = { BuildingID : : DWELL_LVL_1 , BuildingID : : DWELL_LVL_2 , BuildingID : : DWELL_LVL_3 ,
BuildingID : : DWELL_LVL_4 , BuildingID : : DWELL_LVL_5 , BuildingID : : DWELL_LVL_6 , BuildingID : : DWELL_LVL_7 } ;
2013-12-23 23:46:01 +03:00
TGoalVec GatherArmy : : getAllPossibleSubgoals ( )
{
//get all possible towns, heroes and dwellings we may use
TGoalVec ret ;
2016-01-19 19:25:15 +02:00
2013-12-23 23:46:01 +03:00
//TODO: include evaluation of monsters gather in calculation
for ( auto t : cb - > getTownsInfo ( ) )
2013-10-18 23:17:25 +03:00
{
2013-12-23 23:46:01 +03:00
auto pos = t - > visitablePos ( ) ;
if ( ai - > isAccessibleForHero ( pos , hero ) )
2013-10-18 23:17:25 +03:00
{
2013-12-23 23:46:01 +03:00
if ( ! t - > visitingHero & & howManyReinforcementsCanGet ( hero , t ) )
{
if ( ! vstd : : contains ( ai - > townVisitsThisWeek [ hero ] , t ) )
ret . push_back ( sptr ( Goals : : VisitTile ( pos ) . sethero ( hero ) ) ) ;
}
auto bid = ai - > canBuildAnyStructure ( t , std : : vector < BuildingID >
( unitsSource , unitsSource + ARRAY_COUNT ( unitsSource ) ) , 8 - cb - > getDate ( Date : : DAY_OF_WEEK ) ) ;
if ( bid ! = BuildingID : : NONE )
ret . push_back ( sptr ( BuildThis ( bid , t ) ) ) ;
2013-10-18 23:17:25 +03:00
}
}
2013-12-23 23:46:01 +03:00
auto otherHeroes = cb - > getHeroesInfo ( ) ;
auto heroDummy = hero ;
2015-12-29 01:14:08 +02:00
vstd : : erase_if ( otherHeroes , [ heroDummy ] ( const CGHeroInstance * h )
2013-10-18 23:17:25 +03:00
{
2014-02-17 10:36:03 +03:00
return ( h = = heroDummy . h | | ! ai - > isAccessibleForHero ( heroDummy - > visitablePos ( ) , h , true )
| | ! ai - > canGetArmy ( heroDummy . h , h ) | | ai - > getGoal ( h ) - > goalType = = Goals : : GATHER_ARMY ) ;
2013-12-23 23:46:01 +03:00
} ) ;
for ( auto h : otherHeroes )
2013-10-18 23:17:25 +03:00
{
2013-12-23 23:46:01 +03:00
ret . push_back ( sptr ( Goals : : VisitHero ( h - > id . getNum ( ) ) . setisAbstract ( true ) . sethero ( hero ) ) ) ;
//go to the other hero if we are faster
ret . push_back ( sptr ( Goals : : VisitHero ( hero - > id . getNum ( ) ) . setisAbstract ( true ) . sethero ( h ) ) ) ;
//let the other hero come to us
}
2013-10-18 23:17:25 +03:00
2013-12-23 23:46:01 +03:00
std : : vector < const CGObjectInstance * > objs ;
for ( auto obj : ai - > visitableObjs )
{
if ( obj - > ID = = Obj : : CREATURE_GENERATOR1 )
{
2013-10-18 23:17:25 +03:00
auto relationToOwner = cb - > getPlayerRelations ( obj - > getOwner ( ) , ai - > playerID ) ;
//Use flagged dwellings only when there are available creatures that we can afford
if ( relationToOwner = = PlayerRelations : : SAME_PLAYER )
{
auto dwelling = dynamic_cast < const CGDwelling * > ( obj ) ;
for ( auto & creLevel : dwelling - > creatures )
{
if ( creLevel . first )
{
for ( auto & creatureID : creLevel . second )
{
auto creature = VLC - > creh - > creatures [ creatureID ] ;
2013-12-23 23:46:01 +03:00
if ( ai - > freeResources ( ) . canAfford ( creature - > cost ) )
objs . push_back ( obj ) ;
2013-10-18 23:17:25 +03:00
}
}
}
}
}
}
2013-12-23 23:46:01 +03:00
for ( auto h : cb - > getHeroesInfo ( ) )
{
2015-12-04 00:10:51 +02:00
auto sm = ai - > getCachedSectorMap ( h ) ;
2013-12-23 23:46:01 +03:00
for ( auto obj : objs )
{ //find safe dwelling
auto pos = obj - > visitablePos ( ) ;
2015-12-04 00:10:51 +02:00
if ( ai - > isGoodForVisit ( obj , h , * sm ) )
2013-12-23 23:46:01 +03:00
ret . push_back ( sptr ( Goals : : VisitTile ( pos ) . sethero ( h ) ) ) ;
}
}
2016-11-26 19:03:09 +02:00
2017-07-15 00:15:08 +02:00
if ( ai - > canRecruitAnyHero ( ) & & ai - > freeResources ( ) [ Res : : GOLD ] > GameConstants : : HERO_GOLD_COST ) //this is not stupid in early phase of game
2016-11-26 19:03:09 +02:00
{
2017-07-15 00:15:08 +02:00
if ( auto t = ai - > findTownWithTavern ( ) )
2016-11-26 19:03:09 +02:00
{
for ( auto h : cb - > getAvailableHeroes ( t ) ) //we assume that all towns have same set of heroes
{
2017-07-15 00:15:08 +02:00
if ( h & & h - > getTotalStrength ( ) > 500 ) //do not buy heroes with single creatures for GatherArmy
2016-11-26 19:03:09 +02:00
{
ret . push_back ( sptr ( Goals : : RecruitHero ( ) ) ) ;
break ;
}
}
}
}
2014-02-17 20:28:39 +03:00
2013-12-23 23:46:01 +03:00
if ( ret . empty ( ) )
2014-02-17 10:36:03 +03:00
{
if ( hero = = ai - > primaryHero ( ) | | value > = 1.1f )
ret . push_back ( sptr ( Goals : : Explore ( ) ) ) ;
else //workaround to break loop - seemingly there are no ways to explore left
throw goalFulfilledException ( sptr ( Goals : : GatherArmy ( 0 ) . sethero ( hero ) ) ) ;
}
2013-10-18 23:17:25 +03:00
2013-12-23 23:46:01 +03:00
return ret ;
2013-10-18 23:17:25 +03:00
}
2013-11-23 21:16:25 +03:00
//TSubgoal AbstractGoal::whatToDoToAchieve()
//{
2016-08-13 16:44:37 +02:00
// logAi->debug("Decomposing goal of type %s",name());
2013-11-23 21:16:25 +03:00
// return sptr (Goals::Explore());
//}
2013-10-18 23:17:25 +03:00
2013-11-15 20:30:16 +03:00
TSubgoal AbstractGoal : : goVisitOrLookFor ( const CGObjectInstance * obj )
2013-10-18 23:17:25 +03:00
{
if ( obj )
2013-11-15 20:30:16 +03:00
return sptr ( Goals : : GetObj ( obj - > id . getNum ( ) ) ) ;
2013-10-18 23:17:25 +03:00
else
2013-11-15 20:30:16 +03:00
return sptr ( Goals : : Explore ( ) ) ;
2013-10-18 23:17:25 +03:00
}
2013-11-15 20:30:16 +03:00
TSubgoal AbstractGoal : : lookForArtSmart ( int aid )
2013-10-18 23:17:25 +03:00
{
2013-11-15 20:30:16 +03:00
return sptr ( Goals : : Invalid ( ) ) ;
2013-10-18 23:17:25 +03:00
}
2013-11-15 20:30:16 +03:00
bool AbstractGoal : : invalid ( ) const
2013-10-18 23:17:25 +03:00
{
return goalType = = INVALID ;
2013-10-20 16:51:05 +03:00
}
2013-11-23 21:16:25 +03:00
void AbstractGoal : : accept ( VCAI * ai )
{
ai - > tryRealize ( * this ) ;
2013-11-24 14:36:51 +03:00
}
2013-12-19 23:21:21 +03:00
2013-11-24 14:36:51 +03:00
template < typename T >
void CGoal < T > : : accept ( VCAI * ai )
{
ai - > tryRealize ( static_cast < T & > ( * this ) ) ; //casting enforces template instantiation
}
2013-11-24 18:30:17 +03:00
2013-12-19 23:21:21 +03:00
float AbstractGoal : : accept ( FuzzyHelper * f )
{
return f - > evaluate ( * this ) ;
}
template < typename T >
float CGoal < T > : : accept ( FuzzyHelper * f )
{
return f - > evaluate ( static_cast < T & > ( * this ) ) ; //casting enforces template instantiation
}