2017-07-13 10:26:03 +02:00
/*
* VCAI . 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
*
*/
2012-02-14 21:04:45 +03:00
# include "StdInc.h"
# include "VCAI.h"
2014-06-29 17:23:06 +03:00
# include "Fuzzy.h"
2012-02-20 00:03:43 +03:00
# include "../../lib/UnlockGuard.h"
2014-06-05 19:52:14 +03:00
# include "../../lib/mapObjects/MapObjects.h"
2012-09-29 13:59:43 +03:00
# include "../../lib/CConfigHandler.h"
2012-11-16 00:29:22 +03:00
# include "../../lib/CHeroHandler.h"
2014-06-25 17:11:07 +03:00
# include "../../lib/CModHandler.h"
2015-12-02 21:39:53 +02:00
# include "../../lib/CGameState.h"
2015-12-02 22:10:46 +02:00
# include "../../lib/NetPacks.h"
2016-09-10 02:32:40 +02:00
# include "../../lib/serializer/CTypeList.h"
# include "../../lib/serializer/BinarySerializer.h"
# include "../../lib/serializer/BinaryDeserializer.h"
2014-04-26 17:23:35 +03:00
2012-03-10 22:14:45 +03:00
extern FuzzyHelper * fh ;
2012-03-03 13:08:01 +03:00
2012-03-05 22:11:28 +03:00
class CGVisitableOPW ;
2012-03-04 13:43:46 +03:00
const double SAFE_ATTACK_CONSTANT = 1.5 ;
2013-10-18 23:17:25 +03:00
const int GOLD_RESERVE = 10000 ; //when buying creatures we want to keep at least this much gold (10000 so at least we'll be able to reach capitol)
2012-02-14 21:04:45 +03:00
//one thread may be turn of AI and another will be handling a side effect for AI2
boost : : thread_specific_ptr < CCallback > cb ;
boost : : thread_specific_ptr < VCAI > ai ;
2012-03-03 13:08:01 +03:00
2012-07-01 02:48:40 +03:00
//std::map<int, std::map<int, int> > HeroView::infosCount;
2012-02-14 21:04:45 +03:00
//helper RAII to manage global ai/cb ptrs
struct SetGlobalState
{
SetGlobalState ( VCAI * AI )
{
assert ( ! ai . get ( ) ) ;
assert ( ! cb . get ( ) ) ;
ai . reset ( AI ) ;
2013-06-22 17:47:20 +03:00
cb . reset ( AI - > myCb . get ( ) ) ;
2012-02-14 21:04:45 +03:00
}
~ SetGlobalState ( )
{
ai . release ( ) ;
cb . release ( ) ;
}
} ;
2012-04-17 15:46:21 +03:00
2012-02-14 21:04:45 +03:00
# define SET_GLOBAL_STATE(ai) SetGlobalState _hlpSetState(ai);
# define NET_EVENT_HANDLER SET_GLOBAL_STATE(this)
# define MAKING_TURN SET_GLOBAL_STATE(this)
2013-06-26 14:18:27 +03:00
void foreach_tile ( std : : vector < std : : vector < std : : vector < unsigned char > > > & vectors , std : : function < void ( unsigned char & in ) > foo )
2012-02-14 21:04:45 +03:00
{
2016-12-12 22:38:12 +02:00
for ( auto & vector : vectors )
for ( auto j = vector . begin ( ) ; j ! = vector . end ( ) ; j + + )
for ( auto & elem : * j )
2013-06-29 16:05:48 +03:00
foo ( elem ) ;
2012-02-14 21:04:45 +03:00
}
struct ObjInfo
{
int3 pos ;
std : : string name ;
ObjInfo ( ) { }
2013-11-07 15:48:41 +03:00
ObjInfo ( const CGObjectInstance * obj ) :
pos ( obj - > pos ) ,
2014-06-24 20:39:36 +03:00
name ( obj - > getObjectName ( ) )
2012-02-14 21:04:45 +03:00
{
}
} ;
std : : map < const CGObjectInstance * , ObjInfo > helperObjInfo ;
2018-01-13 10:43:26 +02:00
VCAI : : VCAI ( )
2012-03-03 13:08:01 +03:00
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2013-05-22 01:11:44 +03:00
makingTurn = nullptr ;
2015-03-08 16:47:58 +02:00
destinationTeleport = ObjectInstanceID ( ) ;
2015-12-04 00:54:25 +02:00
destinationTeleportPos = int3 ( - 1 ) ;
2012-02-14 21:04:45 +03:00
}
2018-01-13 10:43:26 +02:00
VCAI : : ~ VCAI ( )
2012-03-03 13:08:01 +03:00
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2016-10-22 16:22:00 +02:00
finish ( ) ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : availableCreaturesChanged ( const CGDwelling * town )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : heroMoved ( const TryMoveHero & details )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2013-04-21 15:11:13 +03:00
validateObject ( details . id ) ; //enemy hero may have left visible area
2016-09-14 02:44:35 +02:00
auto hero = cb - > getHero ( details . id ) ;
2015-10-26 17:38:17 +02:00
cachedSectorMaps . clear ( ) ;
2013-04-21 15:11:13 +03:00
2016-09-14 02:44:35 +02:00
const int3 from = CGHeroInstance : : convertPosition ( details . start , false ) ,
to = CGHeroInstance : : convertPosition ( details . end , false ) ;
const CGObjectInstance * o1 = vstd : : frontOrNull ( cb - > getVisitableObjs ( from ) ) ,
* o2 = vstd : : frontOrNull ( cb - > getVisitableObjs ( to ) ) ;
2012-02-14 21:04:45 +03:00
if ( details . result = = TryMoveHero : : TELEPORTATION )
{
2015-03-08 16:47:58 +02:00
auto t1 = dynamic_cast < const CGTeleport * > ( o1 ) ;
auto t2 = dynamic_cast < const CGTeleport * > ( o2 ) ;
if ( t1 & & t2 )
2012-04-14 10:22:08 +03:00
{
2015-03-09 01:13:40 +02:00
if ( cb - > isTeleportChannelBidirectional ( t1 - > channel ) )
2015-03-08 16:47:58 +02:00
{
2015-03-09 01:27:49 +02:00
if ( o1 - > ID = = Obj : : SUBTERRANEAN_GATE & & o1 - > ID = = o2 - > ID ) // We need to only add subterranean gates in knownSubterraneanGates. Used for features not yet ported to use teleport channels
2015-03-08 16:47:58 +02:00
{
knownSubterraneanGates [ o1 ] = o2 ;
knownSubterraneanGates [ o2 ] = o1 ;
2017-08-11 19:03:05 +02:00
logAi - > debug ( " Found a pair of subterranean gates between %s and %s! " , from . toString ( ) , to . toString ( ) ) ;
2015-03-08 16:47:58 +02:00
}
}
2012-02-14 21:04:45 +03:00
}
2016-12-19 22:46:01 +02:00
//FIXME: teleports are not correctly visited
unreserveObject ( hero , t1 ) ;
unreserveObject ( hero , t2 ) ;
2012-02-14 21:04:45 +03:00
}
2016-09-14 02:44:35 +02:00
else if ( details . result = = TryMoveHero : : EMBARK & & hero )
{
//make sure AI not attempt to visit used boat
validateObject ( hero - > boat ) ;
}
else if ( details . result = = TryMoveHero : : DISEMBARK & & o1 )
{
auto boat = dynamic_cast < const CGBoat * > ( o1 ) ;
if ( boat )
addVisitableObj ( boat ) ;
}
2012-02-14 21:04:45 +03:00
}
void VCAI : : stackChagedCount ( const StackLocation & location , const TQuantity & change , bool isAbsolute )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE_PARAMS ( logAi , " isAbsolute '%i' " , isAbsolute ) ;
2013-04-14 21:52:05 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : heroInGarrisonChange ( const CGTownInstance * town )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : centerView ( int3 pos , int focusTime )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE_PARAMS ( logAi , " focusTime '%i' " , focusTime ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : artifactMoved ( const ArtifactLocation & src , const ArtifactLocation & dst )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : artifactAssembled ( const ArtifactLocation & al )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : showTavernWindow ( const CGObjectInstance * townOrTavern )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
2012-03-07 17:05:54 +03:00
void VCAI : : showThievesGuildWindow ( const CGObjectInstance * obj )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-07 17:05:54 +03:00
NET_EVENT_HANDLER ;
}
2013-09-28 02:46:58 +03:00
void VCAI : : playerBlocked ( int reason , bool start )
2012-02-14 21:04:45 +03:00
{
2013-09-28 02:46:58 +03:00
LOG_TRACE_PARAMS ( logAi , " reason '%i', start '%i' " , reason % start ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2013-09-28 02:46:58 +03:00
if ( start & & reason = = PlayerBlocked : : UPCOMING_BATTLE )
2012-02-14 21:04:45 +03:00
status . setBattle ( UPCOMING_BATTLE ) ;
2013-09-28 02:46:58 +03:00
if ( reason = = PlayerBlocked : : ONGOING_MOVEMENT )
status . setMove ( start ) ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : showPuzzleMap ( )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : showShipyardDialog ( const IShipyard * obj )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
2013-11-17 20:57:04 +03:00
void VCAI : : gameOver ( PlayerColor player , const EVictoryLossCheckResult & victoryLossCheckResult )
2012-02-14 21:04:45 +03:00
{
2014-06-26 02:18:59 +03:00
LOG_TRACE_PARAMS ( logAi , " victoryLossCheckResult '%s' " , victoryLossCheckResult . messageToSelf ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2016-09-16 04:45:00 +02:00
logAi - > debug ( " Player %d (%s): I heard that player %d (%s) %s. " , playerID , playerID . getStr ( ) , player , player . getStr ( ) , ( victoryLossCheckResult . victory ( ) ? " won " : " lost " ) ) ;
2012-02-14 21:04:45 +03:00
if ( player = = playerID )
{
2013-11-17 20:57:04 +03:00
if ( victoryLossCheckResult . victory ( ) )
2012-02-14 21:04:45 +03:00
{
2016-08-15 07:30:06 +02:00
logAi - > debug ( " VCAI: I won! Incredible! " ) ;
logAi - > debug ( " Turn nr %d " , myCb - > getDate ( ) ) ;
2012-02-14 21:04:45 +03:00
}
else
{
2016-09-16 04:45:00 +02:00
logAi - > debug ( " VCAI: Player %d (%s) lost. It's me. What a disappointment! :( " , player , player . getStr ( ) ) ;
2012-02-14 21:04:45 +03:00
}
2012-02-22 16:41:27 +03:00
finish ( ) ;
2012-02-14 21:04:45 +03:00
}
}
void VCAI : : artifactPut ( const ArtifactLocation & al )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : artifactRemoved ( const ArtifactLocation & al )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : stacksErased ( const StackLocation & location )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : artifactDisassembled ( const ArtifactLocation & al )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : heroVisit ( const CGHeroInstance * visitor , const CGObjectInstance * visitedObj , bool start )
{
2014-06-24 20:39:36 +03:00
LOG_TRACE_PARAMS ( logAi , " start '%i'; obj '%s' " , start % ( visitedObj ? visitedObj - > getObjectName ( ) : std : : string ( " n/a " ) ) ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2016-11-29 19:11:52 +02:00
if ( start & & visitedObj ) //we can end visit with null object, anyway
2012-03-05 22:11:28 +03:00
{
2012-03-14 16:02:38 +03:00
markObjectVisited ( visitedObj ) ;
2014-02-23 23:37:33 +03:00
unreserveObject ( visitor , visitedObj ) ;
2013-11-24 22:15:08 +03:00
completeGoal ( sptr ( Goals : : GetObj ( visitedObj - > id . getNum ( ) ) . sethero ( visitor ) ) ) ; //we don't need to visit it anymore
//TODO: what if we visited one-time visitable object that was reserved by another hero (shouldn't, but..)
2012-03-05 22:11:28 +03:00
}
2013-09-28 02:46:58 +03:00
status . heroVisit ( visitedObj , start ) ;
2012-02-14 21:04:45 +03:00
}
2017-07-15 13:08:20 +02:00
void VCAI : : availableArtifactsChanged ( const CGBlackMarket * bm )
2012-02-14 21:04:45 +03:00
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : heroVisitsTown ( const CGHeroInstance * hero , const CGTownInstance * town )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
//buildArmyIn(town);
//moveCreaturesToHero(town);
}
2013-06-29 16:05:48 +03:00
void VCAI : : tileHidden ( const std : : unordered_set < int3 , ShashInt3 > & pos )
2012-02-14 21:04:45 +03:00
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2013-04-21 15:11:13 +03:00
validateVisitableObjs ( ) ;
2016-08-18 02:51:00 +02:00
clearPathsInfo ( ) ;
2012-02-14 21:04:45 +03:00
}
2013-06-29 16:05:48 +03:00
void VCAI : : tileRevealed ( const std : : unordered_set < int3 , ShashInt3 > & pos )
2012-02-14 21:04:45 +03:00
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2013-06-29 16:05:48 +03:00
for ( int3 tile : pos )
for ( const CGObjectInstance * obj : myCb - > getVisitableObjs ( tile ) )
2012-02-14 21:04:45 +03:00
addVisitableObj ( obj ) ;
2014-03-23 19:00:43 +03:00
2015-10-26 17:38:17 +02:00
clearPathsInfo ( ) ;
2012-02-14 21:04:45 +03:00
}
2013-05-27 13:53:28 +03:00
void VCAI : : heroExchangeStarted ( ObjectInstanceID hero1 , ObjectInstanceID hero2 , QueryID query )
2012-02-14 21:04:45 +03:00
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-08-28 18:04:05 +03:00
auto firstHero = cb - > getHero ( hero1 ) ;
auto secondHero = cb - > getHero ( hero2 ) ;
2016-09-14 12:34:22 +02:00
status . addQuery ( query , boost : : str ( boost : : format ( " Exchange between heroes %s (%d) and %s (%d) " ) % firstHero - > name % firstHero - > tempOwner % secondHero - > name % secondHero - > tempOwner ) ) ;
2013-05-27 13:53:28 +03:00
2012-08-28 18:04:05 +03:00
requestActionASAP ( [ = ] ( )
{
2014-02-17 10:36:03 +03:00
float goalpriority1 = 0 , goalpriority2 = 0 ;
auto firstGoal = getGoal ( firstHero ) ;
if ( firstGoal - > goalType = = Goals : : GATHER_ARMY )
goalpriority1 = firstGoal - > priority ;
auto secondGoal = getGoal ( secondHero ) ;
if ( secondGoal - > goalType = = Goals : : GATHER_ARMY )
goalpriority2 = secondGoal - > priority ;
2015-04-07 22:48:35 +02:00
auto transferFrom2to1 = [ this ] ( const CGHeroInstance * h1 , const CGHeroInstance * h2 ) - > void
{
this - > pickBestCreatures ( h1 , h2 ) ;
this - > pickBestArtifacts ( h1 , h2 ) ;
} ;
2016-09-14 12:34:22 +02:00
//Do not attempt army or artifacts exchange if we visited ally player
//Visits can still be useful if hero have skills like Scholar
if ( firstHero - > tempOwner ! = secondHero - > tempOwner )
logAi - > debug ( " Heroes owned by different players. Do not exchange army or artifacts. " ) ;
else if ( goalpriority1 > goalpriority2 )
2015-04-07 22:48:35 +02:00
transferFrom2to1 ( firstHero , secondHero ) ;
2016-09-14 12:34:22 +02:00
else if ( goalpriority1 < goalpriority2 )
2015-04-07 22:48:35 +02:00
transferFrom2to1 ( secondHero , firstHero ) ;
2014-02-17 10:36:03 +03:00
else //regular criteria
{
2015-04-07 08:43:10 +02:00
if ( firstHero - > getFightingStrength ( ) > secondHero - > getFightingStrength ( ) & & canGetArmy ( firstHero , secondHero ) )
2015-04-07 22:48:35 +02:00
transferFrom2to1 ( firstHero , secondHero ) ;
2015-04-07 08:43:10 +02:00
else if ( canGetArmy ( secondHero , firstHero ) )
2015-04-07 22:48:35 +02:00
transferFrom2to1 ( secondHero , firstHero ) ;
2015-04-07 08:43:10 +02:00
}
2012-08-28 18:04:05 +03:00
2015-04-07 08:43:10 +02:00
completeGoal ( sptr ( Goals : : VisitHero ( firstHero - > id . getNum ( ) ) ) ) ; //TODO: what if we were visited by other hero in the meantime?
completeGoal ( sptr ( Goals : : VisitHero ( secondHero - > id . getNum ( ) ) ) ) ;
2013-05-27 13:53:28 +03:00
answerQuery ( query , 0 ) ;
2012-08-28 18:04:05 +03:00
} ) ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : heroPrimarySkillChanged ( const CGHeroInstance * hero , int which , si64 val )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE_PARAMS ( logAi , " which '%i', val '%i' " , which % val ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : showRecruitmentDialog ( const CGDwelling * dwelling , const CArmedInstance * dst , int level )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE_PARAMS ( logAi , " level '%i' " , level ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : heroMovePointsChanged ( const CGHeroInstance * hero )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : stackChangedType ( const StackLocation & location , const CCreature & newType )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : stacksRebalanced ( const StackLocation & src , const StackLocation & dst , TQuantity count )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : newObject ( const CGObjectInstance * obj )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
if ( obj - > isVisitable ( ) )
addVisitableObj ( obj ) ;
2014-03-23 19:00:43 +03:00
2015-10-26 17:38:17 +02:00
cachedSectorMaps . clear ( ) ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : objectRemoved ( const CGObjectInstance * obj )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-07-01 02:48:40 +03:00
2015-12-29 01:14:08 +02:00
vstd : : erase_if_present ( visitableObjs , obj ) ;
vstd : : erase_if_present ( alreadyVisited , obj ) ;
2013-05-09 14:09:23 +03:00
2014-03-30 00:39:19 +03:00
for ( auto h : cb - > getHeroesInfo ( ) )
unreserveObject ( h , obj ) ;
2012-04-17 15:46:21 +03:00
2016-09-04 14:44:42 +02:00
//TODO: Find better way to handle hero boat removal
if ( auto hero = dynamic_cast < const CGHeroInstance * > ( obj ) )
{
if ( hero - > boat )
{
vstd : : erase_if_present ( visitableObjs , hero - > boat ) ;
vstd : : erase_if_present ( alreadyVisited , hero - > boat ) ;
2016-09-14 02:44:35 +02:00
for ( auto h : cb - > getHeroesInfo ( ) )
unreserveObject ( h , hero - > boat ) ;
2016-09-04 14:44:42 +02:00
}
}
2015-10-26 17:38:17 +02:00
cachedSectorMaps . clear ( ) ; //invalidate all paths
2012-04-17 15:46:21 +03:00
//TODO
//there are other places where CGObjectinstance ptrs are stored...
2012-07-19 21:52:44 +03:00
//
2012-07-01 02:48:40 +03:00
2012-09-23 21:01:04 +03:00
if ( obj - > ID = = Obj : : HERO & & obj - > tempOwner = = playerID )
2012-07-01 02:48:40 +03:00
{
2013-04-21 15:11:13 +03:00
lostHero ( cb - > getHero ( obj - > id ) ) ; //we can promote, since objectRemoved is called just before actual deletion
2012-07-01 02:48:40 +03:00
}
2012-02-14 21:04:45 +03:00
}
void VCAI : : showHillFortWindow ( const CGObjectInstance * object , const CGHeroInstance * visitor )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-08-29 12:19:20 +03:00
requestActionASAP ( [ = ] ( )
{
makePossibleUpgrades ( visitor ) ;
} ) ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : playerBonusChanged ( const Bonus & bonus , bool gain )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE_PARAMS ( logAi , " gain '%i' " , gain ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : newStackInserted ( const StackLocation & location , const CStackInstance & stack )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
2013-12-23 23:46:01 +03:00
void VCAI : : heroCreated ( const CGHeroInstance * h )
2012-02-14 21:04:45 +03:00
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2013-12-23 23:46:01 +03:00
if ( h - > visitedTown )
2013-12-25 16:38:20 +03:00
townVisitsThisWeek [ HeroPtr ( h ) ] . insert ( h - > visitedTown ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : advmapSpellCast ( const CGHeroInstance * caster , int spellID )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE_PARAMS ( logAi , " spellID '%i " , spellID ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : showInfoDialog ( const std : : string & text , const std : : vector < Component * > & components , int soundID )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE_PARAMS ( logAi , " soundID '%i' " , soundID ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : requestRealized ( PackageApplied * pa )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
if ( status . haveTurn ( ) )
{
if ( pa - > packType = = typeList . getTypeID < EndTurn > ( ) )
if ( pa - > result )
status . madeTurn ( ) ;
}
if ( pa - > packType = = typeList . getTypeID < QueryReply > ( ) )
{
2012-07-15 18:34:00 +03:00
status . receivedAnswerConfirmation ( pa - > requestID , pa - > result ) ;
2012-02-14 21:04:45 +03:00
}
}
2016-11-26 14:14:43 +02:00
void VCAI : : receivedResource ( )
2012-02-14 21:04:45 +03:00
{
2016-11-26 14:14:43 +02:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : stacksSwapped ( const StackLocation & loc1 , const StackLocation & loc2 )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : showUniversityWindow ( const IMarket * market , const CGHeroInstance * visitor )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : heroManaPointsChanged ( const CGHeroInstance * hero )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : heroSecondarySkillChanged ( const CGHeroInstance * hero , int which , int val )
{
2013-04-20 14:34:01 +03:00
LOG_TRACE_PARAMS ( logAi , " which '%d', val '%d' " , which % val ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : battleResultsApplied ( )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
assert ( status . getBattle ( ) = = ENDING_BATTLE ) ;
status . setBattle ( NO_BATTLE ) ;
}
void VCAI : : objectPropertyChanged ( const SetObjectProperty * sop )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
if ( sop - > what = = ObjProperty : : OWNER )
{
2016-09-13 22:39:32 +02:00
if ( myCb - > getPlayerRelations ( playerID , ( PlayerColor ) sop - > val ) = = PlayerRelations : : ENEMIES )
2015-01-24 22:38:22 +02:00
{
//we want to visit objects owned by oppponents
auto obj = myCb - > getObj ( sop - > id , false ) ;
if ( obj )
{
2016-09-13 22:39:32 +02:00
addVisitableObj ( obj ) ; // TODO: Remove once save compatability broken. In past owned objects were removed from this set
2015-12-29 01:14:08 +02:00
vstd : : erase_if_present ( alreadyVisited , obj ) ;
2015-01-24 22:38:22 +02:00
}
}
2012-02-14 21:04:45 +03:00
}
}
2013-02-11 22:11:34 +03:00
void VCAI : : buildChanged ( const CGTownInstance * town , BuildingID buildingID , int what )
2012-02-14 21:04:45 +03:00
{
2013-04-16 16:16:58 +03:00
LOG_TRACE_PARAMS ( logAi , " what '%i' " , what ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : heroBonusChanged ( const CGHeroInstance * hero , const Bonus & bonus , bool gain )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE_PARAMS ( logAi , " gain '%i' " , gain ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : showMarketWindow ( const IMarket * market , const CGHeroInstance * visitor )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
}
2015-02-26 16:15:17 +02:00
void VCAI : : showWorldViewEx ( const std : : vector < ObjectPosInfo > & objectPositions )
{
//TODO: AI support for ViewXXX spell
LOG_TRACE ( logAi ) ;
NET_EVENT_HANDLER ;
}
2015-12-29 04:43:33 +02:00
void VCAI : : init ( std : : shared_ptr < CCallback > CB )
2012-02-14 21:04:45 +03:00
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-02-14 21:04:45 +03:00
myCb = CB ;
cbc = CB ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2013-02-11 17:42:09 +03:00
playerID = * myCb - > getMyColor ( ) ;
2012-02-14 21:04:45 +03:00
myCb - > waitTillRealize = true ;
2012-02-17 00:46:28 +03:00
myCb - > unlockGsWhenWaiting = true ;
2012-02-16 20:10:58 +03:00
2012-03-10 22:14:45 +03:00
if ( ! fh )
fh = new FuzzyHelper ( ) ;
2015-03-08 16:38:09 +02:00
retreiveVisitableObjs ( ) ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : yourTurn ( )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
status . startedTurn ( ) ;
2013-05-09 14:09:23 +03:00
makingTurn = make_unique < boost : : thread > ( & VCAI : : makeTurn , this ) ;
2012-02-14 21:04:45 +03:00
}
2013-05-27 13:53:28 +03:00
void VCAI : : heroGotLevel ( const CGHeroInstance * hero , PrimarySkill : : PrimarySkill pskill , std : : vector < SecondarySkill > & skills , QueryID queryID )
2012-02-14 21:04:45 +03:00
{
2013-04-16 16:16:58 +03:00
LOG_TRACE_PARAMS ( logAi , " queryID '%i' " , queryID ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-07-15 18:34:00 +03:00
status . addQuery ( queryID , boost : : str ( boost : : format ( " Hero %s got level %d " ) % hero - > name % hero - > level ) ) ;
2017-07-17 14:35:57 +02:00
requestActionASAP ( [ = ] ( ) { answerQuery ( queryID , 0 ) ; } ) ;
2012-02-14 21:04:45 +03:00
}
2013-05-27 13:53:28 +03:00
void VCAI : : commanderGotLevel ( const CCommanderInstance * commander , std : : vector < ui32 > skills , QueryID queryID )
2012-07-06 17:09:34 +03:00
{
2013-04-16 16:16:58 +03:00
LOG_TRACE_PARAMS ( logAi , " queryID '%i' " , queryID ) ;
2012-07-06 17:09:34 +03:00
NET_EVENT_HANDLER ;
2012-07-15 18:34:00 +03:00
status . addQuery ( queryID , boost : : str ( boost : : format ( " Commander %s of %s got level %d " ) % commander - > name % commander - > armyObj - > nodeName ( ) % ( int ) commander - > level ) ) ;
2017-07-17 14:35:57 +02:00
requestActionASAP ( [ = ] ( ) { answerQuery ( queryID , 0 ) ; } ) ;
2012-07-06 17:09:34 +03:00
}
2013-05-27 13:53:28 +03:00
void VCAI : : showBlockingDialog ( const std : : string & text , const std : : vector < Component > & components , QueryID askID , const int soundID , bool selection , bool cancel )
2012-02-14 21:04:45 +03:00
{
2013-04-16 16:16:58 +03:00
LOG_TRACE_PARAMS ( logAi , " text '%s', askID '%i', soundID '%i', selection '%i', cancel '%i' " , text % askID % soundID % selection % cancel ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
int sel = 0 ;
2012-07-19 21:52:44 +03:00
status . addQuery ( askID , boost : : str ( boost : : format ( " Blocking dialog query with %d components - %s " )
2012-07-15 18:34:00 +03:00
% components . size ( ) % text ) ) ;
2012-02-14 21:04:45 +03:00
if ( selection ) //select from multiple components -> take the last one (they're indexed [1-size])
sel = components . size ( ) ;
if ( ! selection & & cancel ) //yes&no -> always answer yes, we are a brave AI :)
sel = 1 ;
2012-03-26 14:38:51 +03:00
requestActionASAP ( [ = ] ( )
2012-03-26 01:46:14 +03:00
{
2012-07-15 18:34:00 +03:00
answerQuery ( askID , sel ) ;
2012-03-26 01:46:14 +03:00
} ) ;
2012-02-14 21:04:45 +03:00
}
2015-11-28 01:41:30 +02:00
void VCAI : : showTeleportDialog ( TeleportChannelID channel , TTeleportExitsList exits , bool impassable , QueryID askID )
2015-03-08 16:47:58 +02:00
{
2015-12-02 16:56:26 +02:00
// LOG_TRACE_PARAMS(logAi, "askID '%i', exits '%s'", askID % exits);
2015-03-08 16:47:58 +02:00
NET_EVENT_HANDLER ;
status . addQuery ( askID , boost : : str ( boost : : format ( " Teleport dialog query with %d exits " )
% exits . size ( ) ) ) ;
2015-12-02 16:56:26 +02:00
int choosenExit = - 1 ;
2015-03-08 16:47:58 +02:00
if ( impassable )
knownTeleportChannels [ channel ] - > passability = TeleportChannel : : IMPASSABLE ;
2015-12-03 16:20:03 +02:00
else if ( destinationTeleport ! = ObjectInstanceID ( ) & & destinationTeleportPos . valid ( ) )
2015-03-08 16:47:58 +02:00
{
2015-12-02 16:56:26 +02:00
auto neededExit = std : : make_pair ( destinationTeleport , destinationTeleportPos ) ;
if ( destinationTeleport ! = ObjectInstanceID ( ) & & vstd : : contains ( exits , neededExit ) )
choosenExit = vstd : : find_pos ( exits , neededExit ) ;
2015-12-03 16:20:03 +02:00
}
2015-03-08 16:47:58 +02:00
2015-12-03 16:20:03 +02:00
for ( auto exit : exits )
{
if ( status . channelProbing ( ) & & exit . first = = destinationTeleport )
2015-03-08 16:47:58 +02:00
{
2015-12-03 16:20:03 +02:00
choosenExit = vstd : : find_pos ( exits , exit ) ;
break ;
}
else
{
2015-12-03 20:18:40 +02:00
// TODO: Implement checking if visiting that teleport will uncovert any FoW
// So far this is the best option to handle decision about probing
auto obj = cb - > getObj ( exit . first , false ) ;
if ( obj = = nullptr & & ! vstd : : contains ( teleportChannelProbingList , exit . first ) & &
2015-12-03 16:20:03 +02:00
exit . first ! = destinationTeleport )
2015-03-08 16:47:58 +02:00
{
2015-12-03 16:20:03 +02:00
teleportChannelProbingList . push_back ( exit . first ) ;
2015-12-02 18:26:24 +02:00
}
2015-03-08 16:47:58 +02:00
}
}
requestActionASAP ( [ = ] ( )
{
2015-12-02 16:56:26 +02:00
answerQuery ( askID , choosenExit ) ;
2015-03-08 16:47:58 +02:00
} ) ;
}
2013-05-27 13:53:28 +03:00
void VCAI : : showGarrisonDialog ( const CArmedInstance * up , const CGHeroInstance * down , bool removableUnits , QueryID queryID )
2012-02-14 21:04:45 +03:00
{
2013-04-16 16:16:58 +03:00
LOG_TRACE_PARAMS ( logAi , " removableUnits '%i', queryID '%i' " , removableUnits % queryID ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2012-07-15 18:34:00 +03:00
std : : string s1 = up ? up - > nodeName ( ) : " NONE " ;
std : : string s2 = down ? down - > nodeName ( ) : " NONE " ;
status . addQuery ( queryID , boost : : str ( boost : : format ( " Garrison dialog with %s and %s " ) % s1 % s2 ) ) ;
2012-03-11 19:29:01 +03:00
//you can't request action from action-response thread
2012-04-08 13:34:23 +03:00
requestActionASAP ( [ = ] ( )
2012-03-26 01:46:14 +03:00
{
2016-09-19 02:30:55 +02:00
if ( removableUnits )
pickBestCreatures ( down , up ) ;
2012-07-15 18:34:00 +03:00
answerQuery ( queryID , 0 ) ;
2012-03-26 01:46:14 +03:00
} ) ;
2012-02-14 21:04:45 +03:00
}
2017-06-06 06:53:51 +02:00
void VCAI : : showMapObjectSelectDialog ( QueryID askID , const Component & icon , const MetaString & title , const MetaString & description , const std : : vector < ObjectInstanceID > & objects )
{
status . addQuery ( askID , " Map object select query " ) ;
2017-07-17 14:35:57 +02:00
requestActionASAP ( [ = ] ( ) { answerQuery ( askID , 0 ) ; } ) ;
2017-06-06 06:53:51 +02:00
//TODO: Town portal destination selection goes here
}
2016-09-10 02:32:40 +02:00
void VCAI : : saveGame ( BinarySerializer & h , const int version )
2012-02-14 21:04:45 +03:00
{
2013-04-16 16:16:58 +03:00
LOG_TRACE_PARAMS ( logAi , " version '%i' " , version ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2013-08-18 18:46:28 +03:00
validateVisitableObjs ( ) ;
2014-02-20 23:18:49 +03:00
registerGoals ( h ) ;
2013-05-09 14:09:23 +03:00
CAdventureAI : : saveGame ( h , version ) ;
serializeInternal ( h , version ) ;
2012-02-14 21:04:45 +03:00
}
2016-09-10 02:32:40 +02:00
void VCAI : : loadGame ( BinaryDeserializer & h , const int version )
2012-02-14 21:04:45 +03:00
{
2013-04-16 16:16:58 +03:00
LOG_TRACE_PARAMS ( logAi , " version '%i' " , version ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2014-02-20 23:18:49 +03:00
registerGoals ( h ) ;
2013-05-09 14:09:23 +03:00
CAdventureAI : : loadGame ( h , version ) ;
serializeInternal ( h , version ) ;
2012-02-14 21:04:45 +03:00
}
void makePossibleUpgrades ( const CArmedInstance * obj )
{
if ( ! obj )
return ;
for ( int i = 0 ; i < GameConstants : : ARMY_SIZE ; i + + )
{
2013-02-16 17:03:47 +03:00
if ( const CStackInstance * s = obj - > getStackPtr ( SlotID ( i ) ) )
2012-02-14 21:04:45 +03:00
{
UpgradeInfo ui ;
2013-02-16 17:03:47 +03:00
cb - > getUpgradeInfo ( obj , SlotID ( i ) , ui ) ;
2012-02-14 21:04:45 +03:00
if ( ui . oldID > = 0 & & cb - > getResourceAmount ( ) . canAfford ( ui . cost [ 0 ] * s - > count ) )
{
2013-02-16 17:03:47 +03:00
cb - > upgradeCreature ( obj , SlotID ( i ) , ui . newID [ 0 ] ) ;
2012-02-14 21:04:45 +03:00
}
}
}
}
void VCAI : : makeTurn ( )
{
2016-09-16 04:45:00 +02:00
logGlobal - > info ( " Player %d (%s) starting turn " , playerID , playerID . getStr ( ) ) ;
2015-08-31 09:18:24 +02:00
2012-02-14 21:04:45 +03:00
MAKING_TURN ;
2017-06-14 06:59:41 +02:00
boost : : shared_lock < boost : : shared_mutex > gsLock ( CGameState : : mutex ) ;
2012-06-27 23:44:01 +03:00
setThreadName ( " VCAI::makeTurn " ) ;
2012-02-14 21:04:45 +03:00
2013-02-02 11:29:57 +03:00
switch ( cb - > getDate ( Date : : DAY_OF_WEEK ) )
2012-02-14 21:04:45 +03:00
{
2012-03-13 15:47:47 +03:00
case 1 :
2012-02-14 21:04:45 +03:00
{
2012-03-13 15:47:47 +03:00
townVisitsThisWeek . clear ( ) ;
std : : vector < const CGObjectInstance * > objs ;
retreiveVisitableObjs ( objs , true ) ;
2013-06-29 16:05:48 +03:00
for ( const CGObjectInstance * obj : objs )
2012-03-13 15:47:47 +03:00
{
if ( isWeeklyRevisitable ( obj ) )
2012-07-19 21:52:44 +03:00
{
2015-03-08 16:38:09 +02:00
addVisitableObj ( obj ) ;
2015-12-29 01:14:08 +02:00
vstd : : erase_if_present ( alreadyVisited , obj ) ;
2012-03-13 15:47:47 +03:00
}
}
}
2012-03-13 23:33:00 +03:00
break ;
2012-02-14 21:04:45 +03:00
}
2014-03-23 19:00:43 +03:00
markHeroAbleToExplore ( primaryHero ( ) ) ;
2012-02-14 21:04:45 +03:00
makeTurnInternal ( ) ;
return ;
}
void VCAI : : makeTurnInternal ( )
{
saving = 0 ;
2012-03-11 04:26:11 +03:00
//it looks messy here, but it's better to have armed heroes before attempting realizing goals
2013-06-29 16:05:48 +03:00
for ( const CGTownInstance * t : cb - > getTownsInfo ( ) )
2012-03-11 04:26:11 +03:00
moveCreaturesToHero ( t ) ;
2012-02-14 21:04:45 +03:00
try
{
2012-05-08 11:10:40 +03:00
//Pick objects reserved in previous turn - we expect only nerby objects there
2012-09-28 22:50:09 +03:00
auto reservedHeroesCopy = reservedHeroesMap ; //work on copy => the map may be changed while iterating (eg because hero died when attempting a goal)
2013-06-29 16:05:48 +03:00
for ( auto hero : reservedHeroesCopy )
2012-05-08 11:10:40 +03:00
{
2013-09-28 15:31:06 +03:00
if ( reservedHeroesMap . count ( hero . first ) )
continue ; //hero might have been removed while we were in this loop
if ( ! hero . first . validAndSet ( ) )
{
2016-08-15 07:30:06 +02:00
logAi - > error ( " Hero %s present on reserved map. Shouldn't be. " , hero . first . name ) ;
2013-09-28 15:31:06 +03:00
continue ;
}
2013-12-25 16:38:20 +03:00
std : : vector < const CGObjectInstance * > vec ( hero . second . begin ( ) , hero . second . end ( ) ) ;
2014-09-21 16:42:08 +03:00
boost : : sort ( vec , CDistanceSorter ( hero . first . get ( ) ) ) ;
2013-12-25 16:38:20 +03:00
for ( auto obj : vec )
2012-05-08 11:10:40 +03:00
{
2014-01-03 02:48:38 +03:00
if ( ! obj | | ! cb - > getObj ( obj - > id ) )
2013-08-18 18:46:28 +03:00
{
2016-08-15 07:30:06 +02:00
logAi - > error ( " Error: there is wrong object on list for hero %s " , hero . first - > name ) ;
2013-08-18 18:46:28 +03:00
continue ;
}
2013-11-23 15:30:10 +03:00
striveToGoal ( sptr ( Goals : : VisitTile ( obj - > visitablePos ( ) ) . sethero ( hero . first ) ) ) ;
2012-05-08 11:10:40 +03:00
}
}
//now try to win
2013-11-23 15:30:10 +03:00
striveToGoal ( sptr ( Goals : : Win ( ) ) ) ;
2012-05-05 09:42:18 +03:00
2012-07-01 02:48:40 +03:00
//finally, continue our abstract long-term goals
2013-12-26 12:53:37 +03:00
int oldMovement = 0 ;
int newMovement = 0 ;
while ( true )
2012-03-13 15:47:47 +03:00
{
2013-12-26 12:53:37 +03:00
oldMovement = newMovement ; //remember old value
newMovement = 0 ;
std : : vector < std : : pair < HeroPtr , Goals : : TSubgoal > > safeCopy ;
for ( auto mission : lockedHeroes )
{
2013-12-27 16:20:40 +03:00
fh - > setPriority ( mission . second ) ; //re-evaluate
2013-12-26 12:53:37 +03:00
if ( canAct ( mission . first ) )
{
newMovement + = mission . first - > movement ;
safeCopy . push_back ( mission ) ;
}
}
if ( newMovement = = oldMovement ) //means our heroes didn't move or didn't re-assign their goals
2012-05-05 11:57:32 +03:00
{
2016-08-15 07:30:06 +02:00
logAi - > warn ( " Our heroes don't move anymore, exhaustive decomposition failed " ) ;
2013-12-26 12:53:37 +03:00
break ;
2014-01-30 14:08:37 +03:00
}
2013-12-26 12:53:37 +03:00
if ( safeCopy . empty ( ) )
break ; //all heroes exhausted their locked goals
else
{
typedef std : : pair < HeroPtr , Goals : : TSubgoal > TItrType ;
auto lockedHeroesSorter = [ ] ( TItrType m1 , TItrType m2 ) - > bool
{
return m1 . second - > priority < m2 . second - > priority ;
} ;
boost : : sort ( safeCopy , lockedHeroesSorter ) ;
striveToGoal ( safeCopy . back ( ) . second ) ;
2012-05-05 11:57:32 +03:00
}
2012-03-13 15:47:47 +03:00
}
2012-05-05 09:42:18 +03:00
2012-07-18 13:10:14 +03:00
auto quests = myCb - > getMyQuests ( ) ;
2013-06-29 16:05:48 +03:00
for ( auto quest : quests )
2012-07-18 13:10:14 +03:00
{
2012-07-19 21:52:44 +03:00
striveToQuest ( quest ) ;
2012-07-18 13:10:14 +03:00
}
2013-11-23 15:30:10 +03:00
striveToGoal ( sptr ( Goals : : Build ( ) ) ) ; //TODO: smarter building management
2013-12-26 12:53:37 +03:00
performTypicalActions ( ) ;
//for debug purpose
for ( auto h : cb - > getHeroesInfo ( ) )
{
if ( h - > movement )
2016-08-15 07:30:06 +02:00
logAi - > warn ( " Hero %s has %d MP left " , h - > name , h - > movement ) ;
2015-10-31 17:04:06 +02:00
}
2012-02-14 21:04:45 +03:00
}
catch ( boost : : thread_interrupted & e )
{
2016-08-15 07:30:06 +02:00
logAi - > debug ( " Making turn thread has been interrupted. We'll end without calling endTurn. " ) ;
2012-02-14 21:04:45 +03:00
return ;
}
catch ( std : : exception & e )
{
2016-08-15 07:30:06 +02:00
logAi - > debug ( " Making turn thread has caught an exception: %s " , e . what ( ) ) ;
2012-02-14 21:04:45 +03:00
}
endTurn ( ) ;
}
2012-07-01 02:48:40 +03:00
bool VCAI : : goVisitObj ( const CGObjectInstance * obj , HeroPtr h )
2012-02-14 21:04:45 +03:00
{
int3 dst = obj - > visitablePos ( ) ;
2015-12-04 00:10:51 +02:00
auto sm = getCachedSectorMap ( h ) ;
2017-08-11 19:03:05 +02:00
logAi - > debug ( " %s will try to visit %s at (%s) " , h - > name , obj - > getObjectName ( ) , dst . toString ( ) ) ;
2015-12-04 00:10:51 +02:00
int3 pos = sm - > firstTileToGet ( h , dst ) ;
2015-04-05 21:13:47 +02:00
if ( ! pos . valid ( ) ) //rare case when we are already standing on one of potential objects
return false ;
return moveHeroToTile ( pos , h ) ;
2012-02-14 21:04:45 +03:00
}
2012-07-01 02:48:40 +03:00
void VCAI : : performObjectInteraction ( const CGObjectInstance * obj , HeroPtr h )
2012-03-05 22:11:28 +03:00
{
2017-08-11 19:03:05 +02:00
LOG_TRACE_PARAMS ( logAi , " Hero %s and object %s at %s " , h - > name % obj - > getObjectName ( ) % obj - > pos . toString ( ) ) ;
2012-03-05 22:11:28 +03:00
switch ( obj - > ID )
{
case Obj : : CREATURE_GENERATOR1 :
2014-09-19 00:18:49 +03:00
recruitCreatures ( dynamic_cast < const CGDwelling * > ( obj ) , h . get ( ) ) ;
2012-07-18 13:10:14 +03:00
checkHeroArmy ( h ) ;
2012-05-22 13:15:16 +03:00
break ;
2012-09-23 21:01:04 +03:00
case Obj : : TOWN :
2012-05-22 13:15:16 +03:00
moveCreaturesToHero ( dynamic_cast < const CGTownInstance * > ( obj ) ) ;
2012-10-01 21:25:43 +03:00
if ( h - > visitedTown ) //we are inside, not just attacking
{
2013-12-25 16:38:20 +03:00
townVisitsThisWeek [ h ] . insert ( h - > visitedTown ) ;
2012-10-01 21:25:43 +03:00
if ( ! h - > hasSpellbook ( ) & & cb - > getResourceAmount ( Res : : GOLD ) > = GameConstants : : SPELLBOOK_GOLD_COST + saving [ Res : : GOLD ] & &
2013-02-11 02:24:57 +03:00
h - > visitedTown - > hasBuilt ( BuildingID : : MAGES_GUILD_1 ) )
2013-02-07 20:34:50 +03:00
cb - > buyArtifact ( h . get ( ) , ArtifactID : : SPELLBOOK ) ;
2012-10-01 21:25:43 +03:00
}
2012-05-22 13:15:16 +03:00
break ;
2012-03-05 22:11:28 +03:00
}
2013-12-22 00:31:28 +03:00
completeGoal ( sptr ( Goals : : GetObj ( obj - > id . getNum ( ) ) . sethero ( h ) ) ) ;
2012-03-05 22:11:28 +03:00
}
2012-02-14 21:04:45 +03:00
void VCAI : : moveCreaturesToHero ( const CGTownInstance * t )
{
2013-08-18 18:46:28 +03:00
if ( t - > visitingHero & & t - > armedGarrison ( ) & & t - > visitingHero - > tempOwner = = t - > tempOwner )
2012-02-14 21:04:45 +03:00
{
2012-03-06 21:49:23 +03:00
pickBestCreatures ( t - > visitingHero , t ) ;
}
}
2012-08-29 18:23:47 +03:00
bool VCAI : : canGetArmy ( const CGHeroInstance * army , const CGHeroInstance * source )
{ //TODO: merge with pickBestCreatures
2014-02-19 19:23:47 +03:00
//if (ai->primaryHero().h == source)
2014-02-16 00:32:49 +03:00
2013-08-18 18:46:28 +03:00
if ( army - > tempOwner ! = source - > tempOwner )
{
2016-08-15 07:30:06 +02:00
logAi - > error ( " Why are we even considering exchange between heroes from different players? " ) ;
2013-08-18 18:46:28 +03:00
return false ;
}
2012-08-29 18:23:47 +03:00
const CArmedInstance * armies [ ] = { army , source } ;
2015-10-31 17:04:06 +02:00
2012-08-29 18:23:47 +03:00
//we calculate total strength for each creature type available in armies
std : : map < const CCreature * , int > creToPower ;
2013-06-29 16:05:48 +03:00
for ( auto armyPtr : armies )
for ( auto & i : armyPtr - > Slots ( ) )
2012-08-29 12:19:20 +03:00
{
2014-02-19 19:23:47 +03:00
//TODO: allow splitting stacks?
2012-08-29 18:23:47 +03:00
creToPower [ i . second - > type ] + = i . second - > getPower ( ) ;
2012-08-29 12:19:20 +03:00
}
2012-08-29 18:23:47 +03:00
//TODO - consider more than just power (ie morale penalty, hero specialty in certain stacks, etc)
2014-02-19 19:23:47 +03:00
int armySize = creToPower . size ( ) ;
2012-08-30 21:28:40 +03:00
armySize = std : : min ( ( source - > needsLastStack ( ) ? armySize - 1 : armySize ) , GameConstants : : ARMY_SIZE ) ; //can't move away last stack
2012-08-29 18:23:47 +03:00
std : : vector < const CCreature * > bestArmy ; //types that'll be in final dst army
for ( int i = 0 ; i < armySize ; i + + ) //pick the creatures from which we can get most power, as many as dest can fit
{
typedef const std : : pair < const CCreature * , int > & CrePowerPair ;
auto creIt = boost : : max_element ( creToPower , [ ] ( CrePowerPair lhs , CrePowerPair rhs )
{
return lhs . second < rhs . second ;
} ) ;
bestArmy . push_back ( creIt - > first ) ;
creToPower . erase ( creIt ) ;
if ( creToPower . empty ( ) )
break ;
2012-08-29 12:19:20 +03:00
}
2012-08-29 18:23:47 +03:00
//foreach best type -> iterate over slots in both armies and if it's the appropriate type, send it to the slot where it belongs
for ( int i = 0 ; i < bestArmy . size ( ) ; i + + ) //i-th strongest creature type will go to i-th slot
2012-08-29 12:19:20 +03:00
{
2013-06-29 16:05:48 +03:00
for ( auto armyPtr : armies )
2012-08-29 18:23:47 +03:00
for ( int j = 0 ; j < GameConstants : : ARMY_SIZE ; j + + )
2012-08-29 12:19:20 +03:00
{
2014-02-17 10:36:03 +03:00
if ( armyPtr - > getCreature ( SlotID ( j ) ) = = bestArmy [ i ] & & armyPtr ! = army ) //it's a searched creature not in dst ARMY
2014-02-19 19:23:47 +03:00
{
//FIXME: line below is useless when simulating exchange between two non-singular armies
2015-12-24 20:30:57 +02:00
if ( ! ( armyPtr - > needsLastStack ( ) & & armyPtr - > stacksCount ( ) = = 1 ) ) //can't take away last creature
2012-08-29 18:23:47 +03:00
return true ; //at least one exchange will be performed
2014-02-19 19:23:47 +03:00
else
return false ; //no further exchange possible
}
2012-08-29 12:19:20 +03:00
}
}
return false ;
}
2012-03-06 21:49:23 +03:00
void VCAI : : pickBestCreatures ( const CArmedInstance * army , const CArmedInstance * source )
{
2012-03-11 04:26:11 +03:00
//TODO - what if source is a hero (the last stack problem) -> it'd good to create a single stack of weakest cre
const CArmedInstance * armies [ ] = { army , source } ;
2014-02-19 19:23:47 +03:00
2012-03-11 04:26:11 +03:00
//we calculate total strength for each creature type available in armies
std : : map < const CCreature * , int > creToPower ;
2013-06-29 16:05:48 +03:00
for ( auto armyPtr : armies )
for ( auto & i : armyPtr - > Slots ( ) )
2014-02-19 19:23:47 +03:00
{ //TODO: allow splitting stacks?
2012-07-19 21:52:44 +03:00
creToPower [ i . second - > type ] + = i . second - > getPower ( ) ;
2012-08-29 18:23:47 +03:00
}
2012-03-11 04:26:11 +03:00
//TODO - consider more than just power (ie morale penalty, hero specialty in certain stacks, etc)
2014-02-19 19:23:47 +03:00
int armySize = creToPower . size ( ) ;
2012-03-11 04:26:11 +03:00
2012-08-30 21:28:40 +03:00
armySize = std : : min ( ( source - > needsLastStack ( ) ? armySize - 1 : armySize ) , GameConstants : : ARMY_SIZE ) ; //can't move away last stack
2012-03-11 04:26:11 +03:00
std : : vector < const CCreature * > bestArmy ; //types that'll be in final dst army
2012-08-29 18:23:47 +03:00
for ( int i = 0 ; i < armySize ; i + + ) //pick the creatures from which we can get most power, as many as dest can fit
2012-03-06 21:49:23 +03:00
{
2012-03-11 04:26:11 +03:00
typedef const std : : pair < const CCreature * , int > & CrePowerPair ;
auto creIt = boost : : max_element ( creToPower , [ ] ( CrePowerPair lhs , CrePowerPair rhs )
2012-03-06 21:49:23 +03:00
{
2012-03-11 04:26:11 +03:00
return lhs . second < rhs . second ;
} ) ;
bestArmy . push_back ( creIt - > first ) ;
creToPower . erase ( creIt ) ;
if ( creToPower . empty ( ) )
break ;
2012-02-14 21:04:45 +03:00
}
2012-07-19 21:52:44 +03:00
2012-03-11 04:26:11 +03:00
//foreach best type -> iterate over slots in both armies and if it's the appropriate type, send it to the slot where it belongs
for ( int i = 0 ; i < bestArmy . size ( ) ; i + + ) //i-th strongest creature type will go to i-th slot
2012-08-29 12:19:20 +03:00
{
2013-06-29 16:05:48 +03:00
for ( auto armyPtr : armies )
2012-03-11 04:26:11 +03:00
for ( int j = 0 ; j < GameConstants : : ARMY_SIZE ; j + + )
2012-08-29 12:19:20 +03:00
{
2014-02-17 10:36:03 +03:00
if ( armyPtr - > getCreature ( SlotID ( j ) ) = = bestArmy [ i ] & & ( i ! = j | | armyPtr ! = army ) ) //it's a searched creature not in dst SLOT
2015-12-24 20:30:57 +02:00
if ( ! ( armyPtr - > needsLastStack ( ) & & armyPtr - > stacksCount ( ) = = 1 ) ) //can't take away last creature
2013-02-16 17:03:47 +03:00
cb - > mergeOrSwapStacks ( armyPtr , army , SlotID ( j ) , SlotID ( i ) ) ;
2012-08-29 12:19:20 +03:00
}
}
2012-03-11 04:26:11 +03:00
//TODO - having now strongest possible army, we may want to think about arranging stacks
2012-07-18 13:10:14 +03:00
auto hero = dynamic_cast < const CGHeroInstance * > ( army ) ;
if ( hero )
{
checkHeroArmy ( hero ) ;
}
2012-02-14 21:04:45 +03:00
}
2015-04-07 22:48:35 +02:00
void VCAI : : pickBestArtifacts ( const CGHeroInstance * h , const CGHeroInstance * other )
2015-10-31 17:04:06 +02:00
{
2015-04-09 09:53:17 +02:00
auto equipBest = [ ] ( const CGHeroInstance * h , const CGHeroInstance * otherh , bool giveStuffToFirstHero ) - > void
2015-04-07 22:48:35 +02:00
{
2015-04-09 09:53:17 +02:00
bool changeMade = false ;
2015-04-07 22:48:35 +02:00
2015-04-09 09:53:17 +02:00
do
2015-04-07 22:48:35 +02:00
{
2015-04-09 09:53:17 +02:00
changeMade = false ;
2015-04-07 22:48:35 +02:00
2015-04-09 09:53:17 +02:00
//we collect gear always in same order
std : : vector < ArtifactLocation > allArtifacts ;
if ( giveStuffToFirstHero )
{
for ( auto p : h - > artifactsWorn )
{
if ( p . second . artifact )
allArtifacts . push_back ( ArtifactLocation ( h , p . first ) ) ;
}
}
for ( auto slot : h - > artifactsInBackpack )
allArtifacts . push_back ( ArtifactLocation ( h , h - > getArtPos ( slot . artifact ) ) ) ;
2015-04-07 22:48:35 +02:00
2015-04-09 09:53:17 +02:00
if ( otherh )
{
for ( auto p : otherh - > artifactsWorn )
{
if ( p . second . artifact )
allArtifacts . push_back ( ArtifactLocation ( otherh , p . first ) ) ;
}
for ( auto slot : otherh - > artifactsInBackpack )
allArtifacts . push_back ( ArtifactLocation ( otherh , otherh - > getArtPos ( slot . artifact ) ) ) ;
}
//we give stuff to one hero or another, depending on giveStuffToFirstHero
2015-04-07 22:48:35 +02:00
2015-04-09 09:53:17 +02:00
const CGHeroInstance * target = nullptr ;
2016-11-29 18:18:05 +02:00
if ( giveStuffToFirstHero | | ! otherh )
2015-04-09 09:53:17 +02:00
target = h ;
else
target = otherh ;
2015-04-07 22:48:35 +02:00
2015-04-09 09:53:17 +02:00
for ( auto location : allArtifacts )
{
if ( location . relatedObj ( ) = = target & & location . slot < ArtifactPosition : : AFTER_LAST )
continue ; //don't reequip artifact we already wear
2015-04-07 22:48:35 +02:00
2015-12-09 17:49:22 +02:00
if ( location . slot = = ArtifactPosition : : MACH4 ) // don't attempt to move catapult
continue ;
2015-04-09 09:53:17 +02:00
auto s = location . getSlot ( ) ;
2015-08-30 15:15:04 +02:00
if ( ! s | | s - > locked ) //we can't move locks
2015-04-09 09:53:17 +02:00
continue ;
auto artifact = s - > artifact ;
if ( ! artifact )
continue ;
//FIXME: why are the above possible to be null?
bool emptySlotFound = false ;
for ( auto slot : artifact - > artType - > possibleSlots . at ( target - > bearerType ( ) ) )
{
2015-08-30 15:15:04 +02:00
ArtifactLocation destLocation ( target , slot ) ;
if ( target - > isPositionFree ( slot ) & & artifact - > canBePutAt ( destLocation , true ) ) //combined artifacts are not always allowed to move
2015-04-09 09:53:17 +02:00
{
2015-08-30 15:15:04 +02:00
cb - > swapArtifacts ( location , destLocation ) ; //just put into empty slot
2015-04-09 09:53:17 +02:00
emptySlotFound = true ;
changeMade = true ;
break ;
}
}
if ( ! emptySlotFound ) //try to put that atifact in already occupied slot
{
for ( auto slot : artifact - > artType - > possibleSlots . at ( target - > bearerType ( ) ) )
{
auto otherSlot = target - > getSlot ( slot ) ;
if ( otherSlot & & otherSlot - > artifact ) //we need to exchange artifact for better one
{
2015-08-30 15:15:04 +02:00
ArtifactLocation destLocation ( target , slot ) ;
//if that artifact is better than what we have, pick it
if ( compareArtifacts ( artifact , otherSlot - > artifact ) & & artifact - > canBePutAt ( destLocation , true ) ) //combined artifacts are not always allowed to move
2015-04-09 09:53:17 +02:00
{
cb - > swapArtifacts ( location , ArtifactLocation ( target , target - > getArtPos ( otherSlot - > artifact ) ) ) ;
changeMade = true ;
2016-08-18 05:35:29 +02:00
break ;
2015-04-09 09:53:17 +02:00
}
}
}
}
if ( changeMade )
break ; //start evaluating artifacts from scratch
}
} while ( changeMade ) ;
} ;
equipBest ( h , other , true ) ;
if ( other )
{
equipBest ( h , other , false ) ;
2015-04-07 22:48:35 +02:00
}
}
2014-09-19 00:18:49 +03:00
void VCAI : : recruitCreatures ( const CGDwelling * d , const CArmedInstance * recruiter )
2012-02-14 21:04:45 +03:00
{
2012-03-05 22:11:28 +03:00
for ( int i = 0 ; i < d - > creatures . size ( ) ; i + + )
2012-02-14 21:04:45 +03:00
{
2012-03-05 22:11:28 +03:00
if ( ! d - > creatures [ i ] . second . size ( ) )
2012-02-14 21:04:45 +03:00
continue ;
2012-03-05 22:11:28 +03:00
int count = d - > creatures [ i ] . first ;
2013-02-11 02:24:57 +03:00
CreatureID creID = d - > creatures [ i ] . second . back ( ) ;
2012-02-16 20:10:58 +03:00
// const CCreature *c = VLC->creh->creatures[creID];
2012-02-14 21:04:45 +03:00
// if(containsSavedRes(c->cost))
// continue;
2015-12-29 01:14:08 +02:00
vstd : : amin ( count , freeResources ( ) / VLC - > creh - > creatures [ creID ] - > cost ) ;
2012-02-14 21:04:45 +03:00
if ( count > 0 )
2014-09-19 00:18:49 +03:00
cb - > recruitCreatures ( d , recruiter , creID , count , i ) ;
2012-02-14 21:04:45 +03:00
}
}
2013-02-11 22:11:34 +03:00
bool VCAI : : tryBuildStructure ( const CGTownInstance * t , BuildingID building , unsigned int maxDays )
2012-02-14 21:04:45 +03:00
{
2013-12-19 20:29:35 +03:00
if ( maxDays = = 0 )
{
2016-08-15 07:30:06 +02:00
logAi - > warn ( " Request to build building %d in 0 days! " , building . toEnum ( ) ) ;
2013-12-19 20:29:35 +03:00
return false ;
}
2012-09-06 13:39:48 +03:00
if ( ! vstd : : contains ( t - > town - > buildings , building ) )
return false ; // no such building in town
2012-05-19 19:22:34 +03:00
if ( t - > hasBuilt ( building ) ) //Already built? Shouldn't happen in general
return true ;
2013-12-02 14:58:02 +03:00
const CBuilding * buildPtr = t - > town - > buildings . at ( building ) ;
2012-08-26 15:26:07 +03:00
2013-12-02 14:58:02 +03:00
auto toBuild = buildPtr - > requirements . getFulfillmentCandidates ( [ & ] ( const BuildingID & buildID )
2012-08-26 15:26:07 +03:00
{
2013-12-02 14:58:02 +03:00
return t - > hasBuilt ( buildID ) ;
} ) ;
toBuild . push_back ( building ) ;
2012-02-14 21:04:45 +03:00
2013-06-29 16:05:48 +03:00
for ( BuildingID buildID : toBuild )
2012-07-07 16:32:37 +03:00
{
2013-02-09 15:56:35 +03:00
EBuildingState : : EBuildingState canBuild = cb - > canBuildStructure ( t , buildID ) ;
2012-07-07 16:32:37 +03:00
if ( canBuild = = EBuildingState : : HAVE_CAPITAL
| | canBuild = = EBuildingState : : FORBIDDEN
| | canBuild = = EBuildingState : : NO_WATER )
return false ; //we won't be able to build this
}
2012-05-19 19:22:34 +03:00
if ( maxDays & & toBuild . size ( ) > maxDays )
return false ;
2012-02-14 21:04:45 +03:00
2012-05-19 19:22:34 +03:00
TResources currentRes = cb - > getResourceAmount ( ) ;
//TODO: calculate if we have enough resources to build it in maxDays
2013-11-17 20:57:04 +03:00
for ( const auto & buildID : toBuild )
{
const CBuilding * b = t - > town - > buildings . at ( buildID ) ;
EBuildingState : : EBuildingState canBuild = cb - > canBuildStructure ( t , buildID ) ;
if ( canBuild = = EBuildingState : : ALLOWED )
2012-02-14 21:04:45 +03:00
{
if ( ! containsSavedRes ( b - > resources ) )
{
2017-08-11 19:03:05 +02:00
logAi - > debug ( " Player %d will build %s in town of %s at %s " , playerID , b - > Name ( ) , t - > name , t - > pos . toString ( ) ) ;
2012-05-19 19:22:34 +03:00
cb - > buildBuilding ( t , buildID ) ;
return true ;
2012-02-14 21:04:45 +03:00
}
2012-05-19 19:22:34 +03:00
continue ;
2013-11-17 20:57:04 +03:00
}
else if ( canBuild = = EBuildingState : : NO_RESOURCES )
{
2014-03-23 19:36:16 +03:00
//TResources income = estimateIncome();
2013-11-17 20:57:04 +03:00
TResources cost = t - > town - > buildings . at ( buildID ) - > resources ;
for ( int i = 0 ; i < GameConstants : : RESOURCE_QUANTITY ; i + + )
{
2014-03-23 19:36:16 +03:00
//int diff = currentRes[i] - cost[i] + income[i];
int diff = currentRes [ i ] - cost [ i ] ;
2012-02-14 21:04:45 +03:00
if ( diff < 0 )
saving [ i ] = 1 ;
}
2012-05-19 19:22:34 +03:00
continue ;
}
2013-12-02 14:58:02 +03:00
else if ( canBuild = = EBuildingState : : PREREQUIRES )
{
// can happen when dependencies have their own missing dependencies
if ( tryBuildStructure ( t , buildID , maxDays - 1 ) )
return true ;
}
else if ( canBuild = = EBuildingState : : MISSING_BASE )
{
if ( tryBuildStructure ( t , b - > upgrade , maxDays - 1 ) )
return true ;
}
2012-05-19 19:22:34 +03:00
}
return false ;
}
2013-12-23 23:46:01 +03:00
//bool VCAI::canBuildStructure(const CGTownInstance * t, BuildingID building, unsigned int maxDays=7)
//{
// if (maxDays == 0)
// {
2016-08-15 07:30:06 +02:00
// logAi->warn("Request to build building %d in 0 days!", building.toEnum());
2013-12-23 23:46:01 +03:00
// return false;
// }
//
// if (!vstd::contains(t->town->buildings, building))
// return false; // no such building in town
//
// if (t->hasBuilt(building)) //Already built? Shouldn't happen in general
// return true;
//
// const CBuilding * buildPtr = t->town->buildings.at(building);
//
// auto toBuild = buildPtr->requirements.getFulfillmentCandidates([&](const BuildingID & buildID)
// {
// return t->hasBuilt(buildID);
// });
// toBuild.push_back(building);
//
// for(BuildingID buildID : toBuild)
// {
// EBuildingState::EBuildingState canBuild = cb->canBuildStructure(t, buildID);
// if (canBuild == EBuildingState::HAVE_CAPITAL
// || canBuild == EBuildingState::FORBIDDEN
// || canBuild == EBuildingState::NO_WATER)
// return false; //we won't be able to build this
// }
//
// if (maxDays && toBuild.size() > maxDays)
// return false;
//
// TResources currentRes = cb->getResourceAmount();
// TResources income = estimateIncome();
// //TODO: calculate if we have enough resources to build it in maxDays
//
// for(const auto & buildID : toBuild)
// {
// const CBuilding *b = t->town->buildings.at(buildID);
//
// EBuildingState::EBuildingState canBuild = cb->canBuildStructure(t, buildID);
// if(canBuild == EBuildingState::ALLOWED)
// {
// if(!containsSavedRes(b->resources))
// {
2017-08-11 19:03:05 +02:00
// logAi->debug("Player %d will build %s in town of %s at %s", playerID, b->Name(), t->name, t->pos.toString());
2013-12-23 23:46:01 +03:00
// return true;
// }
// continue;
// }
// else if(canBuild == EBuildingState::NO_RESOURCES)
// {
// TResources cost = t->town->buildings.at(buildID)->resources;
// for (int i = 0; i < GameConstants::RESOURCE_QUANTITY; i++)
// {
// int diff = currentRes[i] - cost[i] + income[i];
// if(diff < 0)
// saving[i] = 1;
// }
// continue;
// }
// else if (canBuild == EBuildingState::PREREQUIRES)
// {
// // can happen when dependencies have their own missing dependencies
// if (canBuildStructure(t, buildID, maxDays - 1))
// return true;
// }
// else if (canBuild == EBuildingState::MISSING_BASE)
// {
// if (canBuildStructure(t, b->upgrade, maxDays - 1))
// return true;
// }
// }
// return false;
//}
2013-02-11 22:11:34 +03:00
bool VCAI : : tryBuildAnyStructure ( const CGTownInstance * t , std : : vector < BuildingID > buildList , unsigned int maxDays )
2012-05-19 19:22:34 +03:00
{
2013-06-29 16:05:48 +03:00
for ( const auto & building : buildList )
2012-05-19 19:22:34 +03:00
{
if ( t - > hasBuilt ( building ) )
continue ;
if ( tryBuildStructure ( t , building , maxDays ) )
return true ;
}
return false ; //Can't build anything
}
2012-02-14 21:04:45 +03:00
2013-12-23 23:46:01 +03:00
BuildingID VCAI : : canBuildAnyStructure ( const CGTownInstance * t , std : : vector < BuildingID > buildList , unsigned int maxDays )
{
for ( const auto & building : buildList )
{
if ( t - > hasBuilt ( building ) )
continue ;
if ( cb - > canBuildStructure ( t , building ) )
return building ;
}
return BuildingID : : NONE ; //Can't build anything
}
2013-02-11 22:11:34 +03:00
bool VCAI : : tryBuildNextStructure ( const CGTownInstance * t , std : : vector < BuildingID > buildList , unsigned int maxDays )
2012-05-19 19:22:34 +03:00
{
2013-06-29 16:05:48 +03:00
for ( const auto & building : buildList )
2012-05-19 19:22:34 +03:00
{
if ( t - > hasBuilt ( building ) )
2012-02-14 21:04:45 +03:00
continue ;
2012-05-19 19:22:34 +03:00
return tryBuildStructure ( t , building , maxDays ) ;
}
return false ; //Nothing to build
}
2015-12-04 01:06:02 +02:00
//Set of buildings for different goals. Does not include any prerequisites.
static const BuildingID essential [ ] = { BuildingID : : TAVERN , BuildingID : : TOWN_HALL } ;
static const BuildingID goldSource [ ] = { BuildingID : : TOWN_HALL , BuildingID : : CITY_HALL , BuildingID : : CAPITOL } ;
2017-07-15 00:15:08 +02:00
static const BuildingID capitolRequirements [ ] = { BuildingID : : FORT , BuildingID : : CITADEL } ;
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 } ;
static const BuildingID unitsUpgrade [ ] = { BuildingID : : DWELL_LVL_1_UP , BuildingID : : DWELL_LVL_2_UP , BuildingID : : DWELL_LVL_3_UP ,
BuildingID : : DWELL_LVL_4_UP , BuildingID : : DWELL_LVL_5_UP , BuildingID : : DWELL_LVL_6_UP , BuildingID : : DWELL_LVL_7_UP } ;
static const BuildingID unitGrowth [ ] = { BuildingID : : FORT , BuildingID : : CITADEL , BuildingID : : CASTLE , BuildingID : : HORDE_1 ,
BuildingID : : HORDE_1_UPGR , BuildingID : : HORDE_2 , BuildingID : : HORDE_2_UPGR } ;
2017-07-20 06:08:49 +02:00
static const BuildingID _spells [ ] = { BuildingID : : MAGES_GUILD_1 , BuildingID : : MAGES_GUILD_2 , BuildingID : : MAGES_GUILD_3 ,
2015-12-04 01:06:02 +02:00
BuildingID : : MAGES_GUILD_4 , BuildingID : : MAGES_GUILD_5 } ;
static const BuildingID extra [ ] = { BuildingID : : RESOURCE_SILO , BuildingID : : SPECIAL_1 , BuildingID : : SPECIAL_2 , BuildingID : : SPECIAL_3 ,
BuildingID : : SPECIAL_4 , BuildingID : : SHIPYARD } ; // all remaining buildings
2012-05-19 19:22:34 +03:00
void VCAI : : buildStructure ( const CGTownInstance * t )
{
//TODO make *real* town development system
2012-05-20 00:38:01 +03:00
//TODO: faction-specific development: use special buildings, build dwellings in better order, etc
2012-05-19 19:22:34 +03:00
//TODO: build resource silo, defences when needed
2012-05-20 00:38:01 +03:00
//Possible - allow "locking" on specific building (build prerequisites and then building itself)
2012-05-19 19:22:34 +03:00
2017-07-15 00:15:08 +02:00
//below algorithm focuses on economy growth at start of the game.
2012-05-19 19:22:34 +03:00
TResources currentRes = cb - > getResourceAmount ( ) ;
2014-04-26 17:23:35 +03:00
TResources currentIncome = t - > dailyIncome ( ) ;
int townIncome = currentIncome [ Res : : GOLD ] ;
2012-05-19 19:22:34 +03:00
2017-07-15 00:15:08 +02:00
if ( tryBuildAnyStructure ( t , std : : vector < BuildingID > ( essential , essential + ARRAY_COUNT ( essential ) ) ) )
2012-05-19 19:22:34 +03:00
return ;
2017-07-15 00:15:08 +02:00
//the more gold the better and less problems later
if ( tryBuildNextStructure ( t , std : : vector < BuildingID > ( goldSource , goldSource + ARRAY_COUNT ( goldSource ) ) ) )
return ;
//workaround for mantis #2696 - build fort and citadel - building castle will be handled without bug
if ( vstd : : contains ( t - > builtBuildings , BuildingID : : CITY_HALL ) & & cb - > canBuildStructure ( t , BuildingID : : CAPITOL ) ! = EBuildingState : : HAVE_CAPITAL & &
cb - > canBuildStructure ( t , BuildingID : : CAPITOL ) ! = EBuildingState : : FORBIDDEN )
if ( tryBuildNextStructure ( t , std : : vector < BuildingID > ( capitolRequirements , capitolRequirements + ARRAY_COUNT ( capitolRequirements ) ) ) )
2012-05-19 19:22:34 +03:00
return ;
2017-07-15 00:15:08 +02:00
if ( ( ! vstd : : contains ( t - > builtBuildings , BuildingID : : CAPITOL ) & & cb - > canBuildStructure ( t , BuildingID : : CAPITOL ) ! = EBuildingState : : HAVE_CAPITAL & & cb - > canBuildStructure ( t , BuildingID : : CAPITOL ) ! = EBuildingState : : FORBIDDEN )
| | ( ! vstd : : contains ( t - > builtBuildings , BuildingID : : CITY_HALL ) & & cb - > canBuildStructure ( t , BuildingID : : CAPITOL ) = = EBuildingState : : HAVE_CAPITAL & & cb - > canBuildStructure ( t , BuildingID : : CITY_HALL ) ! = EBuildingState : : FORBIDDEN )
| | ( ! vstd : : contains ( t - > builtBuildings , BuildingID : : TOWN_HALL ) & & cb - > canBuildStructure ( t , BuildingID : : TOWN_HALL ) ! = EBuildingState : : FORBIDDEN ) )
return ; //save money for capitol or city hall if capitol unavailable, do not build other things (unless gold source buildings are disabled in map editor)
if ( cb - > getDate ( Date : : DAY_OF_WEEK ) > 6 ) // last 2 days of week - try to focus on growth
2012-05-19 19:22:34 +03:00
{
2017-07-15 00:15:08 +02:00
if ( tryBuildNextStructure ( t , std : : vector < BuildingID > ( unitGrowth , unitGrowth + ARRAY_COUNT ( unitGrowth ) ) , 2 ) )
2012-05-19 19:22:34 +03:00
return ;
}
// first in-game week or second half of any week: try build dwellings
2017-07-15 00:15:08 +02:00
if ( cb - > getDate ( Date : : DAY ) < 7 | | cb - > getDate ( Date : : DAY_OF_WEEK ) > 3 )
if ( tryBuildAnyStructure ( t , std : : vector < BuildingID > ( unitsSource , unitsSource + ARRAY_COUNT ( unitsSource ) ) , 8 - cb - > getDate ( Date : : DAY_OF_WEEK ) ) )
2012-05-19 19:22:34 +03:00
return ;
//try to upgrade dwelling
for ( int i = 0 ; i < ARRAY_COUNT ( unitsUpgrade ) ; i + + )
{
2017-07-15 00:15:08 +02:00
if ( t - > hasBuilt ( unitsSource [ i ] ) & & ! t - > hasBuilt ( unitsUpgrade [ i ] ) )
2012-05-19 19:22:34 +03:00
{
2017-07-15 00:15:08 +02:00
if ( tryBuildStructure ( t , unitsUpgrade [ i ] ) )
2012-05-19 19:22:34 +03:00
return ;
2012-02-14 21:04:45 +03:00
}
}
2012-05-19 19:22:34 +03:00
//remaining tasks
2017-07-20 06:08:49 +02:00
if ( tryBuildNextStructure ( t , std : : vector < BuildingID > ( _spells , _spells + ARRAY_COUNT ( _spells ) ) ) )
2012-05-19 19:22:34 +03:00
return ;
2017-07-15 00:15:08 +02:00
if ( tryBuildAnyStructure ( t , std : : vector < BuildingID > ( extra , extra + ARRAY_COUNT ( extra ) ) ) )
2012-05-19 19:22:34 +03:00
return ;
2017-02-18 19:40:44 +02:00
2017-02-18 22:10:59 +02:00
//at the end, try to get and build any extra buildings with nonstandard slots (for example HotA 3rd level dwelling)
std : : vector < BuildingID > extraBuildings ;
2017-02-18 19:40:44 +02:00
2017-07-15 00:15:08 +02:00
for ( auto buildingInfo : t - > town - > buildings )
if ( buildingInfo . first > 43 )
2017-02-18 22:10:59 +02:00
extraBuildings . push_back ( buildingInfo . first ) ;
2017-02-18 19:40:44 +02:00
2017-07-15 00:15:08 +02:00
if ( tryBuildAnyStructure ( t , extraBuildings ) )
2017-02-18 19:40:44 +02:00
return ;
2012-02-14 21:04:45 +03:00
}
2015-03-30 15:32:23 +02:00
bool VCAI : : isGoodForVisit ( const CGObjectInstance * obj , HeroPtr h , SectorMap & sm )
2012-02-14 21:04:45 +03:00
{
2013-12-21 22:17:27 +03:00
const int3 pos = obj - > visitablePos ( ) ;
2015-04-05 21:13:47 +02:00
const int3 targetPos = sm . firstTileToGet ( h , pos ) ;
2015-04-10 08:50:21 +02:00
if ( ! targetPos . valid ( ) )
return false ;
if ( isTileNotReserved ( h . get ( ) , targetPos ) & &
2013-12-21 20:34:59 +03:00
! obj - > wasVisited ( playerID ) & &
2013-12-25 01:01:16 +03:00
( cb - > getPlayerRelations ( ai - > playerID , obj - > tempOwner ) = = PlayerRelations : : ENEMIES | | isWeeklyRevisitable ( obj ) ) & & //flag or get weekly resources / creatures
2013-12-21 20:34:59 +03:00
isSafeToVisit ( h , pos ) & &
shouldVisit ( h , obj ) & &
! vstd : : contains ( alreadyVisited , obj ) & &
2015-04-10 08:50:21 +02:00
! vstd : : contains ( reservedObjs , obj ) & &
isAccessibleForHero ( targetPos , h ) )
2013-12-21 22:17:27 +03:00
{
const CGObjectInstance * topObj = cb - > getVisitableObjs ( obj - > visitablePos ( ) ) . back ( ) ; //it may be hero visiting this obj
//we don't try visiting object on which allied or owned hero stands
// -> it will just trigger exchange windows and AI will be confused that obj behind doesn't get visited
if ( topObj - > ID = = Obj : : HERO & & cb - > getPlayerRelations ( h - > tempOwner , topObj - > tempOwner ) ! = PlayerRelations : : ENEMIES )
return false ;
else
return true ; //all of the following is met
}
return false ;
}
2015-04-10 08:50:21 +02:00
bool VCAI : : isTileNotReserved ( const CGHeroInstance * h , int3 t )
2014-02-17 20:28:39 +03:00
{
if ( t . valid ( ) )
{
auto obj = cb - > getTopObj ( t ) ;
if ( obj & & vstd : : contains ( ai - > reservedObjs , obj ) & & ! vstd : : contains ( reservedHeroesMap [ h ] , obj ) )
2014-02-20 23:18:49 +03:00
return false ; //do not capture object reserved by another hero
2014-02-17 20:28:39 +03:00
else
2014-02-20 23:18:49 +03:00
return true ;
2014-02-17 20:28:39 +03:00
}
2014-02-20 23:18:49 +03:00
else
return false ;
2014-02-17 20:28:39 +03:00
}
2013-12-19 23:21:21 +03:00
bool VCAI : : canRecruitAnyHero ( const CGTownInstance * t ) const
{
//TODO: make gathering gold, building tavern or conquering town (?) possible subgoals
if ( ! t )
t = findTownWithTavern ( ) ;
if ( t )
2015-12-06 23:13:58 +02:00
return cb - > getResourceAmount ( Res : : GOLD ) > = GameConstants : : HERO_GOLD_COST & &
2013-12-19 23:21:21 +03:00
cb - > getHeroesInfo ( ) . size ( ) < ALLOWED_ROAMING_HEROES & &
cb - > getAvailableHeroes ( t ) . size ( ) ;
else
return false ;
}
2012-07-01 02:48:40 +03:00
void VCAI : : wander ( HeroPtr h )
2012-02-14 21:04:45 +03:00
{
2014-02-07 23:09:15 +03:00
//unclaim objects that are now dangerous for us
2014-03-01 15:53:09 +03:00
auto reservedObjsSetCopy = reservedHeroesMap [ h ] ;
for ( auto obj : reservedObjsSetCopy )
2014-02-07 23:09:15 +03:00
{
2014-02-23 23:37:33 +03:00
if ( ! isSafeToVisit ( h , obj - > visitablePos ( ) ) )
unreserveObject ( h , obj ) ;
}
2014-02-07 23:09:15 +03:00
2013-12-23 23:46:01 +03:00
TimeCheck tc ( " looking for wander destination " ) ;
2014-02-05 00:49:04 +03:00
while ( h - > movement )
2012-02-14 21:04:45 +03:00
{
2013-08-18 18:46:28 +03:00
validateVisitableObjs ( ) ;
2016-08-11 15:53:05 +02:00
std : : vector < ObjectIdRef > dests ;
2013-11-24 22:15:08 +03:00
2015-12-04 00:10:51 +02:00
auto sm = getCachedSectorMap ( h ) ;
2015-04-05 21:13:47 +02:00
2016-08-11 15:53:05 +02:00
//also visit our reserved objects - but they are not prioritized to avoid running back and forth
vstd : : copy_if ( reservedHeroesMap [ h ] , std : : back_inserter ( dests ) , [ & ] ( ObjectIdRef obj ) - > bool
2013-11-24 22:15:08 +03:00
{
2015-12-04 00:10:51 +02:00
int3 pos = sm - > firstTileToGet ( h , obj - > visitablePos ( ) ) ;
2016-08-11 15:53:05 +02:00
if ( pos . valid ( ) & & isAccessibleForHero ( pos , h ) ) //even nearby objects could be blocked by other heroes :(
return true ;
return false ;
} ) ;
2016-08-12 08:05:11 +02:00
int pass = 0 ;
while ( ! dests . size ( ) & & pass < 3 )
2016-08-11 15:53:05 +02:00
{
2016-08-12 08:05:11 +02:00
if ( pass < 2 ) // optimization - first check objects in current sector; then in sectors around
{
auto objs = sm - > getNearbyObjs ( h , pass ) ;
vstd : : copy_if ( objs , std : : back_inserter ( dests ) , [ & ] ( ObjectIdRef obj ) - > bool
{
return isGoodForVisit ( obj , h , * sm ) ;
} ) ;
}
else // we only check full objects list if for some reason there are no objects in closest sectors
{
vstd : : copy_if ( visitableObjs , std : : back_inserter ( dests ) , [ & ] ( ObjectIdRef obj ) - > bool
{
return isGoodForVisit ( obj , h , * sm ) ;
} ) ;
}
pass + + ;
}
2013-11-24 22:15:08 +03:00
2015-12-29 01:14:08 +02:00
vstd : : erase_if ( dests , [ & ] ( ObjectIdRef obj ) - > bool
2015-04-05 21:13:47 +02:00
{
2015-12-04 00:10:51 +02:00
return ! isSafeToVisit ( h , sm - > firstTileToGet ( h , obj - > visitablePos ( ) ) ) ;
2015-04-05 21:13:47 +02:00
} ) ;
2012-04-17 15:46:21 +03:00
2012-03-13 23:33:00 +03:00
if ( ! dests . size ( ) )
2012-02-14 21:04:45 +03:00
{
2013-12-23 23:46:01 +03:00
if ( cb - > getVisitableObjs ( h - > visitablePos ( ) ) . size ( ) > 1 )
moveHeroToTile ( h - > visitablePos ( ) , h ) ; //just in case we're standing on blocked subterranean gate
2012-03-29 21:26:06 +03:00
auto compareReinforcements = [ h ] ( const CGTownInstance * lhs , const CGTownInstance * rhs ) - > bool
2014-02-05 00:49:04 +03:00
{
2012-03-29 21:26:06 +03:00
return howManyReinforcementsCanGet ( h , lhs ) < howManyReinforcementsCanGet ( h , rhs ) ;
2014-02-05 00:49:04 +03:00
} ;
std : : vector < const CGTownInstance * > townsReachable ;
std : : vector < const CGTownInstance * > townsNotReachable ;
for ( const CGTownInstance * t : cb - > getTownsInfo ( ) )
{
if ( ! t - > visitingHero & & howManyReinforcementsCanGet ( h , t ) & & ! vstd : : contains ( townVisitsThisWeek [ h ] , t ) )
{
if ( isAccessibleForHero ( t - > visitablePos ( ) , h ) )
2012-03-29 21:26:06 +03:00
townsReachable . push_back ( t ) ;
2014-02-05 00:49:04 +03:00
else
2012-03-29 21:26:06 +03:00
townsNotReachable . push_back ( t ) ;
2014-02-05 00:49:04 +03:00
}
2012-03-29 21:26:06 +03:00
}
2014-02-05 00:49:04 +03:00
if ( townsReachable . size ( ) )
{
2012-03-29 21:26:06 +03:00
boost : : sort ( townsReachable , compareReinforcements ) ;
2014-05-29 13:42:05 +03:00
dests . push_back ( townsReachable . back ( ) ) ;
2012-03-29 21:26:06 +03:00
}
else if ( townsNotReachable . size ( ) )
{
boost : : sort ( townsNotReachable , compareReinforcements ) ;
2013-12-23 23:46:01 +03:00
//TODO pick the truly best
const CGTownInstance * t = townsNotReachable . back ( ) ;
2017-08-11 19:03:05 +02:00
logAi - > debug ( " %s can't reach any town, we'll try to make our way to %s at %s " , h - > name , t - > name , t - > visitablePos ( ) . toString ( ) ) ;
2012-03-29 21:26:06 +03:00
int3 pos1 = h - > pos ;
2013-12-23 23:46:01 +03:00
striveToGoal ( sptr ( Goals : : ClearWayTo ( t - > visitablePos ( ) ) . sethero ( h ) ) ) ;
//if out hero is stuck, we may need to request another hero to clear the way we see
2012-03-29 21:26:06 +03:00
if ( pos1 = = h - > pos & & h = = primaryHero ( ) ) //hero can't move
{
2013-12-19 23:21:21 +03:00
if ( canRecruitAnyHero ( t ) )
recruitHero ( t ) ;
2012-03-29 21:26:06 +03:00
}
2013-12-23 23:46:01 +03:00
break ;
2012-03-29 21:26:06 +03:00
}
2015-12-06 23:13:58 +02:00
else if ( cb - > getResourceAmount ( Res : : GOLD ) > = GameConstants : : HERO_GOLD_COST )
2013-12-23 23:46:01 +03:00
{
2012-03-29 21:26:06 +03:00
std : : vector < const CGTownInstance * > towns = cb - > getTownsInfo ( ) ;
2015-12-29 01:14:08 +02:00
vstd : : erase_if ( towns , [ ] ( const CGTownInstance * t ) - > bool
2012-03-29 21:26:06 +03:00
{
2013-06-29 16:05:48 +03:00
for ( const CGHeroInstance * h : cb - > getHeroesInfo ( ) )
2012-03-29 21:26:06 +03:00
if ( ! t - > getArmyStrength ( ) | | howManyReinforcementsCanGet ( h , t ) )
return true ;
return false ;
} ) ;
boost : : sort ( towns , compareArmyStrength ) ;
2013-12-23 23:46:01 +03:00
if ( towns . size ( ) )
2014-02-05 00:49:04 +03:00
recruitHero ( towns . back ( ) ) ;
2013-12-23 23:46:01 +03:00
break ;
}
else
{
2016-08-15 07:30:06 +02:00
logAi - > debug ( " Nowhere more to go... " ) ;
2012-03-29 21:26:06 +03:00
break ;
}
2012-02-14 21:04:45 +03:00
}
2014-02-05 00:49:04 +03:00
//end of objs empty
2015-04-07 08:55:13 +02:00
if ( dests . size ( ) ) //performance improvement
2012-02-14 21:04:45 +03:00
{
2015-04-07 08:55:13 +02:00
boost : : sort ( dests , CDistanceSorter ( h . get ( ) ) ) ; //find next closest one
2014-02-05 00:49:04 +03:00
//wander should not cause heroes to be reserved - they are always considered free
const ObjectIdRef & dest = dests . front ( ) ;
2016-08-15 07:30:06 +02:00
logAi - > debug ( " Of all %d destinations, object oid=%d seems nice " , dests . size ( ) , dest . id . getNum ( ) ) ;
2014-02-05 00:49:04 +03:00
if ( ! goVisitObj ( dest , h ) )
2012-04-17 15:46:21 +03:00
{
2014-02-05 00:49:04 +03:00
if ( ! dest )
{
2016-08-15 07:30:06 +02:00
logAi - > debug ( " Visit attempt made the object (id=%d) gone... " , dest . id . getNum ( ) ) ;
2014-02-05 00:49:04 +03:00
}
else
{
2016-08-15 07:30:06 +02:00
logAi - > debug ( " Hero %s apparently used all MPs (%d left) " , h - > name , h - > movement ) ;
2014-02-06 09:05:45 +03:00
return ;
2014-02-05 00:49:04 +03:00
}
2012-04-17 15:46:21 +03:00
}
2012-02-14 21:04:45 +03:00
}
2014-02-05 00:49:04 +03:00
if ( h - > visitedTown )
2012-02-14 21:04:45 +03:00
{
2013-12-25 16:38:20 +03:00
townVisitsThisWeek [ h ] . insert ( h - > visitedTown ) ;
2012-02-14 21:04:45 +03:00
buildArmyIn ( h - > visitedTown ) ;
}
}
}
2013-11-23 21:16:25 +03:00
void VCAI : : setGoal ( HeroPtr h , Goals : : TSubgoal goal )
2016-11-27 19:10:20 +02:00
{
2015-12-29 01:14:08 +02:00
if ( goal - > invalid ( ) )
vstd : : erase_if_present ( lockedHeroes , h ) ;
2012-05-05 09:42:18 +03:00
else
2013-11-24 11:56:02 +03:00
{
2013-11-23 21:16:25 +03:00
lockedHeroes [ h ] = goal ;
2016-11-27 19:10:20 +02:00
goal - > setisElementar ( false ) ; //Force always evaluate goals before realizing
2013-11-24 11:56:02 +03:00
}
2012-03-13 15:47:47 +03:00
}
2016-11-27 19:10:20 +02:00
void VCAI : : evaluateGoal ( HeroPtr h )
{
if ( vstd : : contains ( lockedHeroes , h ) )
fh - > setPriority ( lockedHeroes [ h ] ) ;
}
2012-03-13 15:47:47 +03:00
2013-11-23 21:16:25 +03:00
void VCAI : : completeGoal ( Goals : : TSubgoal goal )
2012-03-13 15:47:47 +03:00
{
2016-08-15 07:30:06 +02:00
logAi - > trace ( " Completing goal: %s " , goal - > name ( ) ) ;
2013-11-23 21:16:25 +03:00
if ( const CGHeroInstance * h = goal - > hero . get ( true ) )
2012-05-08 11:10:40 +03:00
{
auto it = lockedHeroes . find ( h ) ;
if ( it ! = lockedHeroes . end ( ) )
2013-11-24 11:21:51 +03:00
if ( it - > second = = goal )
{
2016-08-15 07:30:06 +02:00
logAi - > debug ( goal - > completeMessage ( ) ) ;
2012-05-08 11:10:40 +03:00
lockedHeroes . erase ( it ) ; //goal fulfilled, free hero
2013-11-24 11:21:51 +03:00
}
2012-05-08 11:10:40 +03:00
}
2013-11-24 11:21:51 +03:00
else //complete goal for all heroes maybe?
{
2014-06-08 20:12:16 +03:00
vstd : : erase_if ( lockedHeroes , [ goal ] ( std : : pair < HeroPtr , Goals : : TSubgoal > p )
2013-11-24 11:21:51 +03:00
{
2014-02-23 19:55:42 +03:00
if ( * ( p . second ) = = * goal | | p . second - > fulfillsMe ( goal ) ) //we could have fulfilled goals of other heroes by chance
2013-11-24 11:21:51 +03:00
{
2016-08-15 07:30:06 +02:00
logAi - > debug ( p . second - > completeMessage ( ) ) ;
2014-06-08 20:12:16 +03:00
return true ;
2013-11-24 11:21:51 +03:00
}
2014-06-08 20:12:16 +03:00
return false ;
} ) ;
2013-11-24 11:21:51 +03:00
}
2012-05-08 11:10:40 +03:00
}
2012-02-14 21:04:45 +03:00
void VCAI : : battleStart ( const CCreatureSet * army1 , const CCreatureSet * army2 , int3 tile , const CGHeroInstance * hero1 , const CGHeroInstance * hero2 , bool side )
{
2012-04-17 15:46:21 +03:00
NET_EVENT_HANDLER ;
2013-03-03 20:06:03 +03:00
assert ( playerID > PlayerColor : : PLAYER_LIMIT | | status . getBattle ( ) = = UPCOMING_BATTLE ) ;
2012-02-14 21:04:45 +03:00
status . setBattle ( ONGOING_BATTLE ) ;
2015-12-29 01:14:08 +02:00
const CGObjectInstance * presumedEnemy = vstd : : backOrNull ( cb - > getVisitableObjs ( tile ) ) ; //may be nullptr in some very are cases -> eg. visited monolith and fighting with an enemy at the FoW covered exit
2017-08-11 19:03:05 +02:00
battlename = boost : : str ( boost : : format ( " Starting battle of %s attacking %s at %s " ) % ( hero1 ? hero1 - > name : " a army " ) % ( presumedEnemy ? presumedEnemy - > getObjectName ( ) : " unknown enemy " ) % tile . toString ( ) ) ;
2012-02-14 21:04:45 +03:00
CAdventureAI : : battleStart ( army1 , army2 , tile , hero1 , hero2 , side ) ;
}
void VCAI : : battleEnd ( const BattleResult * br )
{
2012-04-17 15:46:21 +03:00
NET_EVENT_HANDLER ;
2012-02-14 21:04:45 +03:00
assert ( status . getBattle ( ) = = ONGOING_BATTLE ) ;
status . setBattle ( ENDING_BATTLE ) ;
bool won = br - > winner = = myCb - > battleGetMySide ( ) ;
2016-09-16 04:45:00 +02:00
logAi - > debug ( " Player %d (%s): I %s the %s! " , playerID , playerID . getStr ( ) , ( won ? " won " : " lost " ) , battlename ) ;
2012-02-14 21:04:45 +03:00
battlename . clear ( ) ;
CAdventureAI : : battleEnd ( br ) ;
}
void VCAI : : waitTillFree ( )
{
2017-06-14 06:59:41 +02:00
auto unlock = vstd : : makeUnlockSharedGuard ( CGameState : : mutex ) ;
2012-02-14 21:04:45 +03:00
status . waitTillFree ( ) ;
}
2012-03-14 16:02:38 +03:00
void VCAI : : markObjectVisited ( const CGObjectInstance * obj )
{
2016-12-19 22:46:01 +02:00
if ( ! obj )
return ;
if ( dynamic_cast < const CGVisitableOPH * > ( obj ) | | //we may want to visit it with another hero
2012-03-14 16:02:38 +03:00
dynamic_cast < const CGBonusingObject * > ( obj ) | | //or another time
( obj - > ID = = Obj : : MONSTER ) )
return ;
2013-12-25 16:38:20 +03:00
alreadyVisited . insert ( obj ) ;
2012-03-14 16:02:38 +03:00
}
2012-07-01 02:48:40 +03:00
void VCAI : : reserveObject ( HeroPtr h , const CGObjectInstance * obj )
2012-03-29 21:26:06 +03:00
{
2013-12-25 16:38:20 +03:00
reservedObjs . insert ( obj ) ;
reservedHeroesMap [ h ] . insert ( obj ) ;
2016-08-15 07:30:06 +02:00
logAi - > debug ( " reserved object id=%d; address=%p; name=%s " , obj - > id , obj , obj - > getObjectName ( ) ) ;
2012-03-29 21:26:06 +03:00
}
2014-02-23 23:37:33 +03:00
void VCAI : : unreserveObject ( HeroPtr h , const CGObjectInstance * obj )
{
2015-12-29 01:14:08 +02:00
vstd : : erase_if_present ( reservedObjs , obj ) ; //unreserve objects
vstd : : erase_if_present ( reservedHeroesMap [ h ] , obj ) ;
2014-02-23 23:37:33 +03:00
}
2014-03-23 19:00:43 +03:00
void VCAI : : markHeroUnableToExplore ( HeroPtr h )
{
heroesUnableToExplore . insert ( h ) ;
}
void VCAI : : markHeroAbleToExplore ( HeroPtr h )
{
2015-12-29 01:14:08 +02:00
vstd : : erase_if_present ( heroesUnableToExplore , h ) ;
2014-03-23 19:00:43 +03:00
}
bool VCAI : : isAbleToExplore ( HeroPtr h )
{
return ! vstd : : contains ( heroesUnableToExplore , h ) ;
}
2015-10-26 17:38:17 +02:00
void VCAI : : clearPathsInfo ( )
2014-03-23 19:00:43 +03:00
{
heroesUnableToExplore . clear ( ) ;
2015-10-26 17:38:17 +02:00
cachedSectorMaps . clear ( ) ;
2014-03-23 19:00:43 +03:00
}
2012-02-14 21:04:45 +03:00
void VCAI : : validateVisitableObjs ( )
{
2013-08-18 18:46:28 +03:00
std : : string errorMsg ;
auto shouldBeErased = [ & ] ( const CGObjectInstance * obj ) - > bool
2013-04-21 15:11:13 +03:00
{
2015-08-30 09:14:54 +02:00
if ( obj )
2015-12-08 03:18:31 +02:00
return ! cb - > getObj ( obj - > id , false ) ; // no verbose output needed as we check object visibility
2015-08-30 09:14:54 +02:00
else
2013-04-21 15:11:13 +03:00
return true ;
2013-08-18 18:46:28 +03:00
} ;
//errorMsg is captured by ref so lambda will take the new text
errorMsg = " shouldn't be on the visitable objects list! " ;
2015-12-29 01:14:08 +02:00
vstd : : erase_if ( visitableObjs , shouldBeErased ) ;
2013-08-18 18:46:28 +03:00
2014-10-01 14:26:04 +03:00
//FIXME: how comes our own heroes become inaccessible?
2015-12-29 01:14:08 +02:00
vstd : : erase_if ( reservedHeroesMap , [ ] ( std : : pair < HeroPtr , std : : set < const CGObjectInstance * > > hp ) - > bool
2014-10-01 14:26:04 +03:00
{
return ! hp . first . get ( true ) ;
} ) ;
2013-08-18 18:46:28 +03:00
for ( auto & p : reservedHeroesMap )
{
errorMsg = " shouldn't be on list for hero " + p . first - > name + " ! " ;
2015-12-29 01:14:08 +02:00
vstd : : erase_if ( p . second , shouldBeErased ) ;
2013-08-18 18:46:28 +03:00
}
errorMsg = " shouldn't be on the reserved objs list! " ;
2015-12-29 01:14:08 +02:00
vstd : : erase_if ( reservedObjs , shouldBeErased ) ;
2013-08-18 18:46:28 +03:00
//TODO overkill, hidden object should not be removed. However, we can't know if hidden object is erased from game.
errorMsg = " shouldn't be on the already visited objs list! " ;
2015-12-29 01:14:08 +02:00
vstd : : erase_if ( alreadyVisited , shouldBeErased ) ;
2012-02-14 21:04:45 +03:00
}
2017-07-15 13:08:20 +02:00
void VCAI : : retreiveVisitableObjs ( std : : vector < const CGObjectInstance * > & out , bool includeOwned ) const
2012-02-14 21:04:45 +03:00
{
2012-04-14 10:22:08 +03:00
foreach_tile_pos ( [ & ] ( const int3 & pos )
{
2013-06-29 16:05:48 +03:00
for ( const CGObjectInstance * obj : myCb - > getVisitableObjs ( pos , false ) )
2012-04-14 10:22:08 +03:00
{
if ( includeOwned | | obj - > tempOwner ! = playerID )
out . push_back ( obj ) ;
}
} ) ;
2012-02-14 21:04:45 +03:00
}
2015-03-08 16:38:09 +02:00
void VCAI : : retreiveVisitableObjs ( )
2013-12-25 16:38:20 +03:00
{
foreach_tile_pos ( [ & ] ( const int3 & pos )
{
for ( const CGObjectInstance * obj : myCb - > getVisitableObjs ( pos , false ) )
{
2015-03-08 16:56:59 +02:00
if ( obj - > tempOwner ! = playerID )
2015-03-08 16:38:09 +02:00
addVisitableObj ( obj ) ;
2013-12-25 16:38:20 +03:00
}
} ) ;
}
2012-02-14 21:04:45 +03:00
std : : vector < const CGObjectInstance * > VCAI : : getFlaggedObjects ( ) const
{
std : : vector < const CGObjectInstance * > ret ;
2016-08-11 02:58:24 +02:00
for ( const CGObjectInstance * obj : visitableObjs )
2012-02-14 21:04:45 +03:00
{
2016-08-11 11:19:08 +02:00
if ( obj - > tempOwner = = playerID )
2016-08-11 02:58:24 +02:00
ret . push_back ( obj ) ;
}
2012-02-14 21:04:45 +03:00
return ret ;
}
void VCAI : : addVisitableObj ( const CGObjectInstance * obj )
{
2013-12-25 16:38:20 +03:00
visitableObjs . insert ( obj ) ;
2012-02-14 21:04:45 +03:00
helperObjInfo [ obj ] = ObjInfo ( obj ) ;
2015-03-08 16:38:09 +02:00
// All teleport objects seen automatically assigned to appropriate channels
auto teleportObj = dynamic_cast < const CGTeleport * > ( obj ) ;
if ( teleportObj )
CGTeleport : : addToChannel ( knownTeleportChannels , teleportObj ) ;
2012-02-14 21:04:45 +03:00
}
const CGObjectInstance * VCAI : : lookForArt ( int aid ) const
{
2013-06-29 16:05:48 +03:00
for ( const CGObjectInstance * obj : ai - > visitableObjs )
2012-02-14 21:04:45 +03:00
{
2015-12-08 06:33:13 +02:00
if ( obj - > ID = = Obj : : ARTIFACT & & obj - > subID = = aid )
2012-02-14 21:04:45 +03:00
return obj ;
}
2013-06-26 14:18:27 +03:00
return nullptr ;
2012-02-14 21:04:45 +03:00
//TODO what if more than one artifact is available? return them all or some slection criteria
}
bool VCAI : : isAccessible ( const int3 & pos )
{
//TODO precalculate for speed
2013-06-29 16:05:48 +03:00
for ( const CGHeroInstance * h : cb - > getHeroesInfo ( ) )
2012-02-14 21:04:45 +03:00
{
if ( isAccessibleForHero ( pos , h ) )
return true ;
}
return false ;
}
2012-07-01 02:48:40 +03:00
HeroPtr VCAI : : getHeroWithGrail ( ) const
2012-02-14 21:04:45 +03:00
{
2013-06-29 16:05:48 +03:00
for ( const CGHeroInstance * h : cb - > getHeroesInfo ( ) )
2012-02-14 21:04:45 +03:00
if ( h - > hasArt ( 2 ) ) //grail
return h ;
2013-06-26 14:18:27 +03:00
return nullptr ;
2012-02-14 21:04:45 +03:00
}
2013-06-26 14:18:27 +03:00
const CGObjectInstance * VCAI : : getUnvisitedObj ( const std : : function < bool ( const CGObjectInstance * ) > & predicate )
2012-02-14 21:04:45 +03:00
{
//TODO smarter definition of unvisited
2013-06-29 16:05:48 +03:00
for ( const CGObjectInstance * obj : visitableObjs )
2012-02-14 21:04:45 +03:00
if ( predicate ( obj ) & & ! vstd : : contains ( alreadyVisited , obj ) )
return obj ;
2013-06-26 14:18:27 +03:00
return nullptr ;
2012-02-14 21:04:45 +03:00
}
2017-07-15 13:08:20 +02:00
bool VCAI : : isAccessibleForHero ( const int3 & pos , HeroPtr h , bool includeAllies ) const
2012-02-14 21:04:45 +03:00
{
2012-05-09 10:56:39 +03:00
if ( ! includeAllies )
{ //don't visit tile occupied by allied hero
2013-06-29 16:05:48 +03:00
for ( auto obj : cb - > getVisitableObjs ( pos ) )
2012-05-09 10:56:39 +03:00
{
2013-12-25 01:01:16 +03:00
if ( obj - > ID = = Obj : : HERO & &
cb - > getPlayerRelations ( ai - > playerID , obj - > tempOwner ) ! = PlayerRelations : : ENEMIES & &
obj ! = h . get ( ) )
2012-05-09 10:56:39 +03:00
return false ;
}
}
2014-09-21 16:42:08 +03:00
return cb - > getPathsInfo ( h . get ( ) ) - > getPathInfo ( pos ) - > reachable ( ) ;
2012-02-14 21:04:45 +03:00
}
2012-07-01 02:48:40 +03:00
bool VCAI : : moveHeroToTile ( int3 dst , HeroPtr h )
2012-02-14 21:04:45 +03:00
{
2016-11-28 20:29:11 +02:00
//TODO: consider if blockVisit objects change something in our checks: AIUtility::isBlockVisitObj()
2015-03-08 16:47:58 +02:00
auto afterMovementCheck = [ & ] ( ) - > void
{
waitTillFree ( ) ; //movement may cause battle or blocking dialog
if ( ! h )
{
lostHero ( h ) ;
2015-03-10 02:23:36 +02:00
teleportChannelProbingList . clear ( ) ;
2015-03-08 16:47:58 +02:00
if ( status . channelProbing ( ) ) // if hero lost during channel probing we need to switch this mode off
status . setChannelProbing ( false ) ;
2015-03-09 16:09:34 +02:00
throw cannotFulfillGoalException ( " Hero was lost! " ) ;
2015-03-08 16:47:58 +02:00
}
} ;
2017-08-11 19:03:05 +02:00
logAi - > debug ( " Moving hero %s to tile %s " , h - > name , dst . toString ( ) ) ;
2012-02-14 21:04:45 +03:00
int3 startHpos = h - > visitablePos ( ) ;
bool ret = false ;
if ( startHpos = = dst )
{
2013-11-24 11:21:51 +03:00
//FIXME: this assertion fails also if AI moves onto defeated guarded object
2012-04-14 10:22:08 +03:00
assert ( cb - > getVisitableObjs ( dst ) . size ( ) > 1 ) ; //there's no point in revisiting tile where there is no visitable object
2012-07-01 02:48:40 +03:00
cb - > moveHero ( * h , CGHeroInstance : : convertPosition ( dst , true ) ) ;
2015-03-08 16:47:58 +02:00
afterMovementCheck ( ) ; // TODO: is it feasible to hero get killed there if game work properly?
// not sure if AI can currently reconsider to attack bank while staying on it. Check issue 2084 on mantis for more information.
2012-02-14 21:04:45 +03:00
ret = true ;
}
else
{
CGPath path ;
2015-11-02 10:14:32 +02:00
cb - > getPathsInfo ( h . get ( ) ) - > getPath ( path , dst ) ;
2012-02-14 21:04:45 +03:00
if ( path . nodes . empty ( ) )
{
2017-08-11 19:03:05 +02:00
logAi - > error ( " Hero %s cannot reach %s. " , h - > name , dst . toString ( ) ) ;
2013-12-26 12:53:37 +03:00
throw goalFulfilledException ( sptr ( Goals : : VisitTile ( dst ) . sethero ( h ) ) ) ;
2012-02-14 21:04:45 +03:00
}
2015-12-11 08:42:30 +02:00
int i = path . nodes . size ( ) - 1 ;
2012-02-14 21:04:45 +03:00
2015-03-10 11:06:45 +02:00
auto getObj = [ & ] ( int3 coord , bool ignoreHero )
2015-03-08 16:47:58 +02:00
{
2015-08-30 15:15:04 +02:00
auto tile = cb - > getTile ( coord , false ) ;
assert ( tile ) ;
return tile - > topVisitableObj ( ignoreHero ) ;
//return cb->getTile(coord,false)->topVisitableObj(ignoreHero);
2015-03-08 16:47:58 +02:00
} ;
2015-12-11 08:42:30 +02:00
auto isTeleportAction = [ & ] ( CGPathNode : : ENodeAction action ) - > bool
{
if ( action ! = CGPathNode : : TELEPORT_NORMAL & &
action ! = CGPathNode : : TELEPORT_BLOCKING_VISIT & &
action ! = CGPathNode : : TELEPORT_BATTLE )
{
return false ;
}
return true ;
} ;
auto getDestTeleportObj = [ & ] ( const CGObjectInstance * currentObject , const CGObjectInstance * nextObjectTop , const CGObjectInstance * nextObject ) - > const CGObjectInstance *
{
if ( CGTeleport : : isConnected ( currentObject , nextObjectTop ) )
return nextObjectTop ;
if ( nextObjectTop & & nextObjectTop - > ID = = Obj : : HERO & &
CGTeleport : : isConnected ( currentObject , nextObject ) )
{
return nextObject ;
}
return nullptr ;
} ;
2015-03-10 11:06:45 +02:00
auto doMovement = [ & ] ( int3 dst , bool transit )
2015-03-08 16:47:58 +02:00
{
cb - > moveHero ( * h , CGHeroInstance : : convertPosition ( dst , true ) , transit ) ;
} ;
2015-12-03 16:20:03 +02:00
auto doTeleportMovement = [ & ] ( ObjectInstanceID exitId , int3 exitPos )
2015-03-08 16:47:58 +02:00
{
destinationTeleport = exitId ;
2015-12-03 20:18:40 +02:00
if ( exitPos . valid ( ) )
destinationTeleportPos = CGHeroInstance : : convertPosition ( exitPos , true ) ;
2015-12-03 16:20:03 +02:00
cb - > moveHero ( * h , h - > pos ) ;
2015-03-08 16:47:58 +02:00
destinationTeleport = ObjectInstanceID ( ) ;
2015-12-04 00:54:25 +02:00
destinationTeleportPos = int3 ( - 1 ) ;
2015-03-08 16:47:58 +02:00
afterMovementCheck ( ) ;
} ;
auto doChannelProbing = [ & ] ( ) - > void
{
2015-12-04 04:30:43 +02:00
auto currentPos = CGHeroInstance : : convertPosition ( h - > pos , false ) ;
auto currentExit = getObj ( currentPos , true ) - > id ;
2015-03-08 16:47:58 +02:00
status . setChannelProbing ( true ) ;
for ( auto exit : teleportChannelProbingList )
2015-12-04 00:54:25 +02:00
doTeleportMovement ( exit , int3 ( - 1 ) ) ;
2015-03-08 16:47:58 +02:00
teleportChannelProbingList . clear ( ) ;
status . setChannelProbing ( false ) ;
2015-12-04 04:30:43 +02:00
doTeleportMovement ( currentExit , currentPos ) ;
2015-03-08 16:47:58 +02:00
} ;
2012-02-14 21:04:45 +03:00
for ( ; i > 0 ; i - - )
{
2015-03-08 16:47:58 +02:00
int3 currentCoord = path . nodes [ i ] . coord ;
int3 nextCoord = path . nodes [ i - 1 ] . coord ;
auto currentObject = getObj ( currentCoord , currentCoord = = CGHeroInstance : : convertPosition ( h - > pos , false ) ) ;
2015-12-11 08:42:30 +02:00
auto nextObjectTop = getObj ( nextCoord , false ) ;
auto nextObject = getObj ( nextCoord , true ) ;
auto destTeleportObj = getDestTeleportObj ( currentObject , nextObjectTop , nextObject ) ;
if ( isTeleportAction ( path . nodes [ i - 1 ] . action ) & & destTeleportObj ! = nullptr )
2015-03-08 16:47:58 +02:00
{ //we use special login if hero standing on teleporter it's mean we need
2015-12-11 08:42:30 +02:00
doTeleportMovement ( destTeleportObj - > id , nextCoord ) ;
2015-03-08 16:47:58 +02:00
if ( teleportChannelProbingList . size ( ) )
doChannelProbing ( ) ;
2016-12-19 22:46:01 +02:00
markObjectVisited ( destTeleportObj ) ; //FIXME: Monoliths are not correctly visited
2015-03-08 16:47:58 +02:00
continue ;
}
2012-02-14 21:04:45 +03:00
//stop sending move requests if the next node can't be reached at the current turn (hero exhausted his move points)
if ( path . nodes [ i - 1 ] . turns )
{
2012-03-13 15:47:47 +03:00
//blockedHeroes.insert(h); //to avoid attempts of moving heroes with very little MPs
2012-02-14 21:04:45 +03:00
break ;
}
int3 endpos = path . nodes [ i - 1 ] . coord ;
if ( endpos = = h - > visitablePos ( ) )
continue ;
2013-12-27 16:20:40 +03:00
2015-03-08 16:47:58 +02:00
if ( ( i - 2 > = 0 ) // Check there is node after next one; otherwise transit is pointless
2015-12-11 08:42:30 +02:00
& & ( CGTeleport : : isConnected ( nextObjectTop , getObj ( path . nodes [ i - 2 ] . coord , false ) )
| | CGTeleport : : isTeleport ( nextObjectTop ) ) )
2015-03-08 16:47:58 +02:00
{ // Hero should be able to go through object if it's allow transit
doMovement ( endpos , true ) ;
2012-03-13 15:47:47 +03:00
}
2015-11-05 09:02:13 +02:00
else if ( path . nodes [ i - 1 ] . layer = = EPathfindingLayer : : AIR )
doMovement ( endpos , true ) ;
2015-03-08 16:47:58 +02:00
else
2015-03-10 11:06:45 +02:00
doMovement ( endpos , false ) ;
2015-03-08 16:47:58 +02:00
afterMovementCheck ( ) ;
2012-02-14 21:04:45 +03:00
2015-03-08 16:47:58 +02:00
if ( teleportChannelProbingList . size ( ) )
doChannelProbing ( ) ;
2012-02-14 21:04:45 +03:00
}
}
2014-05-18 14:13:31 +03:00
if ( h )
2012-03-29 21:26:06 +03:00
{
2015-12-29 01:14:08 +02:00
if ( auto visitedObject = vstd : : frontOrNull ( cb - > getVisitableObjs ( h - > visitablePos ( ) ) ) ) //we stand on something interesting
2014-05-18 14:13:31 +03:00
{
if ( visitedObject ! = * h )
performObjectInteraction ( visitedObject , h ) ;
}
2012-03-29 21:26:06 +03:00
}
2012-08-26 12:07:48 +03:00
if ( h ) //we could have lost hero after last move
2012-05-05 11:57:32 +03:00
{
2013-12-26 12:53:37 +03:00
completeGoal ( sptr ( Goals : : VisitTile ( dst ) . sethero ( h ) ) ) ; //we stepped on some tile, anyway
2015-10-25 12:16:43 +02:00
completeGoal ( sptr ( Goals : : ClearWayTo ( dst ) . sethero ( h ) ) ) ;
2016-01-18 21:57:19 +02:00
ret = ( dst = = h - > visitablePos ( ) ) ;
2015-12-29 01:14:08 +02:00
if ( ! ret ) //reserve object we are heading towards
2013-12-23 23:46:01 +03:00
{
2015-12-29 01:14:08 +02:00
auto obj = vstd : : frontOrNull ( cb - > getVisitableObjs ( dst ) ) ;
if ( obj & & obj ! = * h )
2013-12-23 23:46:01 +03:00
reserveObject ( h , obj ) ;
}
2015-12-29 01:14:08 +02:00
if ( startHpos = = h - > visitablePos ( ) & & ! ret ) //we didn't move and didn't reach the target
2012-05-08 11:10:40 +03:00
{
2015-12-29 01:14:08 +02:00
vstd : : erase_if_present ( lockedHeroes , h ) ; //hero seemingly is confused
2013-12-26 12:53:37 +03:00
throw cannotFulfillGoalException ( " Invalid path found! " ) ; //FIXME: should never happen
2012-05-08 11:10:40 +03:00
}
2016-11-27 19:10:20 +02:00
evaluateGoal ( h ) ; //new hero position means new game situation
2017-08-11 19:03:05 +02:00
logAi - > debug ( " Hero %s moved from %s to %s. Returning %d. " , h - > name , startHpos . toString ( ) , h - > visitablePos ( ) . toString ( ) , ret ) ;
2012-05-05 11:57:32 +03:00
}
2012-02-14 21:04:45 +03:00
return ret ;
}
2013-11-23 21:16:25 +03:00
void VCAI : : tryRealize ( Goals : : Explore & g )
2013-10-19 08:52:30 +03:00
{
2016-01-18 21:57:19 +02:00
throw cannotFulfillGoalException ( " EXPLORE is not an elementar goal! " ) ;
2013-10-19 08:52:30 +03:00
}
2012-02-14 21:04:45 +03:00
2013-11-23 21:16:25 +03:00
void VCAI : : tryRealize ( Goals : : RecruitHero & g )
2012-02-14 21:04:45 +03:00
{
2013-10-19 08:52:30 +03:00
if ( const CGTownInstance * t = findTownWithTavern ( ) )
2012-02-14 21:04:45 +03:00
{
2013-10-19 08:52:30 +03:00
recruitHero ( t , true ) ;
//TODO try to free way to blocked town
//TODO: adventure map tavern or prison?
}
}
2012-02-14 21:04:45 +03:00
2013-11-23 21:16:25 +03:00
void VCAI : : tryRealize ( Goals : : VisitTile & g )
2013-10-19 08:52:30 +03:00
{
if ( ! g . hero - > movement )
throw cannotFulfillGoalException ( " Cannot visit tile: hero is out of MPs! " ) ;
if ( g . tile = = g . hero - > visitablePos ( ) & & cb - > getVisitableObjs ( g . hero - > visitablePos ( ) ) . size ( ) < 2 )
{
2017-08-11 19:03:05 +02:00
logAi - > warn ( " Why do I want to move hero %s to tile %s? Already standing on that tile! " , g . hero - > name , g . tile . toString ( ) ) ;
2013-11-23 21:16:25 +03:00
throw goalFulfilledException ( sptr ( g ) ) ;
2013-10-19 08:52:30 +03:00
}
2013-12-25 01:01:16 +03:00
if ( ai - > moveHeroToTile ( g . tile , g . hero . get ( ) ) )
{
throw goalFulfilledException ( sptr ( g ) ) ;
}
2013-10-19 08:52:30 +03:00
}
2012-02-14 21:04:45 +03:00
2013-11-23 21:16:25 +03:00
void VCAI : : tryRealize ( Goals : : VisitHero & g )
2013-10-19 08:52:30 +03:00
{
if ( ! g . hero - > movement )
2013-12-27 16:20:40 +03:00
throw cannotFulfillGoalException ( " Cannot visit target hero: hero is out of MPs! " ) ;
const CGObjectInstance * obj = cb - > getObj ( ObjectInstanceID ( g . objid ) ) ;
if ( obj )
2013-10-19 08:52:30 +03:00
{
2013-12-27 16:20:40 +03:00
if ( ai - > moveHeroToTile ( obj - > visitablePos ( ) , g . hero . get ( ) ) )
{
throw goalFulfilledException ( sptr ( g ) ) ;
}
2013-10-19 08:52:30 +03:00
}
2013-12-27 16:20:40 +03:00
else
throw cannotFulfillGoalException ( " Cannot visit hero: object not found! " ) ;
2013-10-19 08:52:30 +03:00
}
2012-02-14 21:04:45 +03:00
2013-11-23 21:16:25 +03:00
void VCAI : : tryRealize ( Goals : : BuildThis & g )
2013-10-19 08:52:30 +03:00
{
const CGTownInstance * t = g . town ;
if ( ! t & & g . hero )
t = g . hero - > visitedTown ;
if ( ! t )
{
for ( const CGTownInstance * t : cb - > getTownsInfo ( ) )
{
switch ( cb - > canBuildStructure ( t , BuildingID ( g . bid ) ) )
2012-02-14 21:04:45 +03:00
{
2013-10-19 08:52:30 +03:00
case EBuildingState : : ALLOWED :
2013-02-11 22:11:34 +03:00
cb - > buildBuilding ( t , BuildingID ( g . bid ) ) ;
2012-02-14 21:04:45 +03:00
return ;
2013-10-19 08:52:30 +03:00
default :
break ;
2012-02-14 21:04:45 +03:00
}
}
2013-10-19 08:52:30 +03:00
}
else if ( cb - > canBuildStructure ( t , BuildingID ( g . bid ) ) = = EBuildingState : : ALLOWED )
{
cb - > buildBuilding ( t , BuildingID ( g . bid ) ) ;
return ;
}
throw cannotFulfillGoalException ( " Cannot build a given structure! " ) ;
}
2012-02-16 20:10:58 +03:00
2013-11-23 21:16:25 +03:00
void VCAI : : tryRealize ( Goals : : DigAtTile & g )
2013-10-19 08:52:30 +03:00
{
assert ( g . hero - > visitablePos ( ) = = g . tile ) ; //surely we want to crash here?
2015-11-29 11:32:06 +02:00
if ( g . hero - > diggingStatus ( ) = = EDiggingStatus : : CAN_DIG )
2013-10-19 08:52:30 +03:00
{
cb - > dig ( g . hero . get ( ) ) ;
2013-12-26 12:53:37 +03:00
completeGoal ( sptr ( g ) ) ; // finished digging
2013-10-19 08:52:30 +03:00
}
else
{
2013-11-23 21:16:25 +03:00
ai - > lockedHeroes [ g . hero ] = sptr ( g ) ; //hero who tries to dig shouldn't do anything else
2013-10-19 08:52:30 +03:00
throw cannotFulfillGoalException ( " A hero can't dig! \n " ) ;
}
}
2012-09-28 19:20:18 +03:00
2013-11-23 21:16:25 +03:00
void VCAI : : tryRealize ( Goals : : CollectRes & g )
2013-10-19 08:52:30 +03:00
{
if ( cb - > getResourceAmount ( static_cast < Res : : ERes > ( g . resID ) ) > = g . value )
throw cannotFulfillGoalException ( " Goal is already fulfilled! " ) ;
if ( const CGObjectInstance * obj = cb - > getObj ( ObjectInstanceID ( g . objid ) , false ) )
{
if ( const IMarket * m = IMarket : : castFrom ( obj , false ) )
2012-02-14 21:04:45 +03:00
{
2013-10-19 08:52:30 +03:00
for ( Res : : ERes i = Res : : WOOD ; i < = Res : : GOLD ; vstd : : advance ( i , 1 ) )
2012-02-14 21:04:45 +03:00
{
2013-10-19 08:52:30 +03:00
if ( i = = g . resID ) continue ;
int toGive , toGet ;
m - > getOffer ( i , g . resID , toGive , toGet , EMarketMode : : RESOURCE_RESOURCE ) ;
toGive = toGive * ( cb - > getResourceAmount ( i ) / toGive ) ;
//TODO trade only as much as needed
cb - > trade ( obj , EMarketMode : : RESOURCE_RESOURCE , i , g . resID , toGive ) ;
if ( cb - > getResourceAmount ( static_cast < Res : : ERes > ( g . resID ) ) > = g . value )
return ;
2015-10-31 17:04:06 +02:00
}
2012-09-28 19:20:18 +03:00
2013-10-19 08:52:30 +03:00
throw cannotFulfillGoalException ( " I cannot get needed resources by trade! " ) ;
2012-02-14 21:04:45 +03:00
}
else
{
2013-10-19 08:52:30 +03:00
throw cannotFulfillGoalException ( " I don't know how to use this object to raise resources! " ) ;
2012-02-14 21:04:45 +03:00
}
2013-10-19 08:52:30 +03:00
}
else
{
saving [ g . resID ] = 1 ;
throw cannotFulfillGoalException ( " No object that could be used to raise resources! " ) ;
}
}
2012-02-14 21:04:45 +03:00
2013-11-23 21:16:25 +03:00
void VCAI : : tryRealize ( Goals : : Build & g )
2013-10-19 08:52:30 +03:00
{
2013-12-26 12:53:37 +03:00
for ( const CGTownInstance * t : cb - > getTownsInfo ( ) )
{
2016-08-15 07:30:06 +02:00
logAi - > debug ( " Looking into %s " , t - > name ) ;
2013-12-26 12:53:37 +03:00
buildStructure ( t ) ;
if ( ! ai - > primaryHero ( ) | |
( t - > getArmyStrength ( ) > ai - > primaryHero ( ) - > getArmyStrength ( ) * 2 & & ! isAccessibleForHero ( t - > visitablePos ( ) , ai - > primaryHero ( ) ) ) )
{
recruitHero ( t ) ;
buildArmyIn ( t ) ;
}
}
2013-10-19 08:52:30 +03:00
throw cannotFulfillGoalException ( " BUILD has been realized as much as possible. " ) ;
}
2013-11-23 21:16:25 +03:00
void VCAI : : tryRealize ( Goals : : Invalid & g )
2013-10-19 08:52:30 +03:00
{
throw cannotFulfillGoalException ( " I don't know how to fulfill this! " ) ;
}
2012-02-14 21:04:45 +03:00
2013-11-23 21:16:25 +03:00
void VCAI : : tryRealize ( Goals : : AbstractGoal & g )
2013-10-19 08:52:30 +03:00
{
2016-08-15 07:30:06 +02:00
logAi - > debug ( " Attempting realizing goal with code %s " , g . name ( ) ) ;
2016-03-12 03:41:27 +02:00
throw cannotFulfillGoalException ( " Unknown type of goal ! " ) ;
2012-02-14 21:04:45 +03:00
}
const CGTownInstance * VCAI : : findTownWithTavern ( ) const
{
2013-06-29 16:05:48 +03:00
for ( const CGTownInstance * t : cb - > getTownsInfo ( ) )
2013-02-11 02:24:57 +03:00
if ( t - > hasBuilt ( BuildingID : : TAVERN ) & & ! t - > visitingHero )
2012-02-14 21:04:45 +03:00
return t ;
2013-06-26 14:18:27 +03:00
return nullptr ;
2012-02-14 21:04:45 +03:00
}
2014-02-17 10:36:03 +03:00
Goals : : TSubgoal VCAI : : getGoal ( HeroPtr h ) const
{
auto it = lockedHeroes . find ( h ) ;
if ( it ! = lockedHeroes . end ( ) )
return it - > second ;
else
return sptr ( Goals : : Invalid ( ) ) ;
}
2012-07-01 02:48:40 +03:00
std : : vector < HeroPtr > VCAI : : getUnblockedHeroes ( ) const
2012-02-14 21:04:45 +03:00
{
2012-07-01 02:48:40 +03:00
std : : vector < HeroPtr > ret ;
2013-12-26 12:53:37 +03:00
for ( auto h : cb - > getHeroesInfo ( ) )
2012-03-13 15:47:47 +03:00
{
2013-12-26 12:53:37 +03:00
//&& !vstd::contains(lockedHeroes, h)
//at this point we assume heroes exhausted their locked goals
if ( canAct ( h ) )
ret . push_back ( h ) ;
2012-03-13 15:47:47 +03:00
}
2012-02-14 21:04:45 +03:00
return ret ;
}
2013-12-26 12:53:37 +03:00
bool VCAI : : canAct ( HeroPtr h ) const
{
auto mission = lockedHeroes . find ( h ) ;
if ( mission ! = lockedHeroes . end ( ) )
{
//FIXME: I'm afraid there can be other conditions when heroes can act but not move :?
if ( mission - > second - > goalType = = Goals : : DIG_AT_TILE & & ! mission - > second - > isElementar )
return false ;
}
return h - > movement ;
}
2012-07-01 02:48:40 +03:00
HeroPtr VCAI : : primaryHero ( ) const
2012-02-14 21:04:45 +03:00
{
auto hs = cb - > getHeroesInfo ( ) ;
boost : : sort ( hs , compareHeroStrength ) ;
if ( hs . empty ( ) )
2013-06-26 14:18:27 +03:00
return nullptr ;
2012-02-14 21:04:45 +03:00
return hs . back ( ) ;
}
void VCAI : : endTurn ( )
{
2016-09-16 04:45:00 +02:00
logAi - > info ( " Player %d (%s) ends turn " , playerID , playerID . getStr ( ) ) ;
2012-02-14 21:04:45 +03:00
if ( ! status . haveTurn ( ) )
{
2016-08-15 07:30:06 +02:00
logAi - > error ( " Not having turn at the end of turn??? " ) ;
2012-02-14 21:04:45 +03:00
}
2017-08-11 19:03:05 +02:00
logAi - > debug ( " Resources at the end of turn: %s " , cb - > getResourceAmount ( ) . toString ( ) ) ;
2012-02-14 21:04:45 +03:00
2012-02-16 20:10:58 +03:00
do
2012-02-14 21:04:45 +03:00
{
cb - > endTurn ( ) ;
} while ( status . haveTurn ( ) ) ; //for some reasons, our request may fail -> stop requesting end of turn only after we've received a confirmation that it's over
2016-09-16 04:45:00 +02:00
logGlobal - > info ( " Player %d (%s) ended turn " , playerID , playerID . getStr ( ) ) ;
2012-02-14 21:04:45 +03:00
}
2013-11-23 15:30:10 +03:00
void VCAI : : striveToGoal ( Goals : : TSubgoal ultimateGoal )
2012-02-14 21:04:45 +03:00
{
2013-11-23 15:30:10 +03:00
if ( ultimateGoal - > invalid ( ) )
2012-03-13 23:33:00 +03:00
return ;
2012-05-08 11:10:40 +03:00
2013-11-24 18:30:17 +03:00
//we are looking for abstract goals
auto abstractGoal = striveToGoalInternal ( ultimateGoal , false ) ;
if ( abstractGoal - > invalid ( ) )
return ;
2014-02-19 19:23:47 +03:00
//we received abstract goal, need to find concrete goals
2013-11-24 18:30:17 +03:00
striveToGoalInternal ( abstractGoal , true ) ;
//TODO: save abstract goals not related to hero
}
Goals : : TSubgoal VCAI : : striveToGoalInternal ( Goals : : TSubgoal ultimateGoal , bool onlyAbstract )
{
2014-02-19 19:23:47 +03:00
const int searchDepth = 30 ;
const int searchDepth2 = searchDepth - 2 ;
2013-11-23 15:30:10 +03:00
Goals : : TSubgoal abstractGoal = sptr ( Goals : : Invalid ( ) ) ;
2012-05-08 11:10:40 +03:00
2012-02-14 21:04:45 +03:00
while ( 1 )
{
2013-11-24 18:30:17 +03:00
Goals : : TSubgoal goal = ultimateGoal ;
2017-08-10 18:39:27 +02:00
logAi - > debug ( " Striving to goal of type %s " , ultimateGoal - > name ( ) ) ;
2014-02-19 19:23:47 +03:00
int maxGoals = searchDepth ; //preventing deadlock for mutually dependent goals
2013-11-24 18:30:17 +03:00
while ( ! goal - > isElementar & & maxGoals & & ( onlyAbstract | | ! goal - > isAbstract ) )
2012-02-14 21:04:45 +03:00
{
2017-08-10 18:39:27 +02:00
logAi - > debug ( " Considering goal %s " , goal - > name ( ) ) ;
2012-02-14 21:04:45 +03:00
try
{
boost : : this_thread : : interruption_point ( ) ;
2013-11-24 18:30:17 +03:00
goal = goal - > whatToDoToAchieve ( ) ;
2012-03-13 15:47:47 +03:00
- - maxGoals ;
2014-02-19 19:23:47 +03:00
if ( * goal = = * ultimateGoal ) //compare objects by value
throw cannotFulfillGoalException ( " Goal dependency loop detected! " ) ;
}
catch ( goalFulfilledException & e )
{
//it is impossible to continue some goals (like exploration, for example)
completeGoal ( goal ) ;
2017-08-10 18:39:27 +02:00
logAi - > debug ( " Goal %s decomposition failed: goal was completed as much as possible " , goal - > name ( ) ) ;
2014-02-19 19:23:47 +03:00
return sptr ( Goals : : Invalid ( ) ) ;
2012-02-14 21:04:45 +03:00
}
catch ( std : : exception & e )
{
2017-08-10 19:17:10 +02:00
logAi - > debug ( " Goal %s decomposition failed: %s " , goal - > name ( ) , e . what ( ) ) ;
2013-11-24 18:30:17 +03:00
return sptr ( Goals : : Invalid ( ) ) ;
2012-02-14 21:04:45 +03:00
}
}
try
{
boost : : this_thread : : interruption_point ( ) ;
2012-05-08 11:10:40 +03:00
2017-06-11 08:01:41 +02:00
if ( ! maxGoals ) //we counted down to 0 and found no solution
2013-02-23 22:13:15 +03:00
{
2017-06-11 08:01:41 +02:00
if ( ultimateGoal - > hero ) // we seemingly don't know what to do with hero, free him
vstd : : erase_if_present ( lockedHeroes , ultimateGoal - > hero ) ;
2013-02-24 01:43:02 +03:00
std : : runtime_error e ( " Too many subgoals, don't know what to do " ) ;
2013-02-23 22:13:15 +03:00
throw ( e ) ;
}
2017-06-11 08:01:41 +02:00
else //we can proceed
2012-03-13 15:47:47 +03:00
{
2017-06-11 08:01:41 +02:00
if ( goal - > hero ) //lock this hero to fulfill ultimate goal
2012-03-13 15:47:47 +03:00
{
2013-11-23 21:16:25 +03:00
setGoal ( goal - > hero , goal ) ;
2012-03-13 15:47:47 +03:00
}
}
2012-05-08 11:10:40 +03:00
2013-11-10 00:29:46 +03:00
if ( goal - > isAbstract )
2012-05-08 11:10:40 +03:00
{
abstractGoal = goal ; //allow only one abstract goal per call
2017-08-10 18:39:27 +02:00
logAi - > debug ( " Choosing abstract goal %s " , goal - > name ( ) ) ;
2012-05-08 11:10:40 +03:00
break ;
}
else
2014-02-05 00:49:04 +03:00
{
2017-08-10 19:17:10 +02:00
logAi - > debug ( " Trying to realize %s (value %2.3f) " , goal - > name ( ) , goal - > priority ) ;
2013-11-23 21:16:25 +03:00
goal - > accept ( this ) ;
2014-02-05 00:49:04 +03:00
}
2012-05-08 11:10:40 +03:00
2012-02-14 21:04:45 +03:00
boost : : this_thread : : interruption_point ( ) ;
}
catch ( boost : : thread_interrupted & e )
{
2017-08-10 18:39:27 +02:00
logAi - > debug ( " Player %d: Making turn thread received an interruption! " , playerID ) ;
2012-02-14 21:04:45 +03:00
throw ; //rethrow, we want to truly end this thread
}
2012-05-05 09:42:18 +03:00
catch ( goalFulfilledException & e )
{
2014-02-19 19:23:47 +03:00
//the goal was completed successfully
2013-11-23 21:16:25 +03:00
completeGoal ( goal ) ;
2014-02-19 19:23:47 +03:00
//completed goal was main goal //TODO: find better condition
if ( ultimateGoal - > fulfillsMe ( goal ) | | maxGoals > searchDepth2 )
2015-10-31 17:04:06 +02:00
return sptr ( Goals : : Invalid ( ) ) ;
2012-05-05 09:42:18 +03:00
}
2012-02-14 21:04:45 +03:00
catch ( std : : exception & e )
{
2017-08-10 19:17:10 +02:00
logAi - > debug ( " Failed to realize subgoal of type %s (greater goal type was %s), I will stop. " , goal - > name ( ) , ultimateGoal - > name ( ) ) ;
2017-08-10 18:39:27 +02:00
logAi - > debug ( " The error message was: %s " , e . what ( ) ) ;
2012-02-14 21:04:45 +03:00
break ;
}
}
2013-11-24 18:30:17 +03:00
return abstractGoal ;
2012-02-14 21:04:45 +03:00
}
2012-07-18 13:10:14 +03:00
void VCAI : : striveToQuest ( const QuestInfo & q )
{
2013-12-22 00:31:28 +03:00
if ( q . quest - > missionType & & q . quest - > progress ! = CQuest : : COMPLETE )
2012-07-18 13:10:14 +03:00
{
MetaString ms ;
2012-10-03 17:49:29 +03:00
q . quest - > getRolloverText ( ms , false ) ;
2017-08-10 18:39:27 +02:00
logAi - > debug ( " Trying to realize quest: %s " , ms . toString ( ) ) ;
2012-07-21 10:15:53 +03:00
auto heroes = cb - > getHeroesInfo ( ) ;
2012-10-03 17:49:29 +03:00
switch ( q . quest - > missionType )
2012-07-18 13:10:14 +03:00
{
case CQuest : : MISSION_ART :
{
2013-06-29 16:05:48 +03:00
for ( auto hero : heroes ) //TODO: remove duplicated code?
2012-07-21 10:15:53 +03:00
{
2012-10-03 17:49:29 +03:00
if ( q . quest - > checkQuest ( hero ) )
2012-07-21 10:15:53 +03:00
{
2013-11-23 15:30:10 +03:00
striveToGoal ( sptr ( Goals : : GetObj ( q . obj - > id . getNum ( ) ) . sethero ( hero ) ) ) ;
2012-07-21 10:15:53 +03:00
return ;
}
}
2013-06-29 16:05:48 +03:00
for ( auto art : q . quest - > m5arts )
2012-07-18 13:10:14 +03:00
{
2013-11-23 15:30:10 +03:00
striveToGoal ( sptr ( Goals : : GetArtOfType ( art ) ) ) ; //TODO: transport?
2012-07-18 13:10:14 +03:00
}
break ;
}
case CQuest : : MISSION_HERO :
{
//striveToGoal (CGoal(RECRUIT_HERO));
2013-06-29 16:05:48 +03:00
for ( auto hero : heroes )
2012-07-18 13:10:14 +03:00
{
2012-10-03 17:49:29 +03:00
if ( q . quest - > checkQuest ( hero ) )
2012-07-18 13:10:14 +03:00
{
2013-11-23 15:30:10 +03:00
striveToGoal ( sptr ( Goals : : GetObj ( q . obj - > id . getNum ( ) ) . sethero ( hero ) ) ) ;
2012-07-21 10:15:53 +03:00
return ;
2012-07-18 13:10:14 +03:00
}
}
2013-11-23 15:30:10 +03:00
striveToGoal ( sptr ( Goals : : FindObj ( Obj : : PRISON ) ) ) ; //rule of a thumb - quest heroes usually are locked in prisons
2012-07-21 10:15:53 +03:00
//BNLOG ("Don't know how to recruit hero with id %d\n", q.quest->m13489val);
2012-07-18 13:10:14 +03:00
break ;
}
case CQuest : : MISSION_ARMY :
{
2013-06-29 16:05:48 +03:00
for ( auto hero : heroes )
2012-07-21 10:15:53 +03:00
{
2014-03-23 15:59:03 +03:00
if ( q . quest - > checkQuest ( hero ) ) //very bad info - stacks can be split between multiple heroes :(
2012-07-21 10:15:53 +03:00
{
2013-11-23 15:30:10 +03:00
striveToGoal ( sptr ( Goals : : GetObj ( q . obj - > id . getNum ( ) ) . sethero ( hero ) ) ) ;
2012-07-21 10:15:53 +03:00
return ;
}
}
2013-06-29 16:05:48 +03:00
for ( auto creature : q . quest - > m6creatures )
2012-07-18 13:10:14 +03:00
{
2013-11-23 15:30:10 +03:00
striveToGoal ( sptr ( Goals : : GatherTroops ( creature . type - > idNumber , creature . count ) ) ) ;
2012-07-18 13:10:14 +03:00
}
2012-07-21 10:15:53 +03:00
//TODO: exchange armies... oh my
//BNLOG ("Don't know how to recruit %d of %s\n", (int)(creature.count) % creature.type->namePl);
2012-07-18 13:10:14 +03:00
break ;
}
case CQuest : : MISSION_RESOURCES :
{
2012-08-08 11:27:52 +03:00
if ( heroes . size ( ) )
2012-07-18 13:10:14 +03:00
{
2012-10-03 17:49:29 +03:00
if ( q . quest - > checkQuest ( heroes . front ( ) ) ) //it doesn't matter which hero it is
2012-07-21 10:15:53 +03:00
{
2013-12-25 01:01:16 +03:00
striveToGoal ( sptr ( Goals : : GetObj ( q . obj - > id . getNum ( ) ) ) ) ;
2012-08-08 11:27:52 +03:00
}
else
{
2012-10-03 17:49:29 +03:00
for ( int i = 0 ; i < q . quest - > m7resources . size ( ) ; + + i )
2012-08-08 11:27:52 +03:00
{
2012-10-03 17:49:29 +03:00
if ( q . quest - > m7resources [ i ] )
2013-11-23 15:30:10 +03:00
striveToGoal ( sptr ( Goals : : CollectRes ( i , q . quest - > m7resources [ i ] ) ) ) ;
2012-08-08 11:27:52 +03:00
}
2012-07-21 10:15:53 +03:00
}
2012-07-18 13:10:14 +03:00
}
2012-08-08 11:27:52 +03:00
else
2013-11-23 15:30:10 +03:00
striveToGoal ( sptr ( Goals : : RecruitHero ( ) ) ) ; //FIXME: checkQuest requires any hero belonging to player :(
2012-07-18 13:10:14 +03:00
break ;
}
case CQuest : : MISSION_KILL_HERO :
case CQuest : : MISSION_KILL_CREATURE :
{
2012-10-03 17:49:29 +03:00
auto obj = cb - > getObjByQuestIdentifier ( q . quest - > m13489val ) ;
2012-07-19 12:10:55 +03:00
if ( obj )
2013-11-23 15:30:10 +03:00
striveToGoal ( sptr ( Goals : : GetObj ( obj - > id . getNum ( ) ) ) ) ;
2012-07-19 12:10:55 +03:00
else
2013-12-25 01:01:16 +03:00
striveToGoal ( sptr ( Goals : : GetObj ( q . obj - > id . getNum ( ) ) ) ) ; //visit seer hut
2012-07-18 13:10:14 +03:00
break ;
}
case CQuest : : MISSION_PRIMARY_STAT :
{
auto heroes = cb - > getHeroesInfo ( ) ;
2013-06-29 16:05:48 +03:00
for ( auto hero : heroes )
2012-07-18 13:10:14 +03:00
{
2012-10-03 17:49:29 +03:00
if ( q . quest - > checkQuest ( hero ) )
2012-07-18 13:10:14 +03:00
{
2013-11-23 15:30:10 +03:00
striveToGoal ( sptr ( Goals : : GetObj ( q . obj - > id . getNum ( ) ) . sethero ( hero ) ) ) ;
2012-07-21 10:15:53 +03:00
return ;
2012-07-18 13:10:14 +03:00
}
}
2012-10-03 17:49:29 +03:00
for ( int i = 0 ; i < q . quest - > m2stats . size ( ) ; + + i )
2012-07-18 13:10:14 +03:00
{
2017-08-10 18:39:27 +02:00
logAi - > debug ( " Don't know how to increase primary stat %d " , i ) ;
2012-07-18 13:10:14 +03:00
}
break ;
}
case CQuest : : MISSION_LEVEL :
{
auto heroes = cb - > getHeroesInfo ( ) ;
2013-06-29 16:05:48 +03:00
for ( auto hero : heroes )
2012-07-18 13:10:14 +03:00
{
2012-10-03 17:49:29 +03:00
if ( q . quest - > checkQuest ( hero ) )
2012-07-18 13:10:14 +03:00
{
2013-12-25 01:01:16 +03:00
striveToGoal ( sptr ( Goals : : GetObj ( q . obj - > id . getNum ( ) ) . sethero ( hero ) ) ) ; //TODO: causes infinite loop :/
2012-07-21 10:15:53 +03:00
return ;
2012-07-18 13:10:14 +03:00
}
}
2017-08-10 18:39:27 +02:00
logAi - > debug ( " Don't know how to reach hero level %d " , q . quest - > m13489val ) ;
2012-07-18 13:10:14 +03:00
break ;
}
case CQuest : : MISSION_PLAYER :
{
2013-03-03 20:06:03 +03:00
if ( playerID . getNum ( ) ! = q . quest - > m13489val )
2017-08-10 18:39:27 +02:00
logAi - > debug ( " Can't be player of color %d " , q . quest - > m13489val ) ;
2012-07-18 13:10:14 +03:00
break ;
}
case CQuest : : MISSION_KEYMASTER :
{
2013-11-23 15:30:10 +03:00
striveToGoal ( sptr ( Goals : : FindObj ( Obj : : KEYMASTER , q . obj - > subID ) ) ) ;
2012-07-18 13:10:14 +03:00
break ;
}
}
}
}
2012-02-14 21:04:45 +03:00
void VCAI : : performTypicalActions ( )
{
2017-11-04 23:14:56 +02:00
//TODO: build army only on request
for ( auto t : cb - > getTownsInfo ( ) )
{
buildArmyIn ( t ) ;
}
2013-06-29 16:05:48 +03:00
for ( auto h : getUnblockedHeroes ( ) )
2012-02-14 21:04:45 +03:00
{
2016-09-16 03:52:17 +02:00
if ( ! h ) //hero might be lost. getUnblockedHeroes() called once on start of turn
continue ;
2017-08-10 19:17:10 +02:00
logAi - > debug ( " Looking into %s, MP=%d " , h - > name . c_str ( ) , h - > movement ) ;
2012-07-01 02:48:40 +03:00
makePossibleUpgrades ( * h ) ;
2015-04-07 22:48:35 +02:00
pickBestArtifacts ( * h ) ;
2012-03-26 01:46:14 +03:00
try
{
wander ( h ) ;
}
catch ( std : : exception & e )
{
2017-08-10 18:39:27 +02:00
logAi - > debug ( " Cannot use this hero anymore, received exception: %s " , e . what ( ) ) ;
2012-03-26 01:46:14 +03:00
continue ;
}
2012-02-14 21:04:45 +03:00
}
}
void VCAI : : buildArmyIn ( const CGTownInstance * t )
{
makePossibleUpgrades ( t - > visitingHero ) ;
makePossibleUpgrades ( t ) ;
2014-09-23 14:28:55 +03:00
recruitCreatures ( t , t - > getUpperArmy ( ) ) ;
2012-02-14 21:04:45 +03:00
moveCreaturesToHero ( t ) ;
}
2012-07-01 02:48:40 +03:00
int3 VCAI : : explorationBestNeighbour ( int3 hpos , int radius , HeroPtr h )
2012-02-14 21:04:45 +03:00
{
2015-10-25 09:39:03 +02:00
int3 ourPos = h - > convertPosition ( h - > pos , false ) ;
2012-02-14 21:04:45 +03:00
std : : map < int3 , int > dstToRevealedTiles ;
2016-11-28 20:29:11 +02:00
for ( crint3 dir : int3 : : getDirs ( ) )
{
int3 tile = hpos + dir ;
if ( cb - > isInTheMap ( tile ) )
2015-10-25 09:39:03 +02:00
if ( ourPos ! = dir ) //don't stand in place
2016-11-28 20:29:11 +02:00
if ( isSafeToVisit ( h , tile ) & & isAccessibleForHero ( tile , h ) )
{
if ( isBlockVisitObj ( tile ) )
continue ;
else
dstToRevealedTiles [ tile ] = howManyTilesWillBeDiscovered ( radius , hpos , dir ) ;
}
}
2012-02-14 21:04:45 +03:00
2014-02-16 00:32:49 +03:00
if ( dstToRevealedTiles . empty ( ) ) //yes, it DID happen!
throw cannotFulfillGoalException ( " No neighbour will bring new discoveries! " ) ;
2012-02-14 21:04:45 +03:00
auto best = dstToRevealedTiles . begin ( ) ;
2013-12-21 20:34:59 +03:00
for ( auto i = dstToRevealedTiles . begin ( ) ; i ! = dstToRevealedTiles . end ( ) ; i + + )
2012-02-14 21:04:45 +03:00
{
2014-09-21 16:42:08 +03:00
const CGPathNode * pn = cb - > getPathsInfo ( h . get ( ) ) - > getPathInfo ( i - > first ) ;
2012-02-16 20:10:58 +03:00
//const TerrainTile *t = cb->getTile(i->first);
2013-12-26 12:53:37 +03:00
if ( best - > second < i - > second & & pn - > reachable ( ) & & pn - > accessible = = CGPathNode : : ACCESSIBLE )
2012-02-14 21:04:45 +03:00
best = i ;
}
if ( best - > second )
return best - > first ;
throw cannotFulfillGoalException ( " No neighbour will bring new discoveries! " ) ;
}
2014-02-17 20:28:39 +03:00
int3 VCAI : : explorationNewPoint ( HeroPtr h )
2012-02-14 21:04:45 +03:00
{
2016-01-31 17:01:58 +02:00
int radius = h - > getSightRadius ( ) ;
2015-10-25 09:21:15 +02:00
CCallback * cbp = cb . get ( ) ;
const CGHeroInstance * hero = h . get ( ) ;
2013-12-23 23:46:01 +03:00
std : : vector < std : : vector < int3 > > tiles ; //tiles[distance_to_fow]
2012-02-14 21:04:45 +03:00
tiles . resize ( radius ) ;
foreach_tile_pos ( [ & ] ( const int3 & pos )
{
2014-04-01 14:53:28 +03:00
if ( ! cbp - > isVisible ( pos ) )
2012-02-14 21:04:45 +03:00
tiles [ 0 ] . push_back ( pos ) ;
} ) ;
2014-02-07 23:09:15 +03:00
float bestValue = 0 ; //discovered tile to node distance ratio
2013-12-23 23:46:01 +03:00
int3 bestTile ( - 1 , - 1 , - 1 ) ;
2015-10-25 09:21:15 +02:00
int3 ourPos = h - > convertPosition ( h - > pos , false ) ;
2013-12-23 23:46:01 +03:00
2012-02-14 21:04:45 +03:00
for ( int i = 1 ; i < radius ; i + + )
{
getVisibleNeighbours ( tiles [ i - 1 ] , tiles [ i ] ) ;
2015-12-29 01:14:08 +02:00
vstd : : removeDuplicates ( tiles [ i ] ) ;
2012-02-14 21:04:45 +03:00
2013-06-29 16:05:48 +03:00
for ( const int3 & tile : tiles [ i ] )
2012-02-14 21:04:45 +03:00
{
2015-10-25 09:21:15 +02:00
if ( tile = = ourPos ) //shouldn't happen, but it does
continue ;
if ( ! cb - > getPathsInfo ( hero ) - > getPathInfo ( tile ) - > reachable ( ) ) //this will remove tiles that are guarded by monsters (or removable objects)
2014-02-07 23:09:15 +03:00
continue ;
CGPath path ;
2015-11-02 10:14:32 +02:00
cb - > getPathsInfo ( hero ) - > getPath ( path , tile ) ;
2014-04-01 14:53:28 +03:00
float ourValue = ( float ) howManyTilesWillBeDiscovered ( tile , radius , cbp ) / ( path . nodes . size ( ) + 1 ) ; //+1 prevents erratic jumps
2014-02-07 23:09:15 +03:00
2013-12-23 23:46:01 +03:00
if ( ourValue > bestValue ) //avoid costly checks of tiles that don't reveal much
2012-02-14 21:04:45 +03:00
{
2016-11-28 20:29:11 +02:00
if ( isSafeToVisit ( h , tile ) )
2013-12-23 23:46:01 +03:00
{
2016-11-28 20:29:11 +02:00
if ( isBlockVisitObj ( tile ) ) //we can't stand on that object
continue ;
2014-02-17 10:36:03 +03:00
bestTile = tile ;
2013-12-23 23:46:01 +03:00
bestValue = ourValue ;
}
2012-02-14 21:04:45 +03:00
}
}
}
2014-02-17 10:36:03 +03:00
return bestTile ;
}
2014-02-17 20:28:39 +03:00
int3 VCAI : : explorationDesperate ( HeroPtr h )
2014-02-17 10:36:03 +03:00
{
2015-12-04 00:10:51 +02:00
auto sm = getCachedSectorMap ( h ) ;
2016-01-31 17:01:58 +02:00
int radius = h - > getSightRadius ( ) ;
2015-10-31 17:04:06 +02:00
2014-02-17 10:36:03 +03:00
std : : vector < std : : vector < int3 > > tiles ; //tiles[distance_to_fow]
tiles . resize ( radius ) ;
2014-04-01 14:53:28 +03:00
CCallback * cbp = cb . get ( ) ;
2014-02-17 10:36:03 +03:00
foreach_tile_pos ( [ & ] ( const int3 & pos )
{
2014-04-01 14:53:28 +03:00
if ( ! cbp - > isVisible ( pos ) )
2014-02-17 10:36:03 +03:00
tiles [ 0 ] . push_back ( pos ) ;
} ) ;
ui64 lowestDanger = - 1 ;
int3 bestTile ( - 1 , - 1 , - 1 ) ;
2015-12-29 01:14:08 +02:00
for ( int i = 1 ; i < radius ; i + + )
2014-02-17 10:36:03 +03:00
{
getVisibleNeighbours ( tiles [ i - 1 ] , tiles [ i ] ) ;
2015-12-29 01:14:08 +02:00
vstd : : removeDuplicates ( tiles [ i ] ) ;
2014-02-17 10:36:03 +03:00
for ( const int3 & tile : tiles [ i ] )
{
2014-04-01 14:53:28 +03:00
if ( cbp - > getTile ( tile ) - > blocked ) //does it shorten the time?
2014-02-17 10:36:03 +03:00
continue ;
2014-04-01 14:53:28 +03:00
if ( ! howManyTilesWillBeDiscovered ( tile , radius , cbp ) ) //avoid costly checks of tiles that don't reveal much
2014-02-17 10:36:03 +03:00
continue ;
2015-12-04 00:10:51 +02:00
auto t = sm - > firstTileToGet ( h , tile ) ;
2014-02-17 10:36:03 +03:00
if ( t . valid ( ) )
{
ui64 ourDanger = evaluateDanger ( t , h . h ) ;
if ( ourDanger < lowestDanger )
{
2016-11-28 20:29:11 +02:00
if ( ! isBlockVisitObj ( t ) )
2014-02-17 10:36:03 +03:00
{
if ( ! ourDanger ) //at least one safe place found
return t ;
bestTile = t ;
lowestDanger = ourDanger ;
}
}
}
}
}
2013-12-23 23:46:01 +03:00
return bestTile ;
2012-02-14 21:04:45 +03:00
}
TResources VCAI : : estimateIncome ( ) const
{
TResources ret ;
2013-06-29 16:05:48 +03:00
for ( const CGTownInstance * t : cb - > getTownsInfo ( ) )
2012-02-14 21:04:45 +03:00
{
2014-04-28 09:33:42 +03:00
ret + = t - > dailyIncome ( ) ;
2012-02-14 21:04:45 +03:00
}
2014-04-26 17:23:35 +03:00
2013-06-29 16:05:48 +03:00
for ( const CGObjectInstance * obj : getFlaggedObjects ( ) )
2012-02-14 21:04:45 +03:00
{
if ( obj - > ID = = Obj : : MINE )
{
switch ( obj - > subID )
{
case Res : : WOOD :
case Res : : ORE :
ret [ obj - > subID ] + = WOOD_ORE_MINE_PRODUCTION ;
break ;
case Res : : GOLD :
case 7 : //abandoned mine -> also gold
ret [ Res : : GOLD ] + = GOLD_MINE_PRODUCTION ;
break ;
default :
ret [ obj - > subID ] + = RESOURCE_MINE_PRODUCTION ;
break ;
}
}
}
return ret ;
}
bool VCAI : : containsSavedRes ( const TResources & cost ) const
{
for ( int i = 0 ; i < GameConstants : : RESOURCE_QUANTITY ; i + + )
{
if ( saving [ i ] & & cost [ i ] )
return true ;
}
return false ;
}
2012-07-18 13:10:14 +03:00
void VCAI : : checkHeroArmy ( HeroPtr h )
{
auto it = lockedHeroes . find ( h ) ;
if ( it ! = lockedHeroes . end ( ) )
{
2013-11-23 21:16:25 +03:00
if ( it - > second - > goalType = = Goals : : GATHER_ARMY & & it - > second - > value < = h - > getArmyStrength ( ) )
completeGoal ( sptr ( Goals : : GatherArmy ( it - > second - > value ) . sethero ( h ) ) ) ;
2012-07-18 13:10:14 +03:00
}
}
2013-02-09 20:37:38 +03:00
void VCAI : : recruitHero ( const CGTownInstance * t , bool throwing )
2012-02-14 21:04:45 +03:00
{
2017-08-11 19:03:05 +02:00
logAi - > debug ( " Trying to recruit a hero in %s at %s " , t - > name , t - > visitablePos ( ) . toString ( ) ) ;
2013-02-09 20:37:38 +03:00
2013-12-23 23:46:01 +03:00
auto heroes = cb - > getAvailableHeroes ( t ) ;
if ( heroes . size ( ) )
{
auto hero = heroes [ 0 ] ;
if ( heroes . size ( ) > = 2 ) //makes sense to recruit two heroes with starting amries in first week
{
if ( heroes [ 1 ] - > getTotalStrength ( ) > hero - > getTotalStrength ( ) )
hero = heroes [ 1 ] ;
}
cb - > recruitHero ( t , hero ) ;
}
2013-02-09 20:37:38 +03:00
else if ( throwing )
throw cannotFulfillGoalException ( " No available heroes in tavern in " + t - > nodeName ( ) ) ;
2012-02-14 21:04:45 +03:00
}
2012-02-22 16:41:27 +03:00
void VCAI : : finish ( )
{
if ( makingTurn )
2016-10-22 16:22:00 +02:00
{
2012-02-22 16:41:27 +03:00
makingTurn - > interrupt ( ) ;
2016-10-22 16:22:00 +02:00
makingTurn - > join ( ) ;
2017-07-12 17:26:20 +02:00
makingTurn . reset ( ) ;
2016-10-22 16:22:00 +02:00
}
2012-02-22 16:41:27 +03:00
}
2013-06-26 14:18:27 +03:00
void VCAI : : requestActionASAP ( std : : function < void ( ) > whatToDo )
2012-03-26 01:46:14 +03:00
{
2015-12-13 10:04:42 +02:00
boost : : thread newThread ( [ this , whatToDo ] ( )
2012-03-26 01:46:14 +03:00
{
2015-10-31 17:04:06 +02:00
setThreadName ( " VCAI::requestActionASAP::whatToDo " ) ;
2012-03-26 01:46:14 +03:00
SET_GLOBAL_STATE ( this ) ;
2017-06-14 06:59:41 +02:00
boost : : shared_lock < boost : : shared_mutex > gsLock ( CGameState : : mutex ) ;
2012-03-26 01:46:14 +03:00
whatToDo ( ) ;
} ) ;
}
2012-07-01 02:48:40 +03:00
void VCAI : : lostHero ( HeroPtr h )
{
2017-08-10 18:39:27 +02:00
logAi - > debug ( " I lost my hero %s. It's best to forget and move on. " , h . name ) ;
2012-07-01 02:48:40 +03:00
2015-12-29 01:14:08 +02:00
vstd : : erase_if_present ( lockedHeroes , h ) ;
2013-06-29 16:05:48 +03:00
for ( auto obj : reservedHeroesMap [ h ] )
2012-07-01 02:48:40 +03:00
{
2015-12-29 01:14:08 +02:00
vstd : : erase_if_present ( reservedObjs , obj ) ; //unreserve all objects for that hero
2012-07-01 02:48:40 +03:00
}
2015-12-29 01:14:08 +02:00
vstd : : erase_if_present ( reservedHeroesMap , h ) ;
vstd : : erase_if_present ( cachedSectorMaps , h ) ;
2012-07-01 02:48:40 +03:00
}
2013-05-27 13:53:28 +03:00
void VCAI : : answerQuery ( QueryID queryID , int selection )
2012-07-15 18:34:00 +03:00
{
2017-08-10 19:17:10 +02:00
logAi - > debug ( " I'll answer the query %d giving the choice %d " , queryID , selection ) ;
2013-05-27 13:53:28 +03:00
if ( queryID ! = QueryID ( - 1 ) )
2012-07-15 18:34:00 +03:00
{
2012-07-19 21:52:44 +03:00
cb - > selectionMade ( selection , queryID ) ;
2012-07-15 18:34:00 +03:00
}
else
{
2017-08-10 18:39:27 +02:00
logAi - > debug ( " Since the query ID is %d, the answer won't be sent. This is not a real query! " , queryID ) ;
2012-07-15 18:34:00 +03:00
//do nothing
}
}
void VCAI : : requestSent ( const CPackForServer * pack , int requestID )
{
//BNLOG("I have sent request of type %s", typeid(*pack).name());
if ( auto reply = dynamic_cast < const QueryReply * > ( pack ) )
{
status . attemptedAnsweringQuery ( reply - > qid , requestID ) ;
}
}
2012-09-29 13:59:43 +03:00
std : : string VCAI : : getBattleAIName ( ) const
{
2017-11-26 23:18:18 +02:00
if ( settings [ " server " ] [ " enemyAI " ] . getType ( ) = = JsonNode : : JsonType : : DATA_STRING )
2017-01-17 13:17:37 +02:00
return settings [ " server " ] [ " enemyAI " ] . String ( ) ;
2012-09-29 13:59:43 +03:00
else
2017-01-17 13:17:37 +02:00
return " BattleAI " ;
2012-09-29 13:59:43 +03:00
}
2013-04-21 15:11:13 +03:00
void VCAI : : validateObject ( const CGObjectInstance * obj )
{
validateObject ( obj - > id ) ;
}
void VCAI : : validateObject ( ObjectIdRef obj )
{
2015-12-29 01:14:08 +02:00
auto matchesId = [ & ] ( const CGObjectInstance * hlpObj ) - > bool { return hlpObj - > id = = obj . id ; } ;
2013-04-21 15:11:13 +03:00
if ( ! obj )
{
2015-12-29 01:14:08 +02:00
vstd : : erase_if ( visitableObjs , matchesId ) ;
2013-04-21 15:11:13 +03:00
2013-06-29 16:05:48 +03:00
for ( auto & p : reservedHeroesMap )
2015-12-29 01:14:08 +02:00
vstd : : erase_if ( p . second , matchesId ) ;
2013-08-18 18:46:28 +03:00
2015-12-29 01:14:08 +02:00
vstd : : erase_if ( reservedObjs , matchesId ) ;
2013-04-21 15:11:13 +03:00
}
}
2013-09-12 00:57:08 +03:00
TResources VCAI : : freeResources ( ) const
{
TResources myRes = cb - > getResourceAmount ( ) ;
2017-07-15 00:15:08 +02:00
auto iterator = cb - > getTownsInfo ( ) ;
2017-08-10 18:39:27 +02:00
if ( std : : none_of ( iterator . begin ( ) , iterator . end ( ) , [ ] ( const CGTownInstance * x ) - > bool
{
return x - > builtBuildings . find ( BuildingID : : CAPITOL ) ! = x - > builtBuildings . end ( ) ;
2017-07-15 00:15:08 +02:00
} )
/*|| std::all_of(iterator.begin(), iterator.end(), [](const CGTownInstance * x) -> bool { return x->forbiddenBuildings.find(BuildingID::CAPITOL) != x->forbiddenBuildings.end(); })*/ )
myRes [ Res : : GOLD ] - = GOLD_RESERVE ; //what if capitol is blocked from building in all possessed towns (set in map editor)? What about reserve for city hall or something similar in that case?
2013-09-12 00:57:08 +03:00
vstd : : amax ( myRes [ Res : : GOLD ] , 0 ) ;
return myRes ;
}
2015-12-04 00:10:51 +02:00
std : : shared_ptr < SectorMap > VCAI : : getCachedSectorMap ( HeroPtr h )
2015-10-26 17:38:17 +02:00
{
auto it = cachedSectorMaps . find ( h ) ;
if ( it ! = cachedSectorMaps . end ( ) )
return it - > second ;
else
{
2015-12-04 00:10:51 +02:00
cachedSectorMaps [ h ] = std : : make_shared < SectorMap > ( h ) ;
2015-10-26 17:38:17 +02:00
return cachedSectorMaps [ h ] ;
}
}
2012-02-14 21:04:45 +03:00
AIStatus : : AIStatus ( )
{
battle = NO_BATTLE ;
havingTurn = false ;
2013-09-28 03:30:12 +03:00
ongoingHeroMovement = false ;
2015-03-08 16:23:56 +02:00
ongoingChannelProbing = false ;
2012-02-14 21:04:45 +03:00
}
AIStatus : : ~ AIStatus ( )
{
}
void AIStatus : : setBattle ( BattleState BS )
{
boost : : unique_lock < boost : : mutex > lock ( mx ) ;
2013-04-20 14:34:01 +03:00
LOG_TRACE_PARAMS ( logAi , " battle state=%d " , ( int ) BS ) ;
2012-02-14 21:04:45 +03:00
battle = BS ;
cv . notify_all ( ) ;
}
BattleState AIStatus : : getBattle ( )
{
boost : : unique_lock < boost : : mutex > lock ( mx ) ;
return battle ;
}
2013-05-27 13:53:28 +03:00
void AIStatus : : addQuery ( QueryID ID , std : : string description )
2016-01-18 21:57:19 +02:00
{
2013-05-27 13:53:28 +03:00
if ( ID = = QueryID ( - 1 ) )
2012-07-15 18:34:00 +03:00
{
2017-08-10 20:59:55 +02:00
logAi - > debug ( " The \" query \" has an id %d, it'll be ignored as non-query. Description: %s " , ID , description ) ;
2012-07-15 18:34:00 +03:00
return ;
}
2013-05-27 13:53:28 +03:00
assert ( ID . getNum ( ) > = 0 ) ;
2015-08-31 09:18:24 +02:00
boost : : unique_lock < boost : : mutex > lock ( mx ) ;
assert ( ! vstd : : contains ( remainingQueries , ID ) ) ;
2012-07-15 18:34:00 +03:00
remainingQueries [ ID ] = description ;
2015-08-31 09:18:24 +02:00
2012-02-14 21:04:45 +03:00
cv . notify_all ( ) ;
2017-08-10 20:59:55 +02:00
logAi - > debug ( " Adding query %d - %s. Total queries count: %d " , ID , description , remainingQueries . size ( ) ) ;
2012-02-14 21:04:45 +03:00
}
2013-05-27 13:53:28 +03:00
void AIStatus : : removeQuery ( QueryID ID )
2012-02-14 21:04:45 +03:00
{
2012-07-15 18:34:00 +03:00
boost : : unique_lock < boost : : mutex > lock ( mx ) ;
assert ( vstd : : contains ( remainingQueries , ID ) ) ;
2012-02-14 21:04:45 +03:00
2012-07-15 18:34:00 +03:00
std : : string description = remainingQueries [ ID ] ;
remainingQueries . erase ( ID ) ;
2016-01-18 21:57:19 +02:00
2012-07-15 18:34:00 +03:00
cv . notify_all ( ) ;
2017-08-10 20:59:55 +02:00
logAi - > debug ( " Removing query %d - %s. Total queries count: %d " , ID , description , remainingQueries . size ( ) ) ;
2012-02-14 21:04:45 +03:00
}
int AIStatus : : getQueriesCount ( )
{
boost : : unique_lock < boost : : mutex > lock ( mx ) ;
2012-07-15 18:34:00 +03:00
return remainingQueries . size ( ) ;
2012-02-14 21:04:45 +03:00
}
void AIStatus : : startedTurn ( )
{
boost : : unique_lock < boost : : mutex > lock ( mx ) ;
havingTurn = true ;
cv . notify_all ( ) ;
}
void AIStatus : : madeTurn ( )
{
boost : : unique_lock < boost : : mutex > lock ( mx ) ;
havingTurn = false ;
cv . notify_all ( ) ;
}
void AIStatus : : waitTillFree ( )
{
boost : : unique_lock < boost : : mutex > lock ( mx ) ;
2013-11-03 15:51:25 +03:00
while ( battle ! = NO_BATTLE | | ! remainingQueries . empty ( ) | | ! objectsBeingVisited . empty ( ) | | ongoingHeroMovement )
2013-09-28 03:30:12 +03:00
cv . timed_wait ( lock , boost : : posix_time : : milliseconds ( 100 ) ) ;
2012-02-14 21:04:45 +03:00
}
bool AIStatus : : haveTurn ( )
{
boost : : unique_lock < boost : : mutex > lock ( mx ) ;
return havingTurn ;
}
2013-05-27 13:53:28 +03:00
void AIStatus : : attemptedAnsweringQuery ( QueryID queryID , int answerRequestID )
2012-07-15 18:34:00 +03:00
{
boost : : unique_lock < boost : : mutex > lock ( mx ) ;
assert ( vstd : : contains ( remainingQueries , queryID ) ) ;
std : : string description = remainingQueries [ queryID ] ;
2017-08-10 20:59:55 +02:00
logAi - > debug ( " Attempted answering query %d - %s. Request id=%d. Waiting for results... " , queryID , description , answerRequestID ) ;
2012-07-15 18:34:00 +03:00
requestToQueryID [ answerRequestID ] = queryID ;
}
void AIStatus : : receivedAnswerConfirmation ( int answerRequestID , int result )
{
assert ( vstd : : contains ( requestToQueryID , answerRequestID ) ) ;
2013-05-27 13:53:28 +03:00
QueryID query = requestToQueryID [ answerRequestID ] ;
2012-07-15 18:34:00 +03:00
assert ( vstd : : contains ( remainingQueries , query ) ) ;
requestToQueryID . erase ( answerRequestID ) ;
if ( result )
{
removeQuery ( query ) ;
}
else
{
2017-08-10 20:59:55 +02:00
logAi - > error ( " Something went really wrong, failed to answer query %d : %s " , query . getNum ( ) , remainingQueries [ query ] ) ;
2012-07-15 18:34:00 +03:00
//TODO safely retry
}
}
2013-09-28 02:46:58 +03:00
void AIStatus : : heroVisit ( const CGObjectInstance * obj , bool started )
{
boost : : unique_lock < boost : : mutex > lock ( mx ) ;
if ( started )
objectsBeingVisited . push_back ( obj ) ;
else
{
2015-10-31 17:04:06 +02:00
// There can be more than one object visited at the time (eg. hero visits Subterranean Gate
// causing visit to hero on the other side.
2013-10-13 18:24:02 +03:00
// However, we are guaranteed that start/end visit notification maintain stack order.
assert ( ! objectsBeingVisited . empty ( ) ) ;
objectsBeingVisited . pop_back ( ) ;
2013-09-28 02:46:58 +03:00
}
cv . notify_all ( ) ;
}
void AIStatus : : setMove ( bool ongoing )
{
boost : : unique_lock < boost : : mutex > lock ( mx ) ;
ongoingHeroMovement = ongoing ;
cv . notify_all ( ) ;
}
2015-03-08 16:23:56 +02:00
void AIStatus : : setChannelProbing ( bool ongoing )
{
boost : : unique_lock < boost : : mutex > lock ( mx ) ;
2015-12-04 00:54:25 +02:00
ongoingChannelProbing = ongoing ;
2015-03-08 16:23:56 +02:00
cv . notify_all ( ) ;
}
bool AIStatus : : channelProbing ( )
{
return ongoingChannelProbing ;
}
2013-10-18 23:17:25 +03:00
SectorMap : : SectorMap ( )
2012-02-14 21:04:45 +03:00
{
2013-10-18 23:17:25 +03:00
update ( ) ;
}
2012-02-14 21:04:45 +03:00
2014-02-15 22:39:03 +03:00
SectorMap : : SectorMap ( HeroPtr h )
{
update ( ) ;
makeParentBFS ( h - > visitablePos ( ) ) ;
}
2016-12-12 22:38:12 +02:00
bool SectorMap : : markIfBlocked ( TSectorID & sec , crint3 pos , const TerrainTile * t )
2013-10-18 23:17:25 +03:00
{
if ( t - > blocked & & ! t - > visitable )
2012-02-14 21:04:45 +03:00
{
2013-10-18 23:17:25 +03:00
sec = NOT_AVAILABLE ;
return true ;
2012-02-14 21:04:45 +03:00
}
2013-10-18 23:17:25 +03:00
return false ;
}
2012-02-14 21:04:45 +03:00
2016-12-12 22:38:12 +02:00
bool SectorMap : : markIfBlocked ( TSectorID & sec , crint3 pos )
2013-10-18 23:17:25 +03:00
{
2015-08-31 07:39:03 +02:00
return markIfBlocked ( sec , pos , getTile ( pos ) ) ;
2012-02-14 21:04:45 +03:00
}
2013-10-18 23:17:25 +03:00
void SectorMap : : update ( )
2012-02-14 21:04:45 +03:00
{
2015-08-31 08:25:33 +02:00
visibleTiles = cb - > getAllVisibleTiles ( ) ;
2016-12-12 22:38:12 +02:00
auto shape = visibleTiles - > shape ( ) ;
sector . resize ( boost : : extents [ shape [ 0 ] ] [ shape [ 1 ] ] [ shape [ 2 ] ] ) ;
2015-08-31 07:39:03 +02:00
2013-10-18 23:17:25 +03:00
clear ( ) ;
int curSector = 3 ; //0 is invisible, 1 is not explored
2014-04-01 14:53:28 +03:00
CCallback * cbp = cb . get ( ) ; //optimization
2013-10-18 23:17:25 +03:00
foreach_tile_pos ( [ & ] ( crint3 pos )
2012-02-14 21:04:45 +03:00
{
2013-10-18 23:17:25 +03:00
if ( retreiveTile ( pos ) = = NOT_CHECKED )
2012-02-14 21:04:45 +03:00
{
2013-10-18 23:17:25 +03:00
if ( ! markIfBlocked ( retreiveTile ( pos ) , pos ) )
2014-04-01 14:53:28 +03:00
exploreNewSector ( pos , curSector + + , cbp ) ;
2012-02-14 21:04:45 +03:00
}
} ) ;
valid = true ;
}
2016-12-12 22:38:12 +02:00
SectorMap : : TSectorID & SectorMap : : retreiveTileN ( SectorMap : : TSectorArray & a , const int3 & pos )
{
return a [ pos . x ] [ pos . y ] [ pos . z ] ;
}
const SectorMap : : TSectorID & SectorMap : : retreiveTileN ( const SectorMap : : TSectorArray & a , const int3 & pos )
{
return a [ pos . x ] [ pos . y ] [ pos . z ] ;
}
2012-02-14 21:04:45 +03:00
void SectorMap : : clear ( )
{
2016-12-12 22:38:12 +02:00
//TODO: rotate to [z][x][y]
auto fow = cb - > getVisibilityMap ( ) ;
2016-12-12 23:52:18 +02:00
//TODO: any magic to automate this? will need array->array conversion
//std::transform(fow.begin(), fow.end(), sector.begin(), [](const ui8 &f) -> unsigned short
//{
// return f; //type conversion
//});
2016-12-12 22:38:12 +02:00
auto width = fow . size ( ) ;
auto height = fow . front ( ) . size ( ) ;
auto depth = fow . front ( ) . front ( ) . size ( ) ;
for ( size_t x = 0 ; x < width ; x + + )
for ( size_t y = 0 ; y < height ; y + + )
for ( size_t z = 0 ; z < depth ; z + + )
sector [ x ] [ y ] [ z ] = fow [ x ] [ y ] [ z ] ;
2012-02-14 21:04:45 +03:00
valid = false ;
}
2014-04-01 14:53:28 +03:00
void SectorMap : : exploreNewSector ( crint3 pos , int num , CCallback * cbp )
2012-02-14 21:04:45 +03:00
{
Sector & s = infoOnSectors [ num ] ;
s . id = num ;
2015-08-31 07:39:03 +02:00
s . water = getTile ( pos ) - > isWater ( ) ;
2012-02-14 21:04:45 +03:00
std : : queue < int3 > toVisit ;
toVisit . push ( pos ) ;
2013-11-03 15:51:25 +03:00
while ( ! toVisit . empty ( ) )
2012-02-14 21:04:45 +03:00
{
int3 curPos = toVisit . front ( ) ;
toVisit . pop ( ) ;
2016-12-12 22:38:12 +02:00
TSectorID & sec = retreiveTile ( curPos ) ;
2012-02-14 21:04:45 +03:00
if ( sec = = NOT_CHECKED )
{
2015-08-31 07:39:03 +02:00
const TerrainTile * t = getTile ( curPos ) ;
2012-02-14 21:04:45 +03:00
if ( ! markIfBlocked ( sec , curPos , t ) )
{
if ( t - > isWater ( ) = = s . water ) //sector is only-water or only-land
{
sec = num ;
s . tiles . push_back ( curPos ) ;
2014-04-01 14:53:28 +03:00
foreach_neighbour ( cbp , curPos , [ & ] ( CCallback * cbp , crint3 neighPos )
2012-02-14 21:04:45 +03:00
{
if ( retreiveTile ( neighPos ) = = NOT_CHECKED )
{
toVisit . push ( neighPos ) ;
//parent[neighPos] = curPos;
}
2015-08-31 07:39:03 +02:00
const TerrainTile * nt = getTile ( neighPos ) ;
2014-05-18 14:13:31 +03:00
if ( nt & & nt - > isWater ( ) ! = s . water & & canBeEmbarkmentPoint ( nt , s . water ) )
2012-02-14 21:04:45 +03:00
{
s . embarkmentPoints . push_back ( neighPos ) ;
}
} ) ;
2015-10-31 17:04:06 +02:00
2014-02-20 23:18:49 +03:00
if ( t - > visitable )
{
auto obj = t - > visitableObjects . front ( ) ;
2016-08-18 02:33:23 +02:00
if ( cb - > getObj ( obj - > id , false ) ) // FIXME: we have to filter invisible objcts like events, but probably TerrainTile shouldn't be used in SectorMap at all
s . visitableObjs . push_back ( obj ) ;
2014-02-20 23:18:49 +03:00
}
2012-02-14 21:04:45 +03:00
}
}
}
}
2015-12-29 01:14:08 +02:00
vstd : : removeDuplicates ( s . embarkmentPoints ) ;
2012-02-14 21:04:45 +03:00
}
void SectorMap : : write ( crstring fname )
{
std : : ofstream out ( fname ) ;
for ( int k = 0 ; k < cb - > getMapSize ( ) . z ; k + + )
{
for ( int j = 0 ; j < cb - > getMapSize ( ) . y ; j + + )
{
for ( int i = 0 ; i < cb - > getMapSize ( ) . x ; i + + )
{
out < < ( int ) sector [ i ] [ j ] [ k ] < < ' \t ' ;
}
out < < std : : endl ;
}
out < < std : : endl ;
}
}
2012-03-05 22:11:28 +03:00
bool isWeeklyRevisitable ( const CGObjectInstance * obj )
{ //TODO: allow polling of remaining creatures in dwelling
2012-03-14 16:02:38 +03:00
if ( dynamic_cast < const CGVisitableOPW * > ( obj ) | | //ensures future compatibility, unlike IDs
dynamic_cast < const CGDwelling * > ( obj ) | |
dynamic_cast < const CBank * > ( obj ) ) //banks tend to respawn often in mods
2012-03-05 22:11:28 +03:00
return true ;
2012-03-06 21:49:23 +03:00
switch ( obj - > ID )
{
2012-08-29 12:19:20 +03:00
case Obj : : STABLES :
case Obj : : MAGIC_WELL :
case Obj : : HILL_FORT :
2012-03-06 21:49:23 +03:00
return true ;
2012-07-18 13:10:14 +03:00
case Obj : : BORDER_GATE :
case Obj : : BORDERGUARD :
2012-07-19 21:52:44 +03:00
return ( dynamic_cast < const CGKeys * > ( obj ) ) - > wasMyColorVisited ( ai - > playerID ) ; //FIXME: they could be revisited sooner than in a week
2012-03-06 21:49:23 +03:00
}
2012-03-05 22:11:28 +03:00
return false ;
}
2012-07-01 02:48:40 +03:00
bool shouldVisit ( HeroPtr h , const CGObjectInstance * obj )
2012-03-14 16:02:38 +03:00
{
switch ( obj - > ID )
2015-10-31 17:04:06 +02:00
{
2013-12-23 23:46:01 +03:00
case Obj : : TOWN :
2013-12-26 12:53:37 +03:00
case Obj : : HERO : //never visit our heroes at random
2013-12-23 23:46:01 +03:00
return obj - > tempOwner ! = h - > tempOwner ; //do not visit our towns at random
break ;
2012-07-19 12:10:55 +03:00
case Obj : : BORDER_GATE :
2012-10-03 17:49:29 +03:00
{
2013-06-29 16:05:48 +03:00
for ( auto q : ai - > myCb - > getMyQuests ( ) )
2012-10-03 17:49:29 +03:00
{
if ( q . obj = = obj )
{
return false ; // do not visit guards or gates when wandering
}
}
return true ; //we don't have this quest yet
}
2013-12-22 00:31:28 +03:00
break ;
case Obj : : BORDERGUARD : //open borderguard if possible
2016-02-25 07:40:28 +02:00
return ( dynamic_cast < const CGKeys * > ( obj ) ) - > wasMyColorVisited ( ai - > playerID ) ;
2012-07-18 13:10:14 +03:00
case Obj : : SEER_HUT :
2012-07-19 12:10:55 +03:00
case Obj : : QUEST_GUARD :
2012-07-18 13:10:14 +03:00
{
2013-06-29 16:05:48 +03:00
for ( auto q : ai - > myCb - > getMyQuests ( ) )
2012-07-18 13:10:14 +03:00
{
2012-07-19 12:10:55 +03:00
if ( q . obj = = obj )
2012-07-18 13:10:14 +03:00
{
2013-12-22 00:31:28 +03:00
if ( q . quest - > checkQuest ( h . h ) )
2012-07-18 13:10:14 +03:00
return true ; //we completed the quest
else
return false ; //we can't complete this quest
}
}
2012-07-19 12:10:55 +03:00
return true ; //we don't have this quest yet
2012-07-18 13:10:14 +03:00
}
2013-12-22 00:31:28 +03:00
break ;
2012-03-14 16:02:38 +03:00
case Obj : : CREATURE_GENERATOR1 :
{
2012-05-05 09:42:18 +03:00
if ( obj - > tempOwner ! = h - > tempOwner )
return true ; //flag just in case
2012-03-14 16:02:38 +03:00
bool canRecruitCreatures = false ;
const CGDwelling * d = dynamic_cast < const CGDwelling * > ( obj ) ;
2013-06-29 16:05:48 +03:00
for ( auto level : d - > creatures )
2012-03-14 16:02:38 +03:00
{
2013-06-29 16:05:48 +03:00
for ( auto c : level . second )
2012-03-14 16:02:38 +03:00
{
2013-02-16 17:03:47 +03:00
if ( h - > getSlotFor ( CreatureID ( c ) ) ! = SlotID ( ) )
2012-03-14 16:02:38 +03:00
canRecruitCreatures = true ;
}
}
return canRecruitCreatures ;
}
2012-08-29 12:19:20 +03:00
case Obj : : HILL_FORT :
2015-10-31 17:04:06 +02:00
{
2013-06-29 16:05:48 +03:00
for ( auto slot : h - > Slots ( ) )
2012-08-29 12:19:20 +03:00
{
if ( slot . second - > type - > upgrades . size ( ) )
return true ; //TODO: check price?
}
return false ;
}
2014-06-01 13:02:43 +03:00
case Obj : : MONOLITH_ONE_WAY_ENTRANCE :
case Obj : : MONOLITH_ONE_WAY_EXIT :
case Obj : : MONOLITH_TWO_WAY :
2012-03-14 16:02:38 +03:00
case Obj : : WHIRLPOOL :
return false ;
2012-03-29 21:26:06 +03:00
case Obj : : SCHOOL_OF_MAGIC :
case Obj : : SCHOOL_OF_WAR :
{
TResources myRes = ai - > myCb - > getResourceAmount ( ) ;
if ( myRes [ Res : : GOLD ] - GOLD_RESERVE < 1000 )
return false ;
}
2012-04-04 11:03:52 +03:00
break ;
2012-03-29 21:26:06 +03:00
case Obj : : LIBRARY_OF_ENLIGHTENMENT :
2012-03-29 21:29:47 +03:00
if ( h - > level < 12 )
return false ;
2012-07-19 19:29:29 +03:00
break ;
2012-04-28 16:01:39 +03:00
case Obj : : TREE_OF_KNOWLEDGE :
{
TResources myRes = ai - > myCb - > getResourceAmount ( ) ;
if ( myRes [ Res : : GOLD ] - GOLD_RESERVE < 2000 | | myRes [ Res : : GEMS ] < 10 )
return false ;
}
2012-04-04 11:03:52 +03:00
break ;
2012-08-29 12:19:20 +03:00
case Obj : : MAGIC_WELL :
return h - > mana < h - > manaLimit ( ) ;
2012-10-01 21:25:43 +03:00
case Obj : : PRISON :
2017-11-04 23:45:19 +02:00
return ai - > myCb - > getHeroesInfo ( ) . size ( ) < VLC - > modh - > settings . MAX_HEROES_ON_MAP_PER_PLAYER ;
case Obj : : TAVERN :
2018-02-09 21:12:21 +02:00
{ //TODO: make AI actually recruit heroes
//TODO: only on request
if ( ( ai - > myCb - > getHeroesInfo ( ) . size ( ) > = VLC - > modh - > settings . MAX_HEROES_ON_MAP_PER_PLAYER ) | |
( ai - > myCb - > getResourceAmount ( ) [ Res : : GOLD ] - GOLD_RESERVE < GameConstants : : HERO_GOLD_COST ) )
return false ;
}
break ;
2012-09-28 23:49:23 +03:00
case Obj : : BOAT :
return false ;
//Boats are handled by pathfinder
2016-11-28 22:45:41 +02:00
case Obj : : EYE_OF_MAGI :
return false ; //this object is useless to visit, but could be visited indefinitely
2017-11-04 23:45:19 +02:00
2012-03-14 16:02:38 +03:00
}
2012-03-29 21:26:06 +03:00
2012-07-19 19:29:29 +03:00
if ( obj - > wasVisited ( * h ) ) //it must pointer to hero instance, heroPtr calls function wasVisited(ui8 player);
2012-03-29 21:26:06 +03:00
return false ;
2012-03-14 16:02:38 +03:00
return true ;
}
2012-07-01 02:48:40 +03:00
int3 SectorMap : : firstTileToGet ( HeroPtr h , crint3 dst )
2014-02-15 19:38:51 +03:00
/*
this functions returns one target tile or invalid tile . We will use it to poll possible destinations
For ship construction etc , another function ( goal ? ) is needed
*/
2012-02-14 21:04:45 +03:00
{
2014-02-15 19:38:51 +03:00
int3 ret ( - 1 , - 1 , - 1 ) ;
2012-02-16 20:10:58 +03:00
int sourceSector = retreiveTile ( h - > visitablePos ( ) ) ,
2012-02-14 21:04:45 +03:00
destinationSector = retreiveTile ( dst ) ;
2014-02-23 19:55:42 +03:00
const Sector * src = & infoOnSectors [ sourceSector ] ,
* dest = & infoOnSectors [ destinationSector ] ;
if ( sourceSector ! = destinationSector ) //use ships, shipyards etc..
2012-02-14 21:04:45 +03:00
{
2014-02-23 19:55:42 +03:00
if ( ai - > isAccessibleForHero ( dst , h ) ) //pathfinder can find a way using ships and gates if tile is not blocked by objects
return dst ;
2012-02-14 21:04:45 +03:00
std : : map < const Sector * , const Sector * > preds ;
2014-02-21 10:48:38 +03:00
std : : queue < const Sector * > sectorQueue ;
sectorQueue . push ( src ) ;
while ( ! sectorQueue . empty ( ) )
2012-02-14 21:04:45 +03:00
{
2014-02-21 10:48:38 +03:00
const Sector * s = sectorQueue . front ( ) ;
sectorQueue . pop ( ) ;
2012-02-14 21:04:45 +03:00
2013-06-29 16:05:48 +03:00
for ( int3 ep : s - > embarkmentPoints )
2012-02-14 21:04:45 +03:00
{
Sector * neigh = & infoOnSectors [ retreiveTile ( ep ) ] ;
//preds[s].push_back(neigh);
if ( ! preds [ neigh ] )
{
preds [ neigh ] = s ;
2014-02-21 10:48:38 +03:00
sectorQueue . push ( neigh ) ;
2012-02-14 21:04:45 +03:00
}
}
}
2014-02-21 10:48:38 +03:00
if ( ! preds [ dest ] )
2012-02-14 21:04:45 +03:00
{
2014-02-21 10:48:38 +03:00
//write("test.txt");
2014-02-15 19:38:51 +03:00
return ret ;
//throw cannotFulfillGoalException(boost::str(boost::format("Cannot find connection between sectors %d and %d") % src->id % dst->id));
2012-02-14 21:04:45 +03:00
}
std : : vector < const Sector * > toTraverse ;
2014-02-21 10:48:38 +03:00
toTraverse . push_back ( dest ) ;
2012-02-14 21:04:45 +03:00
while ( toTraverse . back ( ) ! = src )
{
toTraverse . push_back ( preds [ toTraverse . back ( ) ] ) ;
}
2014-02-21 10:48:38 +03:00
if ( preds [ dest ] )
2012-02-14 21:04:45 +03:00
{
2014-02-23 19:55:42 +03:00
//TODO: would be nice to find sectors in loop
2012-02-14 21:04:45 +03:00
const Sector * sectorToReach = toTraverse . at ( toTraverse . size ( ) - 2 ) ;
2014-02-23 19:55:42 +03:00
2012-02-14 21:04:45 +03:00
if ( ! src - > water & & sectorToReach - > water ) //embark
{
//embark on ship -> look for an EP with a boat
auto firstEP = boost : : find_if ( src - > embarkmentPoints , [ = ] ( crint3 pos ) - > bool
{
2015-10-24 20:46:55 +02:00
const TerrainTile * t = getTile ( pos ) ;
2012-11-06 19:39:29 +03:00
return t & & t - > visitableObjects . size ( ) = = 1 & & t - > topVisitableId ( ) = = Obj : : BOAT
2012-02-14 21:04:45 +03:00
& & retreiveTile ( pos ) = = sectorToReach - > id ;
} ) ;
if ( firstEP ! = src - > embarkmentPoints . end ( ) )
{
return * firstEP ;
}
else
{
//we need to find a shipyard with an access to the desired sector's EP
//TODO what about Summon Boat spell?
std : : vector < const IShipyard * > shipyards ;
2013-06-29 16:05:48 +03:00
for ( const CGTownInstance * t : cb - > getTownsInfo ( ) )
2012-02-14 21:04:45 +03:00
{
2013-02-11 02:24:57 +03:00
if ( t - > hasBuilt ( BuildingID : : SHIPYARD ) )
2012-02-14 21:04:45 +03:00
shipyards . push_back ( t ) ;
}
2015-03-30 15:32:23 +02:00
for ( const CGObjectInstance * obj : ai - > getFlaggedObjects ( ) )
2012-02-14 21:04:45 +03:00
{
2012-09-23 21:01:04 +03:00
if ( obj - > ID ! = Obj : : TOWN ) //towns were handled in the previous loop
2012-02-14 21:04:45 +03:00
if ( const IShipyard * shipyard = IShipyard : : castFrom ( obj ) )
shipyards . push_back ( shipyard ) ;
}
2012-02-16 20:10:58 +03:00
shipyards . erase ( boost : : remove_if ( shipyards , [ = ] ( const IShipyard * shipyard ) - > bool
2012-02-14 21:04:45 +03:00
{
2013-07-21 13:08:32 +03:00
return shipyard - > shipyardStatus ( ) ! = 0 | | retreiveTile ( shipyard - > bestLocation ( ) ) ! = sectorToReach - > id ;
2012-02-14 21:04:45 +03:00
} ) , shipyards . end ( ) ) ;
if ( ! shipyards . size ( ) )
{
//TODO consider possibility of building shipyard in a town
2014-02-15 19:38:51 +03:00
return ret ;
//throw cannotFulfillGoalException("There is no known shipyard!");
2012-02-14 21:04:45 +03:00
}
//we have only shipyards that possibly can build ships onto the appropriate EP
auto ownedGoodShipyard = boost : : find_if ( shipyards , [ ] ( const IShipyard * s ) - > bool
{
return s - > o - > tempOwner = = ai - > playerID ;
} ) ;
if ( ownedGoodShipyard ! = shipyards . end ( ) )
{
const IShipyard * s = * ownedGoodShipyard ;
TResources shipCost ;
s - > getBoatCost ( shipCost ) ;
if ( cb - > getResourceAmount ( ) . canAfford ( shipCost ) )
{
int3 ret = s - > bestLocation ( ) ;
2014-02-15 19:38:51 +03:00
cb - > buildBoat ( s ) ; //TODO: move actions elsewhere
2012-02-14 21:04:45 +03:00
return ret ;
}
else
{
//TODO gather res
2014-02-15 19:38:51 +03:00
return ret ;
//throw cannotFulfillGoalException("Not enough resources to build a boat");
2012-02-14 21:04:45 +03:00
}
}
else
{
//TODO pick best shipyard to take over
2013-01-06 07:48:05 +03:00
return shipyards . front ( ) - > o - > visitablePos ( ) ;
2012-02-14 21:04:45 +03:00
}
}
}
else if ( src - > water & & ! sectorToReach - > water )
{
//TODO
//disembark
2014-02-15 19:38:51 +03:00
return ret ;
2012-02-14 21:04:45 +03:00
}
2015-12-24 12:28:14 +02:00
else //use subterranean gates - not needed since gates are now handled via Pathfinder
2012-02-14 21:04:45 +03:00
{
2014-02-15 19:38:51 +03:00
return ret ;
//throw cannotFulfillGoalException("Land-land and water-water inter-sector transitions are not implemented!");
2012-02-14 21:04:45 +03:00
}
}
else
{
2014-02-15 19:38:51 +03:00
return ret ;
//throw cannotFulfillGoalException("Inter-sector route detection failed: not connected sectors?");
2012-02-14 21:04:45 +03:00
}
}
else
{
2014-02-23 19:55:42 +03:00
return findFirstVisitableTile ( h , dst ) ;
}
}
int3 SectorMap : : findFirstVisitableTile ( HeroPtr h , crint3 dst )
{
int3 ret ( - 1 , - 1 , - 1 ) ;
int3 curtile = dst ;
while ( curtile ! = h - > visitablePos ( ) )
{
auto topObj = cb - > getTopObj ( curtile ) ;
2015-11-08 22:02:59 +02:00
if ( topObj & & topObj - > ID = = Obj : : HERO & & topObj ! = h . h & &
cb - > getPlayerRelations ( h - > tempOwner , topObj - > tempOwner ) ! = PlayerRelations : : ENEMIES )
2012-02-14 21:04:45 +03:00
{
2017-08-10 20:59:55 +02:00
logAi - > warn ( " Another allied hero stands in our way " ) ;
2014-02-23 19:55:42 +03:00
return ret ;
}
2014-09-21 16:42:08 +03:00
if ( ai - > myCb - > getPathsInfo ( h . get ( ) ) - > getPathInfo ( curtile ) - > reachable ( ) )
2014-02-23 19:55:42 +03:00
{
return curtile ;
}
else
{
auto i = parent . find ( curtile ) ;
if ( i ! = parent . end ( ) )
2012-02-14 21:04:45 +03:00
{
2014-02-23 19:55:42 +03:00
assert ( curtile ! = i - > second ) ;
curtile = i - > second ;
2012-02-14 21:04:45 +03:00
}
else
{
2014-02-23 19:55:42 +03:00
return ret ;
//throw cannotFulfillGoalException("Unreachable tile in sector? Should not happen!");
2012-02-14 21:04:45 +03:00
}
}
}
2014-02-15 19:38:51 +03:00
return ret ;
2012-02-14 21:04:45 +03:00
}
void SectorMap : : makeParentBFS ( crint3 source )
{
parent . clear ( ) ;
int mySector = retreiveTile ( source ) ;
std : : queue < int3 > toVisit ;
toVisit . push ( source ) ;
2013-11-03 15:51:25 +03:00
while ( ! toVisit . empty ( ) )
2012-02-14 21:04:45 +03:00
{
int3 curPos = toVisit . front ( ) ;
toVisit . pop ( ) ;
2016-12-12 22:38:12 +02:00
TSectorID & sec = retreiveTile ( curPos ) ;
2012-02-14 21:04:45 +03:00
assert ( sec = = mySector ) ; //consider only tiles from the same sector
2013-11-09 16:49:36 +03:00
UNUSED ( sec ) ;
2015-10-31 17:04:06 +02:00
2012-02-14 21:04:45 +03:00
foreach_neighbour ( curPos , [ & ] ( crint3 neighPos )
{
if ( retreiveTile ( neighPos ) = = mySector & & ! vstd : : contains ( parent , neighPos ) )
{
2014-02-15 22:39:03 +03:00
if ( cb - > canMoveBetween ( curPos , neighPos ) )
{
toVisit . push ( neighPos ) ;
parent [ neighPos ] = curPos ;
}
2012-02-14 21:04:45 +03:00
}
2014-02-19 19:23:47 +03:00
} ) ;
2012-02-14 21:04:45 +03:00
}
}
2016-12-12 22:38:12 +02:00
SectorMap : : TSectorID & SectorMap : : retreiveTile ( crint3 pos )
2012-02-14 21:04:45 +03:00
{
return retreiveTileN ( sector , pos ) ;
2012-04-16 20:12:39 +03:00
}
2012-04-17 15:46:21 +03:00
2015-08-31 07:39:03 +02:00
TerrainTile * SectorMap : : getTile ( crint3 pos ) const
{
//out of bounds access should be handled by boost::multi_array
//still we cached this array to avoid any checks
return visibleTiles - > operator [ ] ( pos . x ) [ pos . y ] [ pos . z ] ;
}
2016-08-12 08:05:11 +02:00
std : : vector < const CGObjectInstance * > SectorMap : : getNearbyObjs ( HeroPtr h , bool sectorsAround )
{
const Sector * heroSector = & infoOnSectors [ retreiveTile ( h - > visitablePos ( ) ) ] ;
if ( sectorsAround )
{
std : : vector < const CGObjectInstance * > ret ;
for ( auto embarkPoint : heroSector - > embarkmentPoints )
{
const Sector * embarkSector = & infoOnSectors [ retreiveTile ( embarkPoint ) ] ;
range : : copy ( embarkSector - > visitableObjs , std : : back_inserter ( ret ) ) ;
}
return ret ;
}
return heroSector - > visitableObjs ;
}