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"
2018-07-26 12:06:55 +02:00
# include "ResourceManager.h"
2018-07-29 18:27:21 +02:00
# include "BuildingManager.h"
2017-07-13 10:26:03 +02:00
# include "../../lib/mapping/CMap.h" //for victory conditions
# include "../../lib/CPathfinder.h"
2018-07-29 21:07:40 +02:00
# include "../../lib/StringConstants.h"
2018-07-26 12:06:55 +02:00
# include "AIhelper.h"
2013-10-18 23:17:25 +03:00
extern boost : : thread_specific_ptr < CCallback > cb ;
extern boost : : thread_specific_ptr < VCAI > ai ;
2018-07-26 12:06:55 +02:00
extern boost : : thread_specific_ptr < AIhelper > ah ;
extern FuzzyHelper * fh ;
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 )
{
2018-07-26 12:06:55 +02:00
TSubgoal 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 ;
2018-04-07 10:44:14 +02:00
switch ( goalType )
2013-10-18 23:17:25 +03:00
{
2018-04-07 10:44:14 +02:00
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 :
desc = " EXPLORE " ;
break ;
case GATHER_ARMY :
desc = " GATHER ARMY " ;
break ;
2018-07-26 12:06:55 +02:00
case BUY_ARMY :
return " BUY ARMY " ;
break ;
2018-04-07 10:44:14 +02:00
case BOOST_HERO :
desc = " BOOST_HERO (unsupported) " ;
break ;
case RECRUIT_HERO :
return " RECRUIT HERO " ;
case BUILD_STRUCTURE :
return " BUILD STRUCTURE " ;
case COLLECT_RES :
2018-07-26 12:06:55 +02:00
desc = " COLLECT RESOURCE " + GameConstants : : RESOURCE_NAMES [ resID ] + " ( " + boost : : lexical_cast < std : : string > ( value ) + " ) " ;
2018-04-07 10:44:14 +02:00
break ;
2018-08-04 10:20:40 +02:00
case TRADE :
{
auto obj = cb - > getObjInstance ( ObjectInstanceID ( objid ) ) ;
if ( obj )
desc = ( boost : : format ( " TRADE %d of %s at %s " ) % value % GameConstants : : RESOURCE_NAMES [ resID ] % obj - > getObjectName ( ) ) . str ( ) ;
}
break ;
2018-04-07 10:44:14 +02:00
case GATHER_TROOPS :
desc = " GATHER TROOPS " ;
break ;
case GET_OBJ :
{
auto obj = cb - > getObjInstance ( ObjectInstanceID ( objid ) ) ;
if ( obj )
desc = " GET OBJ " + obj - > getObjectName ( ) ;
}
break ;
case FIND_OBJ :
desc = " FIND OBJ " + boost : : lexical_cast < std : : string > ( objid ) ;
break ;
case VISIT_HERO :
{
auto obj = cb - > getObjInstance ( ObjectInstanceID ( objid ) ) ;
if ( obj )
desc = " VISIT HERO " + obj - > getObjectName ( ) ;
}
break ;
case GET_ART_TYPE :
desc = " GET ARTIFACT OF TYPE " + VLC - > arth - > artifacts [ aid ] - > Name ( ) ;
break ;
case ISSUE_COMMAND :
return " ISSUE COMMAND (unsupported) " ;
case VISIT_TILE :
desc = " VISIT TILE " + tile . toString ( ) ;
break ;
case CLEAR_WAY_TO :
desc = " CLEAR WAY TO " + tile . toString ( ) ;
break ;
case DIG_AT_TILE :
desc = " DIG AT TILE " + tile . toString ( ) ;
break ;
default :
return boost : : lexical_cast < std : : string > ( goalType ) ;
2013-10-18 23:17:25 +03:00
}
2018-04-07 10:44:14 +02: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
}
2018-04-07 10:44:14 +02:00
bool Goals : : AbstractGoal : : operator = = ( AbstractGoal & g )
2014-02-19 19:23:47 +03:00
{
2018-08-05 09:21:41 +02:00
/*this operator checks if goals are EQUIVALENT, ie. if they represent same objective
it does not not check isAbstract or isElementar , as this is up to VCAI decomposition logic
*/
2018-04-07 10:44:14 +02:00
if ( g . goalType ! = goalType )
2014-02-19 19:23:47 +03:00
return false ;
2018-04-07 10:44:14 +02:00
switch ( goalType )
2014-02-19 19:23:47 +03:00
{
2018-04-07 10:44:14 +02:00
//no parameters
case INVALID :
case WIN :
case DO_NOT_LOSE :
2018-07-26 12:06:55 +02:00
case RECRUIT_HERO : //overloaded
2018-04-07 10:44:14 +02:00
return true ;
break ;
//assigned to hero, no parameters
case CONQUER :
case EXPLORE :
case BOOST_HERO :
return g . hero . h = = hero . h ; //how comes HeroPtrs are equal for different heroes?
break ;
2018-07-29 21:07:40 +02:00
case GATHER_ARMY : //actual value is indifferent
2018-07-26 12:06:55 +02:00
return ( g . hero . h = = hero . h | | town = = g . town ) ; //TODO: gather army for town maybe?
break ;
2018-04-07 10:44:14 +02:00
//assigned hero and tile
case VISIT_TILE :
case CLEAR_WAY_TO :
2018-07-26 12:06:55 +02:00
case DIG_AT_TILE :
2018-04-07 10:44:14 +02:00
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 :
return ( g . hero . h = = hero . h & & g . objid = = objid ) ;
break ;
2018-07-26 12:06:55 +02:00
case BUILD_STRUCTURE :
return ( town = = g . town & & bid = = g . bid ) ; //build specific structure in specific town
break ;
2018-04-07 10:44:14 +02:00
//no check atm
case COLLECT_RES :
2018-08-04 10:20:40 +02:00
case TRADE : //TODO
2018-07-26 12:06:55 +02:00
return ( resID = = g . resID ) ; //every hero may collect resources
break ;
2018-08-05 09:21:41 +02:00
case BUILD : //abstract build is indentical, TODO: consider building anything in town
return true ;
break ;
2018-04-07 10:44:14 +02:00
case GATHER_TROOPS :
case ISSUE_COMMAND :
default :
return false ;
2014-02-19 19:23:47 +03:00
}
}
2018-08-05 09:21:41 +02:00
bool Goals : : AbstractGoal : : operator < ( AbstractGoal & g ) //for std::unique
{
//TODO: make sure it gets goals consistent with == operator
if ( goalType < g . goalType )
return true ;
if ( goalType > g . goalType )
return false ;
if ( hero < g . hero )
return true ;
if ( hero > g . hero )
return false ;
if ( tile < g . tile )
return true ;
if ( g . tile < tile )
return false ;
if ( objid < g . objid )
return true ;
if ( objid > g . objid )
return false ;
if ( town < g . town )
return true ;
if ( town > g . town )
return false ;
if ( value < g . value )
return true ;
if ( value > g . value )
return false ;
if ( priority < g . priority )
return true ;
if ( priority > g . priority )
return false ;
if ( resID < g . resID )
return true ;
if ( resID > g . resID )
return false ;
if ( bid < g . bid )
return true ;
if ( bid > g . bid )
return false ;
if ( aid < g . aid )
return true ;
if ( aid > g . aid )
return false ;
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
{
2018-07-26 12:06:55 +02:00
template < >
void CGoal < Win > : : accept ( VCAI * ai )
{
ai - > tryRealize ( static_cast < Win & > ( * this ) ) ;
}
2013-11-30 10:52:08 +03:00
2018-07-26 12:06:55 +02:00
template < >
void CGoal < Build > : : accept ( VCAI * ai )
{
ai - > tryRealize ( static_cast < Build & > ( * this ) ) ;
}
template < >
float CGoal < Win > : : accept ( FuzzyHelper * f )
{
return f - > evaluate ( static_cast < Win & > ( * this ) ) ;
}
2013-12-19 23:21:21 +03:00
2018-07-26 12:06:55 +02:00
template < >
float CGoal < Build > : : accept ( FuzzyHelper * f )
{
return f - > evaluate ( static_cast < Build & > ( * this ) ) ;
}
bool TSubgoal : : operator = = ( const TSubgoal & rhs ) const
{
return * get ( ) = = * rhs . get ( ) ; //comparison for Goals is overloaded, so they don't need to be identical to match
}
2018-08-05 09:21:41 +02:00
bool TSubgoal : : operator < ( const TSubgoal & rhs ) const
2018-07-26 12:06:55 +02:00
{
2018-08-05 09:21:41 +02:00
return get ( ) < rhs . get ( ) ; //compae by value
}
bool BuyArmy : : operator = = ( AbstractGoal & g )
{
if ( g . goalType ! = goalType )
return false ;
2018-07-26 12:06:55 +02:00
//if (hero && hero != g.hero)
// return false;
return town = = g . town ;
}
bool BuyArmy : : fulfillsMe ( TSubgoal goal )
{
//if (hero && hero != goal->hero)
// return false;
return town = = goal - > town & & goal - > value > = value ; //can always buy more army
}
TSubgoal BuyArmy : : whatToDoToAchieve ( )
{
//TODO: calculate the actual cost of units instead
TResources price ;
price [ Res : : GOLD ] = value * 0.4f ; //some approximate value
return ah - > whatToDo ( price , iAmElementar ( ) ) ; //buy right now or gather resources
}
std : : string BuyArmy : : completeMessage ( ) const
{
2018-07-27 09:22:07 +02:00
return boost : : format ( " Bought army of value %d in town of %s " ) % value , town - > name ;
2018-07-26 12:06:55 +02:00
}
2013-11-30 10:52:08 +03:00
}
2018-08-04 10:20:40 +02:00
TSubgoal Trade : : whatToDoToAchieve ( )
{
return iAmElementar ( ) ;
}
2018-08-05 09:21:41 +02:00
bool Trade : : operator = = ( AbstractGoal & g )
2018-08-04 10:20:40 +02:00
{
2018-08-05 09:21:41 +02:00
if ( g . goalType ! = goalType )
return false ;
2018-08-04 10:20:40 +02:00
if ( g . resID = = resID )
if ( g . value = = value ) //TODO: not sure if that logic is consitent
return true ;
return false ;
}
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
2018-04-07 10:44:14 +02: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
2018-04-07 10:44:14 +02:00
if ( event . effect . type = = EventEffect : : VICTORY )
2013-12-29 14:27:38 +03:00
{
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
2018-04-07 10:44:14 +02:00
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 :
2018-04-07 10:44:14 +02:00
return sptr ( Goals : : GetArtOfType ( goal . objectType ) ) ;
2013-12-29 14:27:38 +03:00
case EventCondition : : DESTROY :
2018-04-07 10:44:14 +02:00
{
if ( goal . object )
2013-10-18 23:17:25 +03:00
{
2018-04-07 10:44:14 +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 ( ) ) ;
2015-04-13 18:45:43 +02:00
2018-04-07 10:44:14 +02:00
return sptr ( Goals : : GetObj ( goal . object - > id . getNum ( ) ) ) ;
2013-12-29 14:27:38 +03:00
}
2018-04-07 10:44:14 +02:00
else
2013-12-29 14:27:38 +03:00
{
2018-04-07 10:44:14 +02:00
// TODO: destroy all objects of type goal.objectType
// This situation represents "kill all creatures" condition from H3
break ;
}
}
case EventCondition : : HAVE_BUILDING :
{
2013-12-29 14:27:38 +03:00
// 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)
2018-04-07 10:44:14 +02:00
if ( goal . objectType = = BuildingID : : GRAIL )
{
if ( auto h = ai - > getHeroWithGrail ( ) )
2013-12-29 14:27:38 +03:00
{
2018-04-07 10:44:14 +02:00
//hero is in a town that can host Grail
if ( h - > visitedTown & & ! vstd : : contains ( h - > visitedTown - > forbiddenBuildings , BuildingID : : GRAIL ) )
2013-10-18 23:17:25 +03:00
{
2018-04-07 10:44:14 +02:00
const CGTownInstance * t = h - > visitedTown ;
2018-07-26 12:06:55 +02:00
return sptr ( Goals : : BuildThis ( BuildingID : : GRAIL , t ) . setpriority ( 10 ) ) ;
2018-04-07 10:44:14 +02:00
}
else
{
auto towns = cb - > getTownsInfo ( ) ;
towns . erase ( boost : : remove_if ( towns ,
[ ] ( const CGTownInstance * t ) - > bool
2013-12-29 14:27:38 +03:00
{
2018-04-07 10:44:14 +02:00
return vstd : : contains ( t - > forbiddenBuildings , BuildingID : : GRAIL ) ;
} ) ,
towns . end ( ) ) ;
boost : : sort ( towns , CDistanceSorter ( h . get ( ) ) ) ;
if ( towns . size ( ) )
{
return sptr ( Goals : : VisitTile ( towns . front ( ) - > visitablePos ( ) ) . sethero ( h ) ) ;
2013-12-29 14:27:38 +03:00
}
2013-10-18 23:17:25 +03:00
}
}
2018-04-07 10:44:14 +02:00
double ratio = 0 ;
// maybe make this check a bit more complex? For example:
// 0.75 -> dig randomly within 3 tiles radius
// 0.85 -> radius now 2 tiles
// 0.95 -> 1 tile radius, position is fully known
// AFAIK H3 AI does something like this
int3 grailPos = cb - > getGrailPos ( & ratio ) ;
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
}
2018-04-07 10:44:14 +02:00
break ;
}
2013-12-29 14:27:38 +03:00
case EventCondition : : CONTROL :
2018-04-07 10:44:14 +02:00
{
if ( goal . object )
2013-10-18 23:17:25 +03:00
{
2018-04-07 10:44:14 +02:00
return sptr ( Goals : : GetObj ( goal . object - > id . getNum ( ) ) ) ;
}
else
{
//TODO: control all objects of type "goal.objectType"
// Represents H3 condition "Flag all mines"
break ;
2013-12-29 14:27:38 +03:00
}
2018-04-07 10:44:14 +02:00
}
2013-12-29 14:27:38 +03:00
case EventCondition : : HAVE_RESOURCES :
//TODO mines? piles? marketplace?
//save?
2018-04-07 10:44:14 +02:00
return sptr ( Goals : : CollectRes ( static_cast < Res : : ERes > ( goal . objectType ) , goal . value ) ) ;
2013-12-29 14:27:38 +03:00
case EventCondition : : HAVE_CREATURES :
2018-04-07 10:44:14 +02:00
return sptr ( Goals : : GatherTroops ( goal . objectType , goal . value ) ) ;
2013-12-29 14:27:38 +03:00
case EventCondition : : TRANSPORT :
2018-04-07 10:44:14 +02: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-12-29 14:27:38 +03:00
case EventCondition : : STANDARD_WIN :
2018-04-07 10:44:14 +02:00
return sptr ( Goals : : Conquer ( ) ) ;
2013-12-29 14:27:38 +03:00
// 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
2018-04-07 10:44:14 +02:00
return sptr ( Goals : : Conquer ( ) ) ;
2013-12-29 14:27:38 +03:00
default :
assert ( 0 ) ;
2013-10-18 23:17:25 +03:00
}
}
2018-04-07 10:44:14 +02:00
return sptr ( Goals : : Invalid ( ) ) ;
2013-10-18 23:17:25 +03:00
}
TSubgoal FindObj : : whatToDoToAchieve ( )
{
const CGObjectInstance * o = nullptr ;
2018-04-07 10:44:14 +02:00
if ( resID > - 1 ) //specified
2013-10-18 23:17:25 +03:00
{
2018-04-07 10:44:14 +02:00
for ( const CGObjectInstance * obj : ai - > visitableObjs )
2013-10-18 23:17:25 +03:00
{
if ( obj - > ID = = objid & & obj - > subID = = resID )
{
o = obj ;
break ; //TODO: consider multiple objects and choose best
}
}
}
else
{
2018-04-07 10:44:14 +02:00
for ( const CGObjectInstance * obj : ai - > visitableObjs )
2013-10-18 23:17:25 +03:00
{
if ( obj - > ID = = objid )
{
o = obj ;
break ; //TODO: consider multiple objects and choose best
}
}
}
2018-04-07 10:44:14 +02:00
if ( o & & ai - > isAccessible ( o - > pos ) ) //we don't use isAccessibleForHero as we don't know which hero it is
return sptr ( Goals : : GetObj ( o - > id . getNum ( ) ) ) ;
2013-10-18 23:17:25 +03:00
else
2018-04-07 10:44:14 +02:00
return sptr ( Goals : : Explore ( ) ) ;
2013-10-18 23:17:25 +03:00
}
2013-12-20 12:43:12 +03:00
2018-07-26 12:06:55 +02:00
bool Goals : : FindObj : : fulfillsMe ( TSubgoal goal )
{
if ( goal - > goalType = = Goals : : VISIT_TILE ) //visiting tile visits object at same time
{
if ( ! hero | | hero = = goal - > hero )
for ( auto obj : cb - > getVisitableObjs ( goal - > tile ) ) //check if any object on that tile matches criteria
if ( obj - > visitablePos ( ) = = goal - > tile ) //object could be removed
if ( obj - > ID = = objid & & obj - > subID = = resID ) //same type and subtype
return true ;
}
return false ;
}
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 )
2018-04-07 10:44:14 +02:00
return sptr ( Goals : : Explore ( ) ) ;
if ( obj - > tempOwner = = ai - > playerID ) //we can't capture our own object -> move to Win codition
2014-12-21 12:33:53 +02:00
throw cannotFulfillGoalException ( " Cannot capture my own object " + obj - > getObjectName ( ) ) ;
2013-10-18 23:17:25 +03:00
int3 pos = obj - > visitablePos ( ) ;
2018-04-07 10:44:14 +02:00
if ( hero )
2013-12-25 01:01:16 +03:00
{
2018-04-07 10:44:14 +02:00
if ( ai - > isAccessibleForHero ( pos , hero ) )
return sptr ( Goals : : VisitTile ( pos ) . sethero ( hero ) ) ;
2013-12-25 01:01:16 +03:00
}
else
{
2018-04-07 10:44:14 +02:00
for ( auto h : cb - > getHeroesInfo ( ) )
2014-12-21 12:33:53 +02:00
{
2018-04-07 10:44:14 +02:00
if ( ai - > isAccessibleForHero ( pos , h ) )
2014-12-21 12:33:53 +02:00
return sptr ( Goals : : VisitTile ( pos ) . sethero ( h ) ) ; //we must visit object with same hero, if any
}
2013-12-25 01:01:16 +03:00
}
2018-04-07 10:44:14 +02:00
return sptr ( Goals : : ClearWayTo ( pos ) . sethero ( hero ) ) ;
2013-10-18 23:17:25 +03:00
}
2018-08-05 09:21:41 +02:00
bool Goals : : GetObj : : operator = = ( AbstractGoal & g )
{
if ( g . goalType ! = goalType )
return false ;
return g . objid = = objid ;
}
2018-04-07 10:44:14 +02:00
bool GetObj : : fulfillsMe ( TSubgoal goal )
2013-11-24 22:15:08 +03:00
{
2018-07-26 12:06:55 +02:00
if ( goal - > goalType = = Goals : : VISIT_TILE ) //visiting tile visits object at same time
2013-12-25 01:12:12 +03:00
{
2018-07-26 12:06:55 +02:00
if ( ! hero | | hero = = goal - > hero )
{
auto obj = cb - > getObj ( ObjectInstanceID ( objid ) ) ;
if ( obj & & obj - > visitablePos ( ) = = goal - > tile ) //object could be removed
return true ;
}
2013-12-25 01:12:12 +03:00
}
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 )
2018-04-07 10:44:14 +02:00
return sptr ( Goals : : Explore ( ) ) ;
2013-10-18 23:17:25 +03:00
int3 pos = obj - > visitablePos ( ) ;
2018-04-07 10:44:14 +02:00
if ( hero & & ai - > isAccessibleForHero ( pos , hero , true ) & & isSafeToVisit ( hero , pos ) ) //enemy heroes can get reinforcements
2013-11-15 20:30:16 +03:00
{
2018-04-07 10:44:14 +02: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
2018-04-07 10:44:14 +02:00
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
}
2018-04-07 10:44:14 +02:00
return sptr ( Goals : : Invalid ( ) ) ;
2013-10-18 23:17:25 +03:00
}
2018-08-05 09:21:41 +02:00
bool Goals : : VisitHero : : operator = = ( AbstractGoal & g )
{
if ( g . goalType ! = goalType )
return false ;
return g . hero = = hero & & g . objid = = objid ;
}
2018-04-07 10:44:14 +02:00
bool VisitHero : : fulfillsMe ( TSubgoal goal )
2013-11-24 22:15:08 +03:00
{
2018-07-26 12:06:55 +02:00
//TODO: VisitObj shoudl not be used for heroes, but...
if ( goal - > goalType = = Goals : : VISIT_TILE )
2016-01-19 19:25:15 +02:00
{
2018-07-26 12:06:55 +02:00
auto obj = cb - > getObj ( ObjectInstanceID ( objid ) ) ;
if ( ! obj )
{
logAi - > error ( " Hero %s: VisitHero::fulfillsMe at %s: object %d not found " , hero . name , goal - > tile . toString ( ) , objid ) ;
return false ;
}
return obj - > visitablePos ( ) = = goal - > tile ;
2016-01-19 19:25:15 +02:00
}
2018-07-26 12:06:55 +02:00
return false ;
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 ( ) )
2018-04-07 10:44:14 +02: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! " ) ;
2018-04-07 10:44:14 +02: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
2018-08-05 09:21:41 +02:00
bool Goals : : ClearWayTo : : operator = = ( AbstractGoal & g )
{
if ( g . goalType ! = goalType )
return false ;
return g . goalType = = goalType & & g . tile = = tile ;
}
2018-07-26 12:06:55 +02:00
bool Goals : : ClearWayTo : : fulfillsMe ( TSubgoal goal )
{
if ( goal - > goalType = = Goals : : VISIT_TILE )
2018-07-27 09:22:07 +02:00
{
2018-07-26 12:06:55 +02:00
if ( ! hero | | hero = = goal - > hero )
return tile = = goal - > tile ;
2018-07-27 09:22:07 +02:00
}
2018-07-26 12:06:55 +02:00
return false ;
}
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 ;
2018-04-07 10:44:14 +02:00
if ( hero )
2014-02-20 23:18:49 +03:00
heroes . push_back ( hero . h ) ;
else
heroes = cb - > getHeroesInfo ( ) ;
2013-12-26 12:53:37 +03:00
2018-04-07 10:44:14 +02:00
for ( auto h : heroes )
2014-02-20 23:18:49 +03:00
{
//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 ) ;
2018-04-07 10:44:14 +02:00
if ( ! tileToHit . valid ( ) )
2014-02-15 19:38:51 +03:00
continue ;
2013-10-18 23:17:25 +03:00
2018-04-07 10:44:14 +02: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-12-23 23:46:01 +03:00
}
2013-10-18 23:17:25 +03:00
2014-02-17 20:28:39 +03:00
auto topObj = cb - > getTopObj ( tileToHit ) ;
2018-04-07 10:44:14 +02:00
if ( topObj )
2013-12-23 23:46:01 +03:00
{
2018-04-07 10:44:14 +02:00
if ( vstd : : contains ( ai - > reservedObjs , topObj ) & & ! vstd : : contains ( ai - > reservedHeroesMap [ h ] , topObj ) )
2014-02-23 19:55:42 +03:00
{
2018-04-07 10:44:14 +02: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
2018-04-07 10:44:14 +02:00
if ( topObj - > ID = = Obj : : HERO & & cb - > getPlayerRelations ( h - > tempOwner , topObj - > tempOwner ) ! = PlayerRelations : : ENEMIES )
{
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 ( ) ) ;
2018-04-07 10:44:14 +02:00
}
if ( topObj - > ID = = Obj : : QUEST_GUARD | | topObj - > ID = = Obj : : BORDERGUARD )
2013-12-25 01:01:16 +03:00
{
2018-04-07 10:44:14 +02:00
if ( shouldVisit ( h , topObj ) )
2013-12-25 01:01:16 +03:00
{
//do NOT use VISIT_TILE, as tile with quets guard can't be visited
2018-04-07 10:44:14 +02:00
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
}
2018-04-07 10:44:14 +02:00
if ( isSafeToVisit ( h , tileToHit ) ) //this makes sense only if tile is guarded, but there i no quest object
2014-02-20 23:18:49 +03:00
{
2018-04-07 10:44:14 +02:00
ret . push_back ( sptr ( Goals : : VisitTile ( tileToHit ) . sethero ( h ) ) ) ;
2014-02-20 23:18:49 +03:00
}
else
{
2018-04-07 10:44:14 +02:00
ret . push_back ( sptr ( Goals : : GatherArmy ( evaluateDanger ( tileToHit , h ) * SAFE_ATTACK_CONSTANT ) .
sethero ( h ) . setisAbstract ( true ) ) ) ;
2014-02-20 23:18:49 +03:00
}
2013-12-23 23:46:01 +03:00
}
2018-04-07 10:44:14 +02:00
if ( ai - > canRecruitAnyHero ( ) )
ret . push_back ( sptr ( Goals : : RecruitHero ( ) ) ) ;
2013-10-18 23:17:25 +03:00
2018-04-07 10:44:14 +02: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 ( ) ) ;
2018-04-07 10:44:14 +02: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 ( ) ) ;
2018-04-07 10:44:14 +02:00
if ( hero ) //use best step for this hero
{
2013-12-20 19:49:51 +03:00
return ret ;
2018-04-07 10:44:14 +02:00
}
2013-12-20 19:49:51 +03:00
else
{
2018-04-07 10:44:14 +02:00
if ( ret - > hero . get ( true ) )
return sptr ( sethero ( ret - > hero . h ) . setisAbstract ( true ) ) ; //choose this hero and then continue with him
2013-12-20 19:49:51 +03:00
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
2018-04-07 10:44:14 +02:00
if ( hero )
{
2013-12-23 23:46:01 +03:00
heroes . push_back ( hero . h ) ;
2018-04-07 10:44:14 +02:00
}
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
{
2018-04-07 10:44:14 +02:00
if ( ai - > getGoal ( h ) - > goalType = = Goals : : EXPLORE ) //do not reassign hero who is already explorer
2014-02-19 19:23:47 +03:00
return true ;
2014-02-17 10:36:03 +03:00
2018-04-07 10:44:14 +02:00
if ( ! ai - > isAbleToExplore ( h ) )
2014-03-23 19:00:43 +03:00
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 ;
2018-04-07 10:44:14 +02:00
for ( auto obj : ai - > visitableObjs )
2013-10-18 23:17:25 +03:00
{
2018-04-07 10:44:14 +02:00
if ( ! vstd : : contains ( ai - > alreadyVisited , obj ) )
2013-10-18 23:17:25 +03:00
{
2018-04-07 10:44:14 +02:00
switch ( obj - > ID . num )
2013-12-21 20:34:59 +03:00
{
2015-03-09 00:25:52 +02:00
case Obj : : REDWOOD_OBSERVATORY :
case Obj : : PILLAR_OF_FIRE :
case Obj : : CARTOGRAPHER :
2018-04-07 10:44:14 +02:00
objs . push_back ( obj ) ;
2015-03-09 00:25:52 +02:00
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 )
2018-04-07 10:44:14 +02:00
objs . push_back ( obj ) ;
2015-03-09 00:25:52 +02:00
break ;
2015-03-08 16:49:14 +02:00
}
}
else
{
2018-04-07 10:44:14 +02:00
switch ( obj - > ID . num )
2015-03-08 16:49:14 +02:00
{
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
2018-07-26 16:15:37 +02:00
auto primaryHero = ai - > primaryHero ( ) . h ;
2018-04-07 10:44:14 +02: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
2018-04-07 10:44:14 +02: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
2018-04-07 10:44:14 +02:00
if ( ai - > isTileNotReserved ( h , t ) )
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 ) ;
2018-04-07 10:44:14 +02:00
if ( t . valid ( ) )
2014-02-16 00:32:49 +03:00
{
2018-04-07 10:44:14 +02: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
{
2018-07-26 16:15:37 +02:00
//FIXME: possible issues when gathering army to break
if ( hero . h = = h | | //exporation is assigned to this hero
( ! hero & & h = = primaryHero ) ) //not assigned to specific hero, let our main hero do the job
2014-03-23 19:00:43 +03:00
{
2018-07-26 16:15:37 +02:00
t = ai - > explorationDesperate ( h ) ; //check this only ONCE, high cost
if ( t . valid ( ) ) //don't waste time if we are completely blocked
{
2018-04-07 10:44:14 +02:00
ret . push_back ( sptr ( Goals : : ClearWayTo ( t , h ) . setisAbstract ( true ) ) ) ;
2018-07-26 16:15:37 +02:00
continue ;
}
2014-03-23 19:00:43 +03:00
}
2018-07-26 16:15:37 +02:00
ai - > markHeroUnableToExplore ( h ) ; //there is no freely accessible tile, do not poll this hero anymore
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
2018-04-07 10:44:14 +02:00
if ( ( ! hero | | ret . empty ( ) ) & & ai - > canRecruitAnyHero ( ) )
ret . push_back ( sptr ( Goals : : RecruitHero ( ) ) ) ;
2013-10-18 23:17:25 +03:00
2018-04-07 10:44:14 +02:00
if ( ret . empty ( ) )
2013-12-26 12:53:37 +03:00
{
2018-04-07 10:44:14 +02: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
2018-04-07 10:44:14 +02:00
bool Explore : : fulfillsMe ( TSubgoal goal )
2013-12-26 12:53:37 +03:00
{
2018-04-07 10:44:14 +02:00
if ( goal - > goalType = = Goals : : EXPLORE )
2013-12-26 12:53:37 +03:00
{
2018-04-07 10:44:14 +02:00
if ( goal - > hero )
2013-12-26 12:53:37 +03:00
return hero = = goal - > hero ;
else
return true ; //cancel ALL exploration
}
return false ;
}
2013-10-18 23:17:25 +03:00
TSubgoal RecruitHero : : whatToDoToAchieve ( )
{
2018-04-07 10:44:14 +02:00
const CGTownInstance * t = ai - > findTownWithTavern ( ) ;
2013-10-18 23:17:25 +03:00
if ( ! t )
2018-07-26 12:06:55 +02:00
return sptr ( Goals : : BuildThis ( BuildingID : : TAVERN ) . setpriority ( 2 ) ) ;
2013-10-18 23:17:25 +03:00
2018-07-26 12:06:55 +02:00
TResources res ;
res [ Res : : GOLD ] = GameConstants : : HERO_GOLD_COST ;
return ah - > whatToDo ( res , iAmElementar ( ) ) ; //either buy immediately, or collect res
}
2013-10-18 23:17:25 +03:00
2018-08-05 09:21:41 +02:00
bool Goals : : RecruitHero : : operator = = ( AbstractGoal & g )
2018-07-26 12:06:55 +02:00
{
2018-08-05 09:21:41 +02:00
if ( g . goalType ! = goalType )
return false ;
2018-07-26 12:06:55 +02:00
//TODO: check town and hero
return true ; //for now, recruiting any hero will do
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
{
2018-04-07 10:44:14 +02: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 ;
}
2018-08-05 09:21:41 +02:00
bool Goals : : VisitTile : : operator = = ( AbstractGoal & g )
{
if ( g . goalType ! = goalType )
return false ;
return g . goalType = = goalType & & g . tile = = tile ;
}
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 ;
2018-04-07 10:44:14 +02:00
if ( ! cb - > isVisible ( tile ) )
ret . push_back ( sptr ( Goals : : Explore ( ) ) ) ; //what sense does it make?
2013-12-19 23:21:21 +03:00
else
{
2013-12-21 10:37:23 +03:00
std : : vector < const CGHeroInstance * > heroes ;
2018-04-07 10:44:14 +02:00
if ( hero )
2013-12-21 10:37:23 +03:00
heroes . push_back ( hero . h ) ; //use assigned hero if any
else
heroes = cb - > getHeroesInfo ( ) ; //use most convenient hero
2018-04-07 10:44:14 +02:00
for ( auto h : heroes )
2013-12-19 23:21:21 +03:00
{
2018-04-07 10:44:14 +02:00
if ( ai - > isAccessibleForHero ( tile , h ) )
ret . push_back ( sptr ( Goals : : VisitTile ( tile ) . sethero ( h ) ) ) ;
2013-12-19 23:21:21 +03:00
}
2018-04-07 10:44:14 +02:00
if ( ai - > canRecruitAnyHero ( ) )
ret . push_back ( sptr ( Goals : : RecruitHero ( ) ) ) ;
2013-12-19 23:21:21 +03:00
}
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
{
2018-04-07 10:44:14 +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
2014-12-21 15:50:26 +02:00
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
2018-04-07 10:44:14 +02:00
ret . push_back ( sptr ( Goals : : ClearWayTo ( tile ) ) ) ;
2013-12-26 12:53:37 +03:00
}
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 ( )
{
2018-04-07 10:44:14 +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
{
2018-04-07 10:44:14 +02:00
const CGHeroInstance * h = dynamic_cast < const CGHeroInstance * > ( firstObj ) ;
2013-11-15 20:30:16 +03:00
sethero ( h ) . setisElementar ( true ) ;
2018-04-07 10:44:14 +02:00
return sptr ( * this ) ;
2013-10-18 23:17:25 +03:00
}
2018-04-07 10:44:14 +02:00
return sptr ( Goals : : VisitTile ( tile ) ) ;
2013-10-18 23:17:25 +03:00
}
2018-08-05 09:21:41 +02:00
bool Goals : : DigAtTile : : operator = = ( AbstractGoal & g )
{
if ( g . goalType ! = goalType )
return false ;
return g . goalType = = goalType & & g . tile = = tile ;
}
2013-10-18 23:17:25 +03:00
TSubgoal BuildThis : : whatToDoToAchieve ( )
{
2018-07-26 12:06:55 +02:00
auto b = BuildingID ( bid ) ;
// find town if not set
if ( ! town & & hero )
town = hero - > visitedTown ;
if ( ! town )
{
for ( const CGTownInstance * t : cb - > getTownsInfo ( ) )
{
switch ( cb - > canBuildStructure ( town , b ) )
{
case EBuildingState : : ALLOWED :
town = t ;
break ; //TODO: look for prerequisites? this is not our reponsibility
default :
continue ;
}
}
}
if ( town ) //we have specific town to build this
{
2018-08-05 09:21:41 +02:00
if ( cb - > canBuildStructure ( town , b ) ! = EBuildingState : : ALLOWED ) //FIXME: decompose further? kind of mess if we're here
throw cannotFulfillGoalException ( " Not possible to build " ) ;
else
{
auto res = town - > town - > buildings . at ( BuildingID ( bid ) ) - > resources ;
return ah - > whatToDo ( res , iAmElementar ( ) ) ; //realize immediately or gather resources
}
2018-07-26 12:06:55 +02:00
}
else
throw cannotFulfillGoalException ( " Cannot find town to build this " ) ;
}
TGoalVec Goals : : CollectRes : : getAllPossibleSubgoals ( )
{
TGoalVec ret ;
auto givesResource = [ this ] ( const CGObjectInstance * obj ) - > bool
{
//TODO: move this logic to object side
//TODO: remember mithril exists
//TODO: water objects
//TODO: Creature banks
//return false first from once-visitable, before checking if they were even visited
switch ( obj - > ID . num )
{
case Obj : : TREASURE_CHEST :
return resID = = Res : : GOLD ;
break ;
case Obj : : RESOURCE :
return obj - > subID = = resID ;
break ;
case Obj : : MINE :
return ( obj - > subID = = resID & &
( cb - > getPlayerRelations ( obj - > tempOwner , ai - > playerID ) = = PlayerRelations : : ENEMIES ) ) ; //don't capture our mines
break ;
case Obj : : CAMPFIRE :
return true ; //contains all resources
break ;
case Obj : : WINDMILL :
switch ( resID )
{
case Res : : GOLD :
case Res : : WOOD :
return false ;
}
break ;
case Obj : : WATER_WHEEL :
if ( resID ! = Res : : GOLD )
return false ;
break ;
case Obj : : MYSTICAL_GARDEN :
if ( ( resID ! = Res : : GOLD ) & & ( resID ! = Res : : GEMS ) )
return false ;
break ;
case Obj : : LEAN_TO :
case Obj : : WAGON :
if ( resID ! = Res : : GOLD )
return false ;
break ;
default :
return false ;
break ;
}
return ! vstd : : contains ( ai - > alreadyVisited , obj ) ; //for weekly / once visitable
} ;
std : : vector < const CGObjectInstance * > objs ;
for ( auto obj : ai - > visitableObjs )
{
if ( givesResource ( obj ) )
objs . push_back ( obj ) ;
}
for ( auto h : cb - > getHeroesInfo ( ) )
{
auto sm = ai - > getCachedSectorMap ( h ) ;
std : : vector < const CGObjectInstance * > ourObjs ( objs ) ; //copy common objects
for ( auto obj : ai - > reservedHeroesMap [ h ] ) //add objects reserved by this hero
{
if ( givesResource ( obj ) )
ourObjs . push_back ( obj ) ;
}
for ( auto obj : ourObjs )
{
int3 dest = obj - > visitablePos ( ) ;
auto t = sm - > firstTileToGet ( h , dest ) ; //we assume that no more than one tile on the way is guarded
if ( t . valid ( ) ) //we know any path at all
{
if ( ai - > isTileNotReserved ( h , t ) ) //no other hero wants to conquer that tile
{
if ( isSafeToVisit ( h , dest ) )
{
if ( dest ! = t ) //there is something blocking our way
ret . push_back ( sptr ( Goals : : ClearWayTo ( dest , h ) . setisAbstract ( true ) ) ) ;
else
ret . push_back ( sptr ( Goals : : VisitTile ( dest ) . sethero ( h ) . setisAbstract ( true ) ) ) ;
}
else //we need to get army in order to pick that object
ret . push_back ( sptr ( Goals : : GatherArmy ( evaluateDanger ( dest , h ) * SAFE_ATTACK_CONSTANT ) . sethero ( h ) . setisAbstract ( true ) ) ) ;
}
}
}
}
return ret ;
2013-10-18 23:17:25 +03:00
}
TSubgoal CollectRes : : whatToDoToAchieve ( )
2018-07-29 21:07:40 +02:00
{
2018-07-26 12:06:55 +02:00
auto goals = getAllPossibleSubgoals ( ) ;
auto trade = whatToDoToTrade ( ) ;
if ( ! trade - > invalid ( ) )
goals . push_back ( trade ) ;
if ( goals . empty ( ) )
return sptr ( Goals : : Explore ( ) ) ; //we can always do that
else
return fh - > chooseSolution ( goals ) ; //TODO: evaluate trading
}
TSubgoal Goals : : CollectRes : : whatToDoToTrade ( )
2013-10-18 23:17:25 +03:00
{
2018-04-07 10:44:14 +02:00
std : : vector < const IMarket * > markets ;
2013-10-18 23:17:25 +03:00
2018-04-07 10:44:14 +02:00
std : : vector < const CGObjectInstance * > visObjs ;
2018-02-10 20:52:23 +02:00
ai - > retrieveVisitableObjs ( visObjs , true ) ;
2018-07-26 12:06:55 +02:00
for ( const CGObjectInstance * obj : visObjs )
2013-10-18 23:17:25 +03:00
{
2018-07-26 12:06:55 +02:00
if ( const IMarket * m = IMarket : : castFrom ( obj , false ) )
2013-10-18 23:17:25 +03:00
{
2018-07-26 12:06:55 +02:00
if ( obj - > ID = = Obj : : TOWN & & obj - > tempOwner = = ai - > playerID & & m - > allowsTrade ( EMarketMode : : RESOURCE_RESOURCE ) )
2013-10-18 23:17:25 +03:00
markets . push_back ( m ) ;
2018-07-26 12:06:55 +02:00
else if ( obj - > ID = = Obj : : TRADING_POST )
2013-10-18 23:17:25 +03:00
markets . push_back ( m ) ;
}
}
2018-04-07 10:44:14 +02:00
boost : : sort ( markets , [ ] ( const IMarket * m1 , const IMarket * m2 ) - > bool
2013-10-18 23:17:25 +03:00
{
return m1 - > getMarketEfficiency ( ) < m2 - > getMarketEfficiency ( ) ;
} ) ;
2018-04-07 10:44:14 +02:00
markets . erase ( boost : : remove_if ( markets , [ ] ( const IMarket * market ) - > bool
2013-10-18 23:17:25 +03:00
{
2018-07-26 12:06:55 +02:00
if ( ! ( market - > o - > ID = = Obj : : TOWN & & market - > o - > tempOwner = = ai - > playerID ) )
2018-04-07 10:44:14 +02:00
{
2018-07-26 12:06:55 +02:00
if ( ! ai - > isAccessible ( market - > o - > visitablePos ( ) ) )
2018-04-07 10:44:14 +02:00
return true ;
}
return false ;
} ) , markets . end ( ) ) ;
2013-10-18 23:17:25 +03:00
2018-07-26 12:06:55 +02:00
if ( ! markets . size ( ) )
2013-10-18 23:17:25 +03:00
{
2018-07-26 12:06:55 +02:00
for ( const CGTownInstance * t : cb - > getTownsInfo ( ) )
2013-10-18 23:17:25 +03:00
{
2018-07-26 12:06:55 +02:00
if ( cb - > canBuildStructure ( t , BuildingID : : MARKETPLACE ) = = EBuildingState : : ALLOWED )
return sptr ( Goals : : BuildThis ( BuildingID : : MARKETPLACE , t ) . setpriority ( 2 ) ) ;
2013-10-18 23:17:25 +03:00
}
}
else
{
2018-04-07 10:44:14 +02:00
const IMarket * m = markets . back ( ) ;
2013-10-18 23:17:25 +03:00
//attempt trade at back (best prices)
int howManyCanWeBuy = 0 ;
2018-07-26 12:06:55 +02:00
for ( Res : : ERes i = Res : : WOOD ; i < = Res : : GOLD ; vstd : : advance ( i , 1 ) )
2013-10-18 23:17:25 +03:00
{
2018-07-26 12:06:55 +02:00
if ( i = = resID )
2018-04-07 10:44:14 +02:00
continue ;
2013-10-18 23:17:25 +03:00
int toGive = - 1 , toReceive = - 1 ;
m - > getOffer ( i , resID , toGive , toReceive , EMarketMode : : RESOURCE_RESOURCE ) ;
assert ( toGive > 0 & & toReceive > 0 ) ;
2018-08-04 11:51:19 +02:00
howManyCanWeBuy + = toReceive * ( ah - > freeResources ( ) [ i ] / toGive ) ;
2013-10-18 23:17:25 +03:00
}
2018-07-26 12:06:55 +02:00
if ( howManyCanWeBuy > = value )
2013-10-18 23:17:25 +03:00
{
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 ) ;
2018-07-26 12:06:55 +02:00
auto objid = m - > o - > id . getNum ( ) ;
2018-07-28 09:08:53 +02:00
if ( backObj - > tempOwner ! = ai - > playerID ) //top object not owned
2013-11-15 20:30:16 +03:00
{
2018-07-28 09:08:53 +02:00
return sptr ( Goals : : GetObj ( objid ) ) ; //just go there
2013-11-15 20:30:16 +03:00
}
2018-07-28 09:08:53 +02:00
else //either it's our town, or we have hero there
2013-11-15 20:30:16 +03:00
{
2018-08-04 10:20:40 +02:00
Goals : : Trade trade ( resID , value , objid ) ;
return sptr ( trade . setisElementar ( true ) ) ; //we can do this immediately - highest priority
2013-11-15 20:30:16 +03:00
}
2013-10-18 23:17:25 +03:00
}
}
2018-07-26 12:06:55 +02:00
return sptr ( Goals : : Invalid ( ) ) ; //cannot trade
}
bool CollectRes : : fulfillsMe ( TSubgoal goal )
{
if ( goal - > resID = = resID )
if ( goal - > value > = value )
return true ;
return false ;
2013-10-18 23:17:25 +03:00
}
2018-08-05 09:21:41 +02:00
bool Goals : : CollectRes : : operator = = ( AbstractGoal & g )
2018-08-04 09:24:43 +02:00
{
2018-08-05 09:21:41 +02:00
if ( g . goalType ! = goalType )
return false ;
2018-08-04 09:24:43 +02:00
if ( g . resID = = resID )
if ( g . value = = value ) //TODO: not sure if that logic is consitent
return true ;
return false ;
}
2013-10-18 23:17:25 +03:00
TSubgoal GatherTroops : : whatToDoToAchieve ( )
{
std : : vector < const CGDwelling * > dwellings ;
2018-04-07 10:44:14 +02:00
for ( const CGTownInstance * t : cb - > getTownsInfo ( ) )
2013-10-18 23:17:25 +03:00
{
auto creature = VLC - > creh - > creatures [ objid ] ;
2018-04-07 10:44:14 +02:00
if ( t - > subID = = creature - > faction ) //TODO: how to force AI to build unupgraded creatures? :O
2013-10-18 23:17:25 +03:00
{
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 ) ;
2018-04-07 10:44:14 +02:00
if ( t - > hasBuilt ( bid ) ) //this assumes only creatures with dwellings are assigned to faction
2013-10-18 23:17:25 +03:00
{
dwellings . push_back ( t ) ;
}
else
{
2018-07-26 12:06:55 +02:00
return sptr ( Goals : : BuildThis ( bid , t ) . setpriority ( priority ) ) ;
2013-10-18 23:17:25 +03:00
}
}
}
2018-04-07 10:44:14 +02:00
for ( auto obj : ai - > visitableObjs )
2013-10-18 23:17:25 +03:00
{
2018-04-07 10:44:14 +02:00
if ( obj - > ID ! = Obj : : CREATURE_GENERATOR1 ) //TODO: what with other creature generators?
2013-10-18 23:17:25 +03:00
continue ;
auto d = dynamic_cast < const CGDwelling * > ( obj ) ;
2018-04-07 10:44:14 +02:00
for ( auto creature : d - > creatures )
2013-10-18 23:17:25 +03:00
{
2018-04-07 10:44:14 +02:00
if ( creature . first ) //there are more than 0 creatures avaliabe
2013-10-18 23:17:25 +03:00
{
2018-04-07 10:44:14 +02:00
for ( auto type : creature . second )
2013-10-18 23:17:25 +03:00
{
2018-07-26 12:06:55 +02:00
if ( type = = objid & & ah - > freeResources ( ) . canAfford ( VLC - > creh - > creatures [ type ] - > cost ) )
2013-10-18 23:17:25 +03:00
dwellings . push_back ( d ) ;
}
}
}
}
2018-04-07 10:44:14 +02:00
if ( dwellings . size ( ) )
2013-10-18 23:17:25 +03:00
{
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
{
2018-04-07 10:44:14 +02:00
const CGPathNode * ln = ai - > myCb - > getPathsInfo ( a . first ) - > getPathInfo ( a . second - > visitablePos ( ) ) ;
const CGPathNode * rn = ai - > myCb - > getPathsInfo ( b . first ) - > getPathInfo ( b . second - > visitablePos ( ) ) ;
2014-09-21 18:08:47 +03:00
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 ;
2018-04-07 10:44:14 +02:00
for ( const CGHeroInstance * hero : cb - > getHeroesInfo ( true ) )
2014-09-21 18:08:47 +03:00
{
nearestDwellings [ hero ] = * boost : : range : : min_element ( dwellings , CDistanceSorter ( hero ) ) ;
}
2018-04-07 10:44:14 +02:00
if ( nearestDwellings . size ( ) )
2016-11-29 23:07:35 +02:00
{
// find hero who is nearest to a dwelling
const CGDwelling * nearest = boost : : range : : min_element ( nearestDwellings , comparator ) - > second ;
2018-04-07 10:44:14 +02:00
if ( ! nearest )
2016-11-29 23:07:35 +02:00
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
2018-04-07 10:44:14 +02:00
{
return sptr ( Goals : : Explore ( ) ) ;
}
2013-10-18 23:17:25 +03:00
//TODO: exchange troops between heroes
}
2018-07-26 12:06:55 +02:00
bool Goals : : GatherTroops : : fulfillsMe ( TSubgoal goal )
{
if ( ! hero | | hero = = goal - > hero ) //we got army for desired hero or any hero
if ( goal - > objid = = objid ) //same creature type //TODO: consider upgrades?
if ( goal - > value > = value ) //notify every time we get resources?
return true ;
return false ;
}
2013-10-18 23:17:25 +03:00
TSubgoal Conquer : : whatToDoToAchieve ( )
{
2018-04-07 10:44:14 +02:00
return fh - > chooseSolution ( getAllPossibleSubgoals ( ) ) ;
2013-12-20 16:01:44 +03:00
}
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
{
2018-04-07 10:44:14 +02:00
if ( cb - > getPlayerRelations ( ai - > playerID , obj - > tempOwner ) = = PlayerRelations : : ENEMIES )
2014-02-15 19:38:51 +03:00
{
2018-04-07 10:44:14 +02:00
switch ( obj - > ID . num )
2014-02-15 19:38:51 +03:00
{
2018-04-07 10:44:14 +02:00
case Obj : : TOWN :
case Obj : : HERO :
case Obj : : CREATURE_GENERATOR1 :
case Obj : : MINE : //TODO: check ai->knownSubterraneanGates
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 ) )
2018-04-07 10:44:14 +02: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
}
}
2018-04-07 10:44:14 +02: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
2018-04-07 10:44:14 +02:00
if ( ret . empty ( ) )
ret . push_back ( sptr ( Goals : : Explore ( ) ) ) ; //we need to find an enemy
2013-12-20 16:01:44 +03:00
return ret ;
2013-10-18 23:17:25 +03:00
}
2013-12-20 16:01:44 +03:00
2018-08-05 09:21:41 +02:00
TGoalVec Goals : : Build : : getAllPossibleSubgoals ( )
{
TGoalVec ret ;
for ( const CGTownInstance * t : cb - > getTownsInfo ( ) )
{
//start fresh with every town
ah - > getBuildingOptions ( t ) ;
auto ib = ah - > immediateBuilding ( ) ;
if ( ib . is_initialized ( ) )
{
2018-08-06 17:42:58 +02:00
ret . push_back ( sptr ( Goals : : BuildThis ( ib . get ( ) . bid , t ) . setpriority ( 2 ) ) ) ; //prioritize buildings we can build quick
2018-08-05 09:21:41 +02:00
}
else //try build later
{
auto eb = ah - > expensiveBuilding ( ) ;
if ( eb . is_initialized ( ) )
{
auto pb = eb . get ( ) ; //gather resources for any we can't afford
2018-08-06 17:42:58 +02:00
auto goal = ah - > whatToDo ( pb . price , sptr ( Goals : : BuildThis ( pb . bid , t ) . setpriority ( 0.5 ) ) ) ;
2018-08-05 09:21:41 +02:00
ret . push_back ( goal ) ;
}
}
}
if ( ret . empty ( ) )
throw cannotFulfillGoalException ( " BUILD has been realized as much as possible. " ) ; //who catches it and what for?
else
return ret ;
}
2013-10-18 23:17:25 +03:00
TSubgoal Build : : whatToDoToAchieve ( )
{
2018-08-05 09:21:41 +02:00
return fh - > chooseSolution ( getAllPossibleSubgoals ( ) ) ;
2013-10-18 23:17:25 +03:00
}
2018-07-26 12:06:55 +02:00
bool Goals : : Build : : fulfillsMe ( TSubgoal goal )
{
if ( goal - > goalType = = Goals : : BUILD | | goal - > goalType = = Goals : : BUILD_STRUCTURE )
return ( ! town | | town = = goal - > town ) ; //building anything will do, in this town if set
else
return false ;
}
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
2018-04-07 10:44:14 +02:00
return fh - > chooseSolution ( getAllPossibleSubgoals ( ) ) ; //find dwelling. use current hero to prevent him from doing nothing.
2013-12-23 23:46:01 +03:00
}
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
2018-04-07 10:44:14 +02:00
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 ( ) ;
2018-04-07 10:44:14 +02:00
if ( ai - > isAccessibleForHero ( pos , hero ) )
2013-10-18 23:17:25 +03:00
{
2018-07-26 12:06:55 +02:00
//grab army from town
2018-04-07 10:44:14 +02:00
if ( ! t - > visitingHero & & howManyReinforcementsCanGet ( hero , t ) )
2013-12-23 23:46:01 +03:00
{
2018-04-07 10:44:14 +02:00
if ( ! vstd : : contains ( ai - > townVisitsThisWeek [ hero ] , t ) )
ret . push_back ( sptr ( Goals : : VisitTile ( pos ) . sethero ( hero ) ) ) ;
2013-12-23 23:46:01 +03:00
}
2018-07-26 12:06:55 +02:00
//buy army in town
if ( ! t - > visitingHero | | t - > visitingHero ! = hero . get ( true ) )
{
ui32 val = std : : min < ui32 > ( value , howManyReinforcementsCanBuy ( hero , t ) ) ;
if ( val )
2018-07-26 13:29:46 +02:00
{
auto goal = sptr ( Goals : : BuyArmy ( t , val ) . sethero ( hero ) ) ;
if ( ! ah - > containsObjective ( goal ) ) //avoid loops caused by reserving same objective twice
ret . push_back ( goal ) ;
}
2018-07-26 12:06:55 +02:00
}
//build dwelling
2018-07-29 18:27:21 +02:00
auto bid = ah - > canBuildAnyStructure ( t , std : : vector < BuildingID > ( unitsSource , unitsSource + ARRAY_COUNT ( unitsSource ) ) , 8 - cb - > getDate ( Date : : DAY_OF_WEEK ) ) ;
if ( bid . is_initialized ( ) )
2018-07-26 12:06:55 +02:00
{
2018-07-29 18:27:21 +02:00
auto goal = sptr ( BuildThis ( bid . get ( ) , t ) . setpriority ( priority ) ) ;
2018-07-26 13:29:46 +02:00
if ( ! ah - > containsObjective ( goal ) ) //avoid loops caused by reserving same objective twice
ret . push_back ( goal ) ;
2018-07-26 12:06:55 +02:00
}
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
{
2018-04-07 10:44:14 +02:00
if ( h = = heroDummy . h )
return true ;
else if ( ! ai - > isAccessibleForHero ( heroDummy - > visitablePos ( ) , h , true ) )
return true ;
else if ( ! ai - > canGetArmy ( heroDummy . h , h ) )
return true ;
else if ( ai - > getGoal ( h ) - > goalType = = Goals : : GATHER_ARMY )
return true ;
else
return false ;
2013-12-23 23:46:01 +03:00
} ) ;
2018-04-07 10:44:14 +02:00
for ( auto h : otherHeroes )
2013-10-18 23:17:25 +03:00
{
2018-04-07 10:44:14 +02:00
// Go to the other hero if we are faster
2018-08-05 09:21:41 +02:00
if ( ! vstd : : contains ( ai - > visitedHeroes [ hero ] , h ) ) //visit only once each turn //FIXME: this is only bug workaround
ret . push_back ( sptr ( Goals : : VisitHero ( h - > id . getNum ( ) ) . setisAbstract ( true ) . sethero ( hero ) ) ) ;
2018-04-07 10:44:14 +02:00
// Let the other hero come to us
2018-08-05 09:21:41 +02:00
if ( ! vstd : : contains ( ai - > visitedHeroes [ h ] , hero ) )
ret . push_back ( sptr ( Goals : : VisitHero ( hero - > id . getNum ( ) ) . setisAbstract ( true ) . sethero ( h ) ) ) ;
2013-12-23 23:46:01 +03:00
}
2013-10-18 23:17:25 +03:00
2018-04-07 10:44:14 +02:00
std : : vector < const CGObjectInstance * > objs ;
for ( auto obj : ai - > visitableObjs )
2013-12-23 23:46:01 +03:00
{
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 )
{
2018-04-07 10:44:14 +02:00
auto dwelling = dynamic_cast < const CGDwelling * > ( obj ) ;
2013-10-18 23:17:25 +03:00
for ( auto & creLevel : dwelling - > creatures )
{
if ( creLevel . first )
{
for ( auto & creatureID : creLevel . second )
{
auto creature = VLC - > creh - > creatures [ creatureID ] ;
2018-07-26 12:06:55 +02:00
if ( ah - > freeResources ( ) . canAfford ( creature - > cost ) )
2013-12-23 23:46:01 +03:00
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 ) ;
2018-04-07 10:44:14 +02:00
for ( auto obj : objs )
{
//find safe dwelling
2013-12-23 23:46:01 +03:00
auto pos = obj - > visitablePos ( ) ;
2018-04-07 10:44:14 +02:00
if ( ai - > isGoodForVisit ( obj , h , * sm ) )
ret . push_back ( sptr ( Goals : : VisitTile ( pos ) . sethero ( h ) ) ) ;
2013-12-23 23:46:01 +03:00
}
}
2016-11-26 19:03:09 +02:00
2018-07-26 12:06:55 +02:00
if ( ai - > canRecruitAnyHero ( ) & & ah - > freeGold ( ) > 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
{
2018-04-07 10:44:14 +02:00
for ( auto h : cb - > getAvailableHeroes ( t ) ) //we assume that all towns have same set of heroes
2016-11-26 19:03:09 +02:00
{
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
2018-04-07 10:44:14 +02:00
if ( ret . empty ( ) )
2014-02-17 10:36:03 +03:00
{
2018-04-07 10:44:14 +02:00
if ( hero = = ai - > primaryHero ( ) | | value > = 1.1f ) // FIXME: check PR388
ret . push_back ( sptr ( Goals : : Explore ( ) ) ) ;
2014-02-17 10:36:03 +03:00
else //workaround to break loop - seemingly there are no ways to explore left
2018-04-07 10:44:14 +02:00
throw goalFulfilledException ( sptr ( Goals : : GatherArmy ( 0 ) . sethero ( hero ) ) ) ;
2014-02-17 10:36:03 +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-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
2018-04-07 10:44:14 +02:00
TSubgoal AbstractGoal : : goVisitOrLookFor ( const CGObjectInstance * obj )
2013-10-18 23:17:25 +03:00
{
if ( obj )
2018-04-07 10:44:14 +02:00
return sptr ( Goals : : GetObj ( obj - > id . getNum ( ) ) ) ;
2013-10-18 23:17:25 +03:00
else
2018-04-07 10:44:14 +02: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
{
2018-04-07 10:44:14 +02: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
2018-04-07 10:44:14 +02:00
void AbstractGoal : : accept ( VCAI * ai )
2013-11-23 21:16:25 +03:00
{
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 >
2018-04-07 10:44:14 +02:00
void CGoal < T > : : accept ( VCAI * ai )
2013-11-24 14:36:51 +03:00
{
2018-04-07 10:44:14 +02:00
ai - > tryRealize ( static_cast < T & > ( * this ) ) ; //casting enforces template instantiation
2013-11-24 14:36:51 +03:00
}
2013-11-24 18:30:17 +03:00
2018-04-07 10:44:14 +02:00
float AbstractGoal : : accept ( FuzzyHelper * f )
2013-12-19 23:21:21 +03:00
{
return f - > evaluate ( * this ) ;
}
template < typename T >
2018-04-07 10:44:14 +02:00
float CGoal < T > : : accept ( FuzzyHelper * f )
2013-12-19 23:21:21 +03:00
{
2018-04-07 10:44:14 +02:00
return f - > evaluate ( static_cast < T & > ( * this ) ) ; //casting enforces template instantiation
2013-12-19 23:21:21 +03:00
}