2012-02-14 21:04:45 +03:00
# include "StdInc.h"
# include "VCAI.h"
2013-11-23 15:30:10 +03:00
# include "Goals.h"
2012-02-20 00:03:43 +03:00
# include "../../lib/UnlockGuard.h"
2012-03-03 13:08:01 +03:00
# include "../../lib/CObjectHandler.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"
2012-02-14 21:04:45 +03:00
2013-10-18 23:17:25 +03:00
/*
* CCreatureHandler . h , 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-22 22:22:11 +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
using namespace vstd ;
//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)
unsigned char & retreiveTileN ( std : : vector < std : : vector < std : : vector < unsigned char > > > & vectors , const int3 & pos )
{
return vectors [ pos . x ] [ pos . y ] [ pos . z ] ;
}
const unsigned char & retreiveTileN ( const std : : vector < std : : vector < std : : vector < unsigned char > > > & vectors , const int3 & pos )
{
return vectors [ pos . x ] [ pos . y ] [ pos . z ] ;
}
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
{
2013-06-29 16:05:48 +03:00
for ( auto & vector : vectors )
for ( auto j = vector . begin ( ) ; j ! = vector . end ( ) ; j + + )
for ( auto & elem : * j )
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 ) ,
name ( obj - > getHoverText ( ) )
2012-02-14 21:04:45 +03:00
{
}
} ;
std : : map < const CGObjectInstance * , ObjInfo > helperObjInfo ;
VCAI : : VCAI ( void )
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 ;
2012-02-14 21:04:45 +03:00
}
VCAI : : ~ VCAI ( void )
2012-03-03 13:08:01 +03:00
{
2013-04-16 16:16:58 +03:00
LOG_TRACE ( logAi ) ;
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
2012-02-14 21:04:45 +03:00
if ( details . result = = TryMoveHero : : TELEPORTATION )
{
2012-07-19 21:52:44 +03:00
const int3 from = CGHeroInstance : : convertPosition ( details . start , false ) ,
2012-04-14 10:22:08 +03:00
to = CGHeroInstance : : convertPosition ( details . end , false ) ;
const CGObjectInstance * o1 = frontOrNull ( cb - > getVisitableObjs ( from ) ) ,
* o2 = frontOrNull ( cb - > getVisitableObjs ( to ) ) ;
2012-02-14 21:04:45 +03:00
2012-04-14 10:22:08 +03:00
if ( o1 & & o2 & & o1 - > ID = = Obj : : SUBTERRANEAN_GATE & & o2 - > ID = = Obj : : SUBTERRANEAN_GATE )
{
knownSubterraneanGates [ o1 ] = o2 ;
knownSubterraneanGates [ o2 ] = o1 ;
2013-04-10 19:28:14 +03:00
logAi - > debugStream ( ) < < boost : : format ( " Found a pair of subterranean gates between %s and %s! " ) % from % to ;
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
{
2013-11-17 20:57:04 +03:00
LOG_TRACE_PARAMS ( logAi , " victoryLossCheckResult '%s' " , victoryLossCheckResult ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2013-11-17 20:57:04 +03:00
logAi - > debugStream ( ) < < boost : : format ( " Player %d: I heard that player %d %s. " ) % playerID % player . getNum ( ) % ( 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
{
2013-04-10 19:28:14 +03:00
logAi - > debugStream ( ) < < " VCAI: I won! Incredible! " ;
logAi - > debugStream ( ) < < " Turn nr " < < myCb - > getDate ( ) ;
2012-02-14 21:04:45 +03:00
}
else
{
2013-04-10 19:28:14 +03:00
logAi - > debugStream ( ) < < " VCAI: Player " < < player < < " lost. It's me. What a disappointment! :( " ;
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 )
{
2013-09-28 02:46:58 +03:00
LOG_TRACE_PARAMS ( logAi , " start '%i'; obj '%s' " , start % ( visitedObj ? visitedObj - > hoverName : std : : string ( " n/a " ) ) ) ;
2012-03-03 13:08:01 +03:00
NET_EVENT_HANDLER ;
2013-09-28 02:46:58 +03:00
if ( start )
2012-03-05 22:11:28 +03:00
{
2012-03-14 16:02:38 +03:00
markObjectVisited ( visitedObj ) ;
2013-04-21 15:11:13 +03:00
erase_if_present ( reservedObjs , visitedObj ) ; //unreserve objects
erase_if_present ( reservedHeroesMap [ 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
}
2013-06-26 14:18:27 +03:00
void VCAI : : availableArtifactsChanged ( const CGBlackMarket * bm /*= nullptr*/ )
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 ( ) ;
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 ) ;
}
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 ) ;
2013-05-27 13:53:28 +03:00
status . addQuery ( query , boost : : str ( boost : : format ( " Exchange between heroes %s and %s " ) % firstHero - > name % secondHero - > name ) ) ;
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 ;
if ( goalpriority1 > goalpriority2 )
2012-08-28 18:04:05 +03:00
pickBestCreatures ( firstHero , secondHero ) ;
2014-02-17 10:36:03 +03:00
else if ( goalpriority1 < goalpriority2 )
2012-08-28 18:04:05 +03:00
pickBestCreatures ( secondHero , firstHero ) ;
2014-02-17 10:36:03 +03:00
else //regular criteria
{
if ( firstHero - > getFightingStrength ( ) > secondHero - > getFightingStrength ( ) & & canGetArmy ( firstHero , secondHero ) )
pickBestCreatures ( firstHero , secondHero ) ;
else if ( canGetArmy ( secondHero , firstHero ) )
pickBestCreatures ( secondHero , firstHero ) ;
2012-08-28 18:04:05 +03:00
2014-02-17 10:36:03 +03: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 ( ) ) ) ) ;
2012-08-28 18:04:05 +03:00
//TODO: exchange artifacts
2014-02-17 10:36:03 +03:00
}
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 ) ;
}
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
2013-04-21 15:11:13 +03:00
erase_if_present ( visitableObjs , obj ) ;
2013-05-09 14:09:23 +03:00
erase_if_present ( alreadyVisited , obj ) ;
erase_if_present ( reservedObjs , obj ) ;
2013-06-29 16:05:48 +03:00
for ( auto & p : reservedHeroesMap )
2013-04-21 15:11:13 +03:00
erase_if_present ( p . second , obj ) ;
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
}
}
void VCAI : : receivedResource ( int type , int val )
{
2013-04-16 16:16:58 +03:00
LOG_TRACE_PARAMS ( logAi , " type '%i', val '%i' " , type % val ) ;
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 )
{
2013-03-03 20:06:03 +03:00
if ( sop - > val = = playerID . getNum ( ) )
2013-04-21 15:11:13 +03:00
erase_if_present ( visitableObjs , myCb - > getObj ( sop - > id ) ) ;
2012-02-14 21:04:45 +03:00
//TODO restore lost obj
}
}
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
}
2013-06-22 17:47:20 +03:00
void VCAI : : init ( 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 ( ) ;
2012-02-14 21:04:45 +03:00
retreiveVisitableObjs ( visitableObjs ) ;
}
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 ) ) ;
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 ) ) ;
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
}
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
{
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
}
2013-05-09 14:09:23 +03:00
void VCAI : : saveGame ( COSer < CSaveFile > & 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
}
2013-05-09 14:09:23 +03:00
void VCAI : : loadGame ( CISer < CLoadFile > & 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 ( )
{
MAKING_TURN ;
boost : : shared_lock < boost : : shared_mutex > gsLock ( cb - > getGsMutex ( ) ) ;
2012-06-27 23:44:01 +03:00
setThreadName ( " VCAI::makeTurn " ) ;
2012-02-14 21:04:45 +03:00
2014-02-05 00:49:04 +03:00
logGlobal - > infoStream ( ) < < boost : : format ( " Player %d starting turn " ) % static_cast < int > ( playerID . getNum ( ) ) ;
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
{
2013-12-25 16:38:20 +03:00
visitableObjs . insert ( obj ) ; //set doesn't need duplicate check
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
}
if ( cb - > getSelectedHero ( ) )
cb - > recalculatePaths ( ) ;
makeTurnInternal ( ) ;
2013-05-09 14:09:23 +03:00
makingTurn . reset ( ) ;
2012-02-14 21:04:45 +03:00
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 ( ) )
{
logAi - > errorStream ( ) < < " Hero " < < hero . first . name < < " present on reserved map. Shouldn't be. " ;
continue ;
}
2012-07-01 02:48:40 +03:00
cb - > setSelection ( hero . first . get ( ) ) ;
2013-12-25 16:38:20 +03:00
std : : vector < const CGObjectInstance * > vec ( hero . second . begin ( ) , hero . second . end ( ) ) ;
boost : : sort ( vec , isCloser ) ;
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
{
logAi - > errorStream ( ) < < " Error: there is wrong object on list for hero " < < hero . first - > name ;
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
{
2013-12-26 12:53:37 +03:00
logAi - > warnStream ( ) < < " Our heroes don't move anymore, exhaustive decomposition failed " ;
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 )
logAi - > warnStream ( ) < < boost : : format ( " hero %s has %d MP left " ) % h - > name % h - > movement ;
}
2012-02-14 21:04:45 +03:00
}
catch ( boost : : thread_interrupted & e )
{
2013-04-10 19:28:14 +03:00
logAi - > debugStream ( ) < < " 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 )
{
2013-04-10 19:28:14 +03:00
logAi - > debugStream ( ) < < " Making turn thread has caught an exception: " < < 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 ( ) ;
2013-04-10 19:28:14 +03:00
logAi - > debugStream ( ) < < boost : : format ( " %s will try to visit %s at (%s) " ) % h - > name % obj - > getHoverText ( ) % strFromInt3 ( dst ) ;
2012-02-14 21:04:45 +03:00
return moveHeroToTile ( dst , h ) ;
}
2012-07-01 02:48:40 +03:00
void VCAI : : performObjectInteraction ( const CGObjectInstance * obj , HeroPtr h )
2012-03-05 22:11:28 +03:00
{
2013-05-09 14:09:23 +03:00
LOG_TRACE_PARAMS ( logAi , " Hero %s and object %s at %s " , h - > name % obj - > getHoverText ( ) % obj - > pos ) ;
2012-03-05 22:11:28 +03:00
switch ( obj - > ID )
{
case Obj : : CREATURE_GENERATOR1 :
2012-05-22 13:15:16 +03:00
recruitCreatures ( dynamic_cast < const CGDwelling * > ( obj ) ) ;
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 )
{
logAi - > errorStream ( ) < < " Why are we even considering exchange between heroes from different players? " ;
return false ;
}
2012-08-29 18:23:47 +03:00
const CArmedInstance * armies [ ] = { army , source } ;
2014-02-19 19:23:47 +03: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
if ( ! ( armyPtr - > needsLastStack ( ) & & armyPtr - > Slots ( ) . size ( ) = = 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
2014-02-19 19:23:47 +03:00
if ( ! ( armyPtr - > needsLastStack ( ) & & armyPtr - > Slots ( ) . size ( ) = = 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
}
2012-03-05 22:11:28 +03:00
void VCAI : : recruitCreatures ( const CGDwelling * d )
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;
2013-09-12 00:57:08 +03:00
amin ( count , freeResources ( ) / VLC - > creh - > creatures [ creID ] - > cost ) ;
2012-02-14 21:04:45 +03:00
if ( count > 0 )
2012-03-05 22:11:28 +03:00
cb - > recruitCreatures ( d , 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 )
{
logAi - > warnStream ( ) < < " Request to build building " < < building < < " in 0 days! " ;
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 ( ) ;
TResources income = estimateIncome ( ) ;
//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 ) )
{
2013-04-10 19:28:14 +03:00
logAi - > debugStream ( ) < < boost : : format ( " Player %d will build %s in town of %s at %s " ) % playerID % b - > Name ( ) % t - > name % t - > pos ;
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 )
{
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 ] ;
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)
// {
// logAi->warnStream() << "Request to build building " << building << " in 0 days!";
// 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))
// {
// logAi->debugStream() << boost::format("Player %d will build %s in town of %s at %s") % playerID % b->Name() % t->name % t->pos;
// 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
}
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
TResources currentRes = cb - > getResourceAmount ( ) ;
TResources income = estimateIncome ( ) ;
2013-02-11 22:11:34 +03:00
if ( tryBuildAnyStructure ( t , std : : vector < BuildingID > ( essential , essential + ARRAY_COUNT ( essential ) ) ) )
2012-05-19 19:22:34 +03:00
return ;
2012-07-07 16:32:37 +03:00
//we're running out of gold - try to build something gold-producing. Multiplier can be tweaked, 6 is minimum due to buildings costs
if ( currentRes [ Res : : GOLD ] < income [ Res : : GOLD ] * 6 )
2013-02-11 22:11:34 +03:00
if ( tryBuildNextStructure ( t , std : : vector < BuildingID > ( goldSource , goldSource + ARRAY_COUNT ( goldSource ) ) ) )
2012-05-19 19:22:34 +03:00
return ;
2013-02-02 11:29:57 +03:00
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
{
2013-02-11 22:11:34 +03: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
2013-02-02 11:29:57 +03:00
if ( cb - > getDate ( Date : : DAY ) < 7 | | cb - > getDate ( Date : : DAY_OF_WEEK ) > 3 )
2013-02-11 22:11:34 +03:00
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 + + )
{
2012-08-28 07:13:50 +03:00
if ( t - > hasBuilt ( unitsSource [ i ] ) & & ! t - > hasBuilt ( unitsUpgrade [ i ] ) )
2012-05-19 19:22:34 +03:00
{
if ( tryBuildStructure ( t , unitsUpgrade [ i ] ) )
return ;
2012-02-14 21:04:45 +03:00
}
}
2012-05-19 19:22:34 +03:00
//remaining tasks
2013-02-11 22:11:34 +03:00
if ( tryBuildNextStructure ( t , std : : vector < BuildingID > ( goldSource , goldSource + ARRAY_COUNT ( goldSource ) ) ) )
2012-05-19 19:22:34 +03:00
return ;
2013-02-11 22:11:34 +03:00
if ( tryBuildNextStructure ( t , std : : vector < BuildingID > ( spells , spells + ARRAY_COUNT ( spells ) ) ) )
2012-05-19 19:22:34 +03:00
return ;
2013-02-11 22:11:34 +03:00
if ( tryBuildAnyStructure ( t , std : : vector < BuildingID > ( extra , extra + ARRAY_COUNT ( extra ) ) ) )
2012-05-19 19:22:34 +03:00
return ;
2012-02-14 21:04:45 +03:00
}
2013-12-21 22:17:27 +03:00
bool VCAI : : isGoodForVisit ( const CGObjectInstance * obj , HeroPtr h )
2012-02-14 21:04:45 +03:00
{
2013-12-21 22:17:27 +03:00
const int3 pos = obj - > visitablePos ( ) ;
if ( isAccessibleForHero ( obj - > visitablePos ( ) , h ) & &
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 ) & &
! vstd : : contains ( reservedObjs , obj ) )
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 ;
}
std : : vector < const CGObjectInstance * > VCAI : : getPossibleDestinations ( HeroPtr h )
{
validateVisitableObjs ( ) ;
std : : vector < const CGObjectInstance * > possibleDestinations ;
for ( const CGObjectInstance * obj : visitableObjs )
{
const int3 pos = obj - > visitablePos ( ) ;
if ( isGoodForVisit ( obj , h ) )
2013-12-21 20:34:59 +03:00
{
2012-02-14 21:04:45 +03:00
possibleDestinations . push_back ( obj ) ;
2013-12-21 20:34:59 +03:00
}
2012-03-05 22:11:28 +03:00
}
2012-02-14 21:04:45 +03:00
2013-12-21 20:34:59 +03:00
boost : : sort ( possibleDestinations , isCloser ) ;
2012-02-14 21:04:45 +03:00
return possibleDestinations ;
}
2014-02-20 23:18:49 +03:00
bool VCAI : : canReachTile ( 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 )
return cb - > getResourceAmount ( Res : : GOLD ) > = HERO_GOLD_COST & &
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
erase_if ( reservedHeroesMap [ h ] , [ h ] ( const CGObjectInstance * obj ) - > bool
{
return ! isSafeToVisit ( h , obj - > visitablePos ( ) ) ;
} ) ;
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 ( ) ;
2013-11-24 22:15:08 +03:00
std : : vector < ObjectIdRef > dests , tmp ;
range : : copy ( reservedHeroesMap [ h ] , std : : back_inserter ( tmp ) ) ; //visit our reserved objects first
for ( auto obj : tmp )
{
if ( isAccessibleForHero ( obj - > visitablePos ( ) , h ) ) //even nearby objects could be blocked by other heroes :(
dests . push_back ( obj ) ; //can't use lambda for member function :(
}
2012-03-29 21:26:06 +03:00
if ( ! dests . size ( ) )
2012-04-17 15:46:21 +03:00
range : : copy ( getPossibleDestinations ( h ) , std : : back_inserter ( dests ) ) ;
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 ) ;
2012-04-17 15:46:21 +03:00
dests . emplace_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 ( ) ;
logAi - > debugStream ( ) < < boost : : format ( " %s can't reach any town, we'll try to make our way to %s at %s " ) % h - > name % t - > name % t - > visitablePos ( ) ;
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
}
else if ( cb - > getResourceAmount ( Res : : GOLD ) > = 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 ( ) ;
erase_if ( towns , [ ] ( const CGTownInstance * t ) - > bool
{
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
{
logAi - > debugStream ( ) < < " 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
while ( dests . size ( ) ) //performance improvement
2012-02-14 21:04:45 +03:00
{
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 ( ) ;
logAi - > debugStream ( ) < < boost : : format ( " Of all %d destinations, object oid=%d seems nice " ) % dests . size ( ) % dest . id . getNum ( ) ;
if ( ! goVisitObj ( dest , h ) )
2012-04-17 15:46:21 +03:00
{
2014-02-05 00:49:04 +03:00
if ( ! dest )
{
logAi - > debugStream ( ) < < boost : : format ( " Visit attempt made the object (id=%d) gone... " ) % dest . id . getNum ( ) ;
}
else
{
logAi - > debugStream ( ) < < boost : : format ( " 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
}
2014-02-06 09:05:45 +03:00
//TODO: refactor removing deleted objects from the list
std : : vector < const CGObjectInstance * > hlp ;
retreiveVisitableObjs ( hlp , true ) ;
auto shouldBeErased = [ & ] ( const CGObjectInstance * obj ) - > bool
2012-04-17 15:46:21 +03:00
{
2014-02-06 09:05:45 +03:00
if ( ! vstd : : contains ( hlp , obj ) )
{
return true ;
}
return false ;
} ;
erase_if ( dests , shouldBeErased ) ;
erase_if_present ( dests , dest ) ; //why that fails sometimes when removing monsters?
boost : : sort ( dests , isCloser ) ; //find next closest one
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 )
2012-03-13 15:47:47 +03:00
{ //TODO: check for presence?
2013-11-23 21:16:25 +03:00
if ( goal - > invalid ( ) )
2013-04-21 15:11:13 +03:00
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 ;
goal - > setisElementar ( false ) ; //always evaluate goals before realizing
2013-11-24 11:56:02 +03:00
}
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
{
2014-02-05 00:49:04 +03:00
logAi - > traceStream ( ) < < boost : : format ( " 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 )
{
logAi - > debugStream ( ) < < boost : : format ( " %s " ) % 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?
{
for ( auto p : lockedHeroes )
{
2013-11-24 22:15:08 +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
{
2013-11-24 22:15:08 +03:00
logAi - > debugStream ( ) < < boost : : format ( " %s " ) % p . second - > completeMessage ( ) ;
2013-11-24 11:21:51 +03:00
lockedHeroes . erase ( lockedHeroes . find ( p . first ) ) ; //is it safe?
}
}
}
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 ) ;
2013-06-26 14:18:27 +03:00
const CGObjectInstance * presumedEnemy = 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
2012-04-14 10:22:08 +03:00
battlename = boost : : str ( boost : : format ( " Starting battle of %s attacking %s at %s " ) % ( hero1 ? hero1 - > name : " a army " ) % ( presumedEnemy ? presumedEnemy - > hoverName : " unknown enemy " ) % tile ) ;
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 ( ) ;
2013-04-10 19:28:14 +03:00
logAi - > debugStream ( ) < < boost : : format ( " Player %d: I %s the %s! " ) % playerID % ( won ? " won " : " lost " ) % battlename ;
2012-02-14 21:04:45 +03:00
battlename . clear ( ) ;
CAdventureAI : : battleEnd ( br ) ;
}
void VCAI : : waitTillFree ( )
{
2012-02-20 00:03:43 +03:00
auto unlock = vstd : : makeUnlockSharedGuard ( cb - > getGsMutex ( ) ) ;
2012-02-14 21:04:45 +03:00
status . waitTillFree ( ) ;
}
2012-03-14 16:02:38 +03:00
void VCAI : : markObjectVisited ( const CGObjectInstance * obj )
{
if ( dynamic_cast < const CGVisitableOPH * > ( obj ) | | //we may want to wisit it with another hero
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 ) ;
2013-08-19 14:50:53 +03:00
logAi - > debugStream ( ) < < " reserved object id= " < < obj - > id < < " ; address= " < < ( intptr_t ) obj < < " ; name= " < < obj - > getHoverText ( ) ;
2012-03-29 21:26:06 +03:00
}
2012-02-14 21:04:45 +03:00
void VCAI : : validateVisitableObjs ( )
{
std : : vector < const CGObjectInstance * > hlp ;
retreiveVisitableObjs ( hlp , true ) ;
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
{
2012-02-14 21:04:45 +03:00
if ( ! vstd : : contains ( hlp , obj ) )
{
2013-08-18 18:46:28 +03:00
logAi - > errorStream ( ) < < helperObjInfo [ obj ] . name < < " at " < < helperObjInfo [ obj ] . pos < < errorMsg ;
2013-04-21 15:11:13 +03:00
return true ;
2012-02-14 21:04:45 +03:00
}
2013-04-21 15:49:26 +03:00
return false ;
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! " ;
erase_if ( visitableObjs , shouldBeErased ) ;
for ( auto & p : reservedHeroesMap )
{
errorMsg = " shouldn't be on list for hero " + p . first - > name + " ! " ;
erase_if ( p . second , shouldBeErased ) ;
}
errorMsg = " shouldn't be on the reserved objs list! " ;
erase_if ( reservedObjs , shouldBeErased ) ;
//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! " ;
erase_if ( alreadyVisited , shouldBeErased ) ;
2012-02-14 21:04:45 +03:00
}
void VCAI : : retreiveVisitableObjs ( std : : vector < const CGObjectInstance * > & out , bool includeOwned /*= false*/ ) const
{
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
}
2013-12-25 16:38:20 +03:00
void VCAI : : retreiveVisitableObjs ( std : : set < const CGObjectInstance * > & out , bool includeOwned /*= false*/ ) const
{
foreach_tile_pos ( [ & ] ( const int3 & pos )
{
for ( const CGObjectInstance * obj : myCb - > getVisitableObjs ( pos , false ) )
{
if ( includeOwned | | obj - > tempOwner ! = playerID )
out . insert ( obj ) ;
}
} ) ;
}
2012-02-14 21:04:45 +03:00
std : : vector < const CGObjectInstance * > VCAI : : getFlaggedObjects ( ) const
{
std : : vector < const CGObjectInstance * > ret ;
retreiveVisitableObjs ( ret , true ) ;
erase_if ( ret , [ ] ( const CGObjectInstance * obj )
{
return obj - > tempOwner ! = ai - > playerID ;
} ) ;
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 ) ;
}
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
{
if ( obj - > ID = = 5 & & obj - > subID = = aid )
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
}
2012-07-01 02:48:40 +03:00
bool VCAI : : isAccessibleForHero ( const int3 & pos , HeroPtr h , bool includeAllies /*= false*/ ) const
2012-02-14 21:04:45 +03:00
{
2012-07-01 02:48:40 +03:00
cb - > setSelection ( * h ) ;
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 ;
}
}
2012-02-14 21:04:45 +03:00
return cb - > getPathInfo ( pos ) - > reachable ( ) ;
}
2012-07-01 02:48:40 +03:00
bool VCAI : : moveHeroToTile ( int3 dst , HeroPtr h )
2012-02-14 21:04:45 +03:00
{
2013-12-20 13:15:29 +03:00
cb - > setSelection ( h . h ) ; //make sure we are using the RIGHT pathfinder
2013-04-21 15:11:13 +03:00
logAi - > debugStream ( ) < < boost : : format ( " Moving hero %s to tile %s " ) % h - > name % dst ;
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 ) ) ;
2012-02-14 21:04:45 +03:00
waitTillFree ( ) ; //movement may cause battle or blocking dialog
ret = true ;
}
else
{
CGPath path ;
cb - > getPath2 ( dst , path ) ;
if ( path . nodes . empty ( ) )
{
2013-04-10 19:28:14 +03:00
logAi - > errorStream ( ) < < " Hero " < < h - > name < < " cannot reach " < < dst ;
2012-05-05 11:57:32 +03:00
cb - > recalculatePaths ( ) ;
2013-12-26 12:53:37 +03:00
throw goalFulfilledException ( sptr ( Goals : : VisitTile ( dst ) . sethero ( h ) ) ) ;
2012-02-14 21:04:45 +03:00
}
int i = path . nodes . size ( ) - 1 ;
for ( ; i > 0 ; i - - )
{
//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
2012-07-01 02:48:40 +03:00
cb - > moveHero ( * h , CGHeroInstance : : convertPosition ( endpos , true ) ) ;
2012-02-14 21:04:45 +03:00
waitTillFree ( ) ; //movement may cause battle or blocking dialog
2012-02-22 16:41:27 +03:00
boost : : this_thread : : interruption_point ( ) ;
2012-08-26 12:07:48 +03:00
if ( ! h ) //we lost hero - remove all tasks assigned to him/her
2012-03-13 15:47:47 +03:00
{
2012-07-01 02:48:40 +03:00
lostHero ( h ) ;
//we need to throw, otherwise hero will be assigned to sth again
2012-07-19 21:52:44 +03:00
throw std : : runtime_error ( " Hero was lost! " ) ;
2012-03-13 15:47:47 +03:00
}
2012-02-14 21:04:45 +03:00
}
ret = ! i ;
}
2013-05-09 14:09:23 +03:00
if ( auto visitedObject = frontOrNull ( cb - > getVisitableObjs ( h - > visitablePos ( ) ) ) ) //we stand on something interesting
2012-03-29 21:26:06 +03:00
{
2013-12-26 12:53:37 +03:00
if ( visitedObject ! = * h )
performObjectInteraction ( visitedObject , h ) ;
2012-10-03 12:29:55 +03:00
//BNLOG("Hero %s moved from %s to %s at %s", h->name % startHpos % visitedObject->hoverName % h->visitablePos());
//throw goalFulfilledException (CGoal(GET_OBJ).setobjid(visitedObject->id));
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
2013-12-23 23:46:01 +03:00
if ( ! ret ) //reserve object we are heading towards
{
auto obj = frontOrNull ( cb - > getVisitableObjs ( dst ) ) ;
2013-12-26 12:53:37 +03:00
if ( obj & & obj ! = * h )
2013-12-23 23:46:01 +03:00
reserveObject ( h , obj ) ;
}
2012-05-05 11:57:32 +03:00
cb - > recalculatePaths ( ) ;
2012-05-08 12:38:01 +03:00
if ( startHpos = = h - > visitablePos ( ) & & ! ret ) //we didn't move and didn't reach the target
2012-05-08 11:10:40 +03:00
{
2013-12-26 12:53:37 +03:00
erase_if_present ( lockedHeroes , h ) ; //hero seemingly is confused
throw cannotFulfillGoalException ( " Invalid path found! " ) ; //FIXME: should never happen
2012-05-08 11:10:40 +03:00
}
2012-05-05 11:57:32 +03:00
}
2013-04-21 15:11:13 +03:00
logAi - > debugStream ( ) < < boost : : format ( " Hero %s moved from %s to %s. Returning %d. " ) % h - > name % startHpos % h - > visitablePos ( ) % ret ;
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
{
throw cannotFulfillGoalException ( " EXPLORE is not a elementar goal! " ) ;
}
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 )
{
2013-11-23 21:16:25 +03:00
logAi - > warnStream ( ) < < boost : : format ( " Why do I want to move hero %s to tile %s? Already standing on that tile! " )
% g . hero - > name % g . tile ;
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?
if ( g . hero - > diggingStatus ( ) = = CGHeroInstance : : CAN_DIG )
{
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 ;
}
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 ( ) )
{
logAi - > debugStream ( ) < < boost : : format ( " Looking into %s " ) % t - > name ;
buildStructure ( t ) ;
buildArmyIn ( 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
{
logAi - > debugStream ( ) < < boost : : format ( " Attempting realizing goal with code %s " ) % g . name ( ) ;
2013-11-23 21:16:25 +03: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
{
bool digsTile = false ;
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 ( )
{
2014-02-05 00:49:04 +03:00
logAi - > infoStream ( ) < < " Player " < < static_cast < int > ( playerID . getNum ( ) ) < < " ends turn " ;
2012-02-14 21:04:45 +03:00
if ( ! status . haveTurn ( ) )
{
2013-04-10 19:28:14 +03:00
logAi - > errorStream ( ) < < " Not having turn at the end of turn??? " ;
2012-02-14 21:04:45 +03:00
}
2014-02-05 00:49:04 +03:00
logAi - > debugStream ( ) < < " Resources at the end of turn: " < < cb - > getResourceAmount ( ) ;
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
2014-02-05 00:49:04 +03:00
logGlobal - > infoStream ( ) < < " Player " < < static_cast < int > ( playerID . getNum ( ) ) < < " ended turn " ;
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 ;
2013-11-23 15:30:10 +03:00
logAi - > debugStream ( ) < < boost : : format ( " 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
{
2013-11-10 00:29:46 +03:00
logAi - > debugStream ( ) < < boost : : format ( " 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 ) ;
logAi - > debugStream ( ) < < boost : : format ( " Goal %s decomposition failed: goal was completed as much as possible " ) % goal - > name ( ) ;
return sptr ( Goals : : Invalid ( ) ) ;
2012-02-14 21:04:45 +03:00
}
catch ( std : : exception & e )
{
2013-11-10 00:29:46 +03:00
logAi - > debugStream ( ) < < boost : : format ( " 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
2013-02-23 22:13:15 +03:00
if ( ! maxGoals )
{
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 ) ;
}
2013-11-10 00:29:46 +03:00
if ( goal - > hero ) //lock this hero to fulfill ultimate goal
2012-03-13 15:47:47 +03:00
{
if ( maxGoals )
{
2013-11-23 21:16:25 +03:00
setGoal ( goal - > hero , goal ) ;
2012-03-13 15:47:47 +03:00
}
else
{
2013-12-26 12:53:37 +03:00
erase_if_present ( lockedHeroes , goal - > hero ) ; // we seemingly don't know what to do with hero
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
2013-11-10 00:29:46 +03:00
logAi - > debugStream ( ) < < boost : : format ( " Choosing abstract goal %s " ) % goal - > name ( ) ;
2012-05-08 11:10:40 +03:00
break ;
}
else
2014-02-05 00:49:04 +03:00
{
logAi - > debugStream ( ) < < boost : : format ( " 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 )
{
2013-04-10 19:28:14 +03:00
logAi - > debugStream ( ) < < boost : : format ( " 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 )
2013-11-24 18:30:17 +03: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 )
{
2013-11-23 15:30:10 +03:00
logAi - > debugStream ( ) < < boost : : format ( " Failed to realize subgoal of type %s (greater goal type was %s), I will stop. " ) % goal - > name ( ) % ultimateGoal - > name ( ) ;
2013-04-10 19:28:14 +03:00
logAi - > debugStream ( ) < < boost : : format ( " 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 ) ;
2013-04-10 19:28:14 +03:00
logAi - > debugStream ( ) < < boost : : format ( " 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
{
2012-10-03 17:49:29 +03:00
if ( q . quest - > checkQuest ( hero ) ) //veyr 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
{
2013-04-10 19:28:14 +03:00
logAi - > debugStream ( ) < < boost : : format ( " 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
}
}
2013-04-10 19:28:14 +03:00
logAi - > debugStream ( ) < < boost : : format ( " 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 )
2013-04-10 19:28:14 +03:00
logAi - > debugStream ( ) < < boost : : format ( " 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 ( )
{
2013-06-29 16:05:48 +03:00
for ( auto h : getUnblockedHeroes ( ) )
2012-02-14 21:04:45 +03:00
{
2013-04-10 19:28:14 +03:00
logAi - > debugStream ( ) < < boost : : format ( " Looking into %s, MP=%d " ) % h - > name . c_str ( ) % h - > movement ;
2012-07-01 02:48:40 +03:00
makePossibleUpgrades ( * h ) ;
cb - > setSelection ( * h ) ;
2012-03-26 01:46:14 +03:00
try
{
wander ( h ) ;
}
catch ( std : : exception & e )
{
2013-04-10 19:28:14 +03:00
logAi - > debugStream ( ) < < boost : : format ( " 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 ) ;
recruitCreatures ( t ) ;
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
{
std : : map < int3 , int > dstToRevealedTiles ;
2013-06-29 16:05:48 +03:00
for ( crint3 dir : dirs )
2012-02-14 21:04:45 +03:00
if ( cb - > isInTheMap ( hpos + dir ) )
2013-12-21 20:34:59 +03:00
if ( isSafeToVisit ( h , hpos + dir ) & & isAccessibleForHero ( hpos + dir , h ) )
dstToRevealedTiles [ hpos + dir ] = 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
{
const CGPathNode * pn = cb - > 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
{
2014-02-07 23:09:15 +03:00
//logAi->debugStream() << "Looking for an another place for exploration...";
2013-12-26 12:53:37 +03:00
cb - > setSelection ( h . h ) ;
2014-02-17 20:28:39 +03:00
int radius = h - > getSightRadious ( ) ;
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 )
{
if ( ! cb - > isVisible ( pos ) )
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 ) ;
2012-02-14 21:04:45 +03:00
for ( int i = 1 ; i < radius ; i + + )
{
getVisibleNeighbours ( tiles [ i - 1 ] , tiles [ i ] ) ;
removeDuplicates ( tiles [ i ] ) ;
2013-06-29 16:05:48 +03:00
for ( const int3 & tile : tiles [ i ] )
2012-02-14 21:04:45 +03:00
{
2013-12-21 20:34:59 +03:00
if ( cb - > getTile ( tile ) - > blocked ) //does it shorten the time?
continue ;
2014-02-17 10:36:03 +03:00
if ( ! cb - > 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 ;
cb - > getPath2 ( tile , path ) ;
float ourValue = ( float ) howManyTilesWillBeDiscovered ( tile , radius ) / ( path . nodes . size ( ) + 1 ) ; //+1 prevents erratic jumps
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
{
2014-02-17 10:36:03 +03:00
if ( isSafeToVisit ( h , tile ) & & ! isBlockedBorderGate ( tile ) )
2013-12-23 23:46:01 +03:00
{
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
{
//logAi->debugStream() << "Looking for an another place for exploration...";
SectorMap sm ( h ) ;
2014-02-17 20:28:39 +03:00
int radius = h - > getSightRadious ( ) ;
2014-02-17 10:36:03 +03:00
std : : vector < std : : vector < int3 > > tiles ; //tiles[distance_to_fow]
tiles . resize ( radius ) ;
foreach_tile_pos ( [ & ] ( const int3 & pos )
{
if ( ! cb - > isVisible ( pos ) )
tiles [ 0 ] . push_back ( pos ) ;
} ) ;
ui64 lowestDanger = - 1 ;
int3 bestTile ( - 1 , - 1 , - 1 ) ;
for ( int i = 1 ; i < radius ; i + + )
{
getVisibleNeighbours ( tiles [ i - 1 ] , tiles [ i ] ) ;
removeDuplicates ( tiles [ i ] ) ;
for ( const int3 & tile : tiles [ i ] )
{
if ( cb - > getTile ( tile ) - > blocked ) //does it shorten the time?
continue ;
if ( ! howManyTilesWillBeDiscovered ( tile , radius ) ) //avoid costly checks of tiles that don't reveal much
continue ;
auto t = sm . firstTileToGet ( h , tile ) ;
if ( t . valid ( ) )
{
ui64 ourDanger = evaluateDanger ( t , h . h ) ;
if ( ourDanger < lowestDanger )
{
if ( ! isBlockedBorderGate ( t ) )
{
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
{
ret [ Res : : GOLD ] + = t - > dailyIncome ( ) ;
//TODO duplikuje newturn
2013-02-11 02:24:57 +03:00
if ( t - > hasBuilt ( BuildingID : : RESOURCE_SILO ) ) //there is resource silo
2012-02-14 21:04:45 +03:00
{
2013-04-21 15:49:26 +03:00
if ( t - > town - > primaryRes = = Res : : WOOD_AND_ORE ) //we'll give wood and ore
2012-02-14 21:04:45 +03:00
{
ret [ Res : : WOOD ] + + ;
ret [ Res : : ORE ] + + ;
}
else
{
ret [ t - > town - > primaryRes ] + + ;
}
}
}
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
{
2013-04-10 19:28:14 +03:00
logAi - > debugStream ( ) < < boost : : format ( " Trying to recruit a hero in %s at %s " ) % t - > name % t - > visitablePos ( ) ;
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 )
makingTurn - > interrupt ( ) ;
}
2013-06-26 14:18:27 +03:00
void VCAI : : requestActionASAP ( std : : function < void ( ) > whatToDo )
2012-03-26 01:46:14 +03:00
{
2012-07-15 18:34:00 +03:00
// static boost::mutex m;
// boost::unique_lock<boost::mutex> mylock(m);
2012-03-26 01:46:14 +03:00
boost : : barrier b ( 2 ) ;
boost : : thread newThread ( [ & b , this , whatToDo ] ( )
{
2012-06-27 23:44:01 +03:00
setThreadName ( " VCAI::requestActionASAP::helper " ) ;
2012-03-26 01:46:14 +03:00
SET_GLOBAL_STATE ( this ) ;
boost : : shared_lock < boost : : shared_mutex > gsLock ( cb - > getGsMutex ( ) ) ;
b . wait ( ) ;
whatToDo ( ) ;
} ) ;
b . wait ( ) ;
}
2012-07-01 02:48:40 +03:00
void VCAI : : lostHero ( HeroPtr h )
{
2013-04-10 19:28:14 +03:00
logAi - > debugStream ( ) < < boost : : format ( " I lost my hero %s. It's best to forget and move on. " ) % h . name ;
2012-07-01 02:48:40 +03:00
2013-04-21 15:11:13 +03:00
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
{
2013-04-21 15:11:13 +03:00
erase_if_present ( reservedObjs , obj ) ; //unreserve all objects for that hero
2012-07-01 02:48:40 +03:00
}
2013-04-21 15:11:13 +03:00
erase_if_present ( reservedHeroesMap , 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
{
2013-04-10 19:28:14 +03:00
logAi - > debugStream ( ) < < boost : : format ( " 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
{
2013-04-10 19:28:14 +03:00
logAi - > debugStream ( ) < < boost : : format ( " 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
{
if ( settings [ " server " ] [ " neutralAI " ] . getType ( ) = = JsonNode : : DATA_STRING )
return settings [ " server " ] [ " neutralAI " ] . String ( ) ;
else
return " StupidAI " ;
}
2013-04-21 15:11:13 +03:00
void VCAI : : validateObject ( const CGObjectInstance * obj )
{
validateObject ( obj - > id ) ;
}
void VCAI : : validateObject ( ObjectIdRef obj )
{
auto matchesId = [ & ] ( const CGObjectInstance * hlpObj ) - > bool { return hlpObj - > id = = obj . id ; } ;
if ( ! obj )
{
erase_if ( visitableObjs , matchesId ) ;
2013-06-29 16:05:48 +03:00
for ( auto & p : reservedHeroesMap )
2013-04-21 15:11:13 +03:00
erase_if ( p . second , matchesId ) ;
2013-08-18 18:46:28 +03:00
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 ( ) ;
myRes [ Res : : GOLD ] - = GOLD_RESERVE ;
vstd : : amax ( myRes [ Res : : GOLD ] , 0 ) ;
return myRes ;
}
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 ;
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 )
2012-02-14 21:04:45 +03:00
{
boost : : unique_lock < boost : : mutex > lock ( mx ) ;
2013-05-27 13:53:28 +03:00
if ( ID = = QueryID ( - 1 ) )
2012-07-15 18:34:00 +03:00
{
2013-04-10 19:28:14 +03:00
logAi - > debugStream ( ) < < boost : : format ( " 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 ;
}
assert ( ! vstd : : contains ( remainingQueries , ID ) ) ;
2013-05-27 13:53:28 +03:00
assert ( ID . getNum ( ) > = 0 ) ;
2012-07-15 18:34:00 +03:00
remainingQueries [ ID ] = description ;
2012-02-14 21:04:45 +03:00
cv . notify_all ( ) ;
2013-04-10 19:28:14 +03:00
logAi - > debugStream ( ) < < boost : : format ( " 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 ) ;
cv . notify_all ( ) ;
2013-04-10 19:28:14 +03:00
logAi - > debugStream ( ) < < boost : : format ( " 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 ] ;
2013-04-10 19:28:14 +03:00
logAi - > debugStream ( ) < < boost : : format ( " 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
{
2013-04-10 19:28:14 +03:00
logAi - > errorStream ( ) < < " Something went really wrong, failed to answer query " < < query < < " : " < < 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
{
2013-10-13 18:24:02 +03: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.
// 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 ( ) ;
}
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 )
{
cb - > setSelection ( h . h ) ;
update ( ) ;
makeParentBFS ( h - > visitablePos ( ) ) ;
}
2013-10-18 23:17:25 +03:00
bool markIfBlocked ( ui8 & sec , crint3 pos , const TerrainTile * t )
{
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
2013-10-18 23:17:25 +03:00
bool markIfBlocked ( ui8 & sec , crint3 pos )
{
return markIfBlocked ( sec , pos , cb - > 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
{
2013-10-18 23:17:25 +03:00
clear ( ) ;
int curSector = 3 ; //0 is invisible, 1 is not explored
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 ) )
exploreNewSector ( pos , curSector + + ) ;
2012-02-14 21:04:45 +03:00
}
} ) ;
valid = true ;
}
void SectorMap : : clear ( )
{
sector = cb - > getVisibilityMap ( ) ;
valid = false ;
}
void SectorMap : : exploreNewSector ( crint3 pos , int num )
{
Sector & s = infoOnSectors [ num ] ;
s . id = num ;
s . water = cb - > getTile ( pos ) - > isWater ( ) ;
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 ( ) ;
ui8 & sec = retreiveTile ( curPos ) ;
if ( sec = = NOT_CHECKED )
{
const TerrainTile * t = cb - > getTile ( curPos ) ;
if ( ! markIfBlocked ( sec , curPos , t ) )
{
if ( t - > isWater ( ) = = s . water ) //sector is only-water or only-land
{
sec = num ;
s . tiles . push_back ( curPos ) ;
foreach_neighbour ( curPos , [ & ] ( crint3 neighPos )
{
if ( retreiveTile ( neighPos ) = = NOT_CHECKED )
{
toVisit . push ( neighPos ) ;
//parent[neighPos] = curPos;
}
const TerrainTile * nt = cb - > getTile ( neighPos , false ) ;
if ( nt & & nt - > isWater ( ) ! = s . water & & canBeEmbarkmentPoint ( nt ) )
{
s . embarkmentPoints . push_back ( neighPos ) ;
}
} ) ;
2014-02-20 23:18:49 +03:00
if ( t - > visitable )
{
auto obj = t - > visitableObjects . front ( ) ;
if ( vstd : : contains ( ai - > knownSubterraneanGates , obj ) )
{ //not really sure what does it do, but subtrranean gates do not make one sector
2014-02-21 10:48:38 +03:00
//toVisit.push(ai->knownSubterraneanGates[obj]->visitablePos());
2014-02-20 23:18:49 +03:00
s . subterraneanGates . push_back ( obj ) ;
}
}
2012-02-14 21:04:45 +03:00
}
}
}
}
removeDuplicates ( s . embarkmentPoints ) ;
}
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 )
2013-12-22 00:31:28 +03: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
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 :
{
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 ;
}
2012-03-14 16:02:38 +03:00
case Obj : : MONOLITH1 :
case Obj : : MONOLITH2 :
case Obj : : MONOLITH3 :
case Obj : : WHIRLPOOL :
2012-07-19 19:29:29 +03:00
//TODO: mechanism for handling monoliths
2012-03-14 16:02:38 +03:00
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 :
return ai - > myCb - > getHeroesInfo ( ) . size ( ) < GameConstants : : MAX_HEROES_PER_PLAYER ;
2012-09-28 23:49:23 +03:00
case Obj : : BOAT :
return false ;
//Boats are handled by pathfinder
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 ) ;
if ( sourceSector ! = destinationSector )
{
const Sector * src = & infoOnSectors [ sourceSector ] ,
2014-02-21 10:48:38 +03:00
* dest = & infoOnSectors [ destinationSector ] ;
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-20 23:18:49 +03:00
for ( auto gate : s - > subterraneanGates )
{
auto gatePair = ai - > knownSubterraneanGates . find ( gate ) ;
if ( gatePair ! = ai - > knownSubterraneanGates . end ( ) )
{
//check the other side of gate
Sector * neigh = & infoOnSectors [ retreiveTile ( gatePair - > second - > visitablePos ( ) ) ] ;
if ( ! preds [ neigh ] ) //if we didn't come into this sector yet
{
preds [ neigh ] = s ; //it becomes our new target sector
2014-02-21 10:48:38 +03:00
sectorQueue . push ( neigh ) ;
2014-02-20 23:18:49 +03:00
}
}
}
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-21 12:32:24 +03:00
//ai->completeGoal (sptr(Goals::Explore(h))); //if we can't find the way, seemingly all tiles were explored
2012-05-09 10:56:39 +03:00
//TODO: more organized way?
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
{
const Sector * sectorToReach = toTraverse . at ( toTraverse . size ( ) - 2 ) ;
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
{
const TerrainTile * t = cb - > 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 ) ;
}
std : : vector < const CGObjectInstance * > visObjs ;
ai - > retreiveVisitableObjs ( visObjs , true ) ;
2013-06-29 16:05:48 +03:00
for ( const CGObjectInstance * obj : visObjs )
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
}
2014-02-20 23:18:49 +03:00
else //use subterranean gates
2012-02-14 21:04:45 +03:00
{
2014-02-20 23:18:49 +03:00
auto firstGate = boost : : find_if ( src - > subterraneanGates , [ = ] ( const CGObjectInstance * gate ) - > bool
{
2014-02-21 12:32:24 +03:00
//make sure no hero block the way
auto pos = ai - > knownSubterraneanGates [ gate ] - > visitablePos ( ) ;
const TerrainTile * t = cb - > getTile ( pos ) ;
return t & & t - > visitableObjects . size ( ) = = 1 & & t - > topVisitableId ( ) = = Obj : : SUBTERRANEAN_GATE
& & retreiveTile ( pos ) = = sectorToReach - > id ;
2014-02-20 23:18:49 +03:00
} ) ;
if ( firstGate ! = src - > subterraneanGates . end ( ) )
{
2014-02-21 12:32:24 +03:00
//TODO: pahtfinder can find path through subterranean gates, but this function only reaches closest gate
2014-02-20 23:18:49 +03:00
return ( * firstGate ) - > visitablePos ( ) ;
}
2012-02-14 21:04:45 +03:00
//TODO
2014-02-20 23:18:49 +03:00
//Monolith? Whirlpool? ...
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
{
int3 curtile = dst ;
while ( curtile ! = h - > visitablePos ( ) )
{
2014-02-17 20:28:39 +03:00
auto topObj = cb - > getTopObj ( curtile ) ;
2014-02-15 19:38:51 +03:00
if ( topObj & & topObj - > ID = = Obj : : HERO & & topObj ! = h . h )
{
logAi - > warnStream ( ) < < ( " Another allied hero stands in our way " ) ;
return ret ;
}
2012-02-14 21:04:45 +03:00
if ( cb - > getPathInfo ( curtile ) - > reachable ( ) )
{
return curtile ;
}
else
{
auto i = parent . find ( curtile ) ;
if ( i ! = parent . end ( ) )
{
assert ( curtile ! = i - > second ) ;
curtile = i - > second ;
}
else
2014-02-15 19:38:51 +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
//FIXME: find out why this line is reached
logAi - > errorStream ( ) < < ( " Impossible happened at SectorMap::firstTileToGet " ) ;
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 ( ) ;
ui8 & sec = retreiveTile ( curPos ) ;
assert ( sec = = mySector ) ; //consider only tiles from the same sector
2013-11-09 16:49:36 +03:00
UNUSED ( sec ) ;
2014-02-17 20:28:39 +03: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
}
}
unsigned char & SectorMap : : retreiveTile ( crint3 pos )
{
return retreiveTileN ( sector , pos ) ;
2012-04-16 20:12:39 +03:00
}
2012-04-17 15:46:21 +03:00