BattleAI.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833
  1. /*
  2. * BattleAI.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "BattleAI.h"
  12. #include "BattleExchangeVariant.h"
  13. #include "StackWithBonuses.h"
  14. #include "EnemyInfo.h"
  15. #include "../../lib/CStopWatch.h"
  16. #include "../../lib/CThreadHelper.h"
  17. #include "../../lib/mapObjects/CGTownInstance.h"
  18. #include "../../lib/spells/CSpellHandler.h"
  19. #include "../../lib/spells/ISpellMechanics.h"
  20. #include "../../lib/battle/BattleStateInfoForRetreat.h"
  21. #include "../../lib/CStack.h" // TODO: remove
  22. // Eventually only IBattleInfoCallback and battle::Unit should be used,
  23. // CUnitState should be private and CStack should be removed completely
  24. #define LOGL(text) print(text)
  25. #define LOGFL(text, formattingEl) print(boost::str(boost::format(text) % formattingEl))
  26. enum class SpellTypes
  27. {
  28. ADVENTURE, BATTLE, OTHER
  29. };
  30. SpellTypes spellType(const CSpell * spell)
  31. {
  32. if(!spell->isCombat() || spell->isCreatureAbility())
  33. return SpellTypes::OTHER;
  34. if(spell->isOffensive() || spell->hasEffects() || spell->hasBattleEffects())
  35. return SpellTypes::BATTLE;
  36. return SpellTypes::OTHER;
  37. }
  38. std::vector<BattleHex> CBattleAI::getBrokenWallMoatHexes() const
  39. {
  40. std::vector<BattleHex> result;
  41. for(EWallPart wallPart : { EWallPart::BOTTOM_WALL, EWallPart::BELOW_GATE, EWallPart::OVER_GATE, EWallPart::UPPER_WALL })
  42. {
  43. auto state = cb->battleGetWallState(wallPart);
  44. if(state != EWallState::DESTROYED)
  45. continue;
  46. auto wallHex = cb->wallPartToBattleHex((EWallPart)wallPart);
  47. auto moatHex = wallHex.cloneInDirection(BattleHex::LEFT);
  48. result.push_back(moatHex);
  49. }
  50. return result;
  51. }
  52. CBattleAI::CBattleAI()
  53. : side(-1),
  54. wasWaitingForRealize(false),
  55. wasUnlockingGs(false)
  56. {
  57. }
  58. CBattleAI::~CBattleAI()
  59. {
  60. if(cb)
  61. {
  62. //Restore previous state of CB - it may be shared with the main AI (like VCAI)
  63. cb->waitTillRealize = wasWaitingForRealize;
  64. cb->unlockGsWhenWaiting = wasUnlockingGs;
  65. }
  66. }
  67. void CBattleAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB)
  68. {
  69. setCbc(CB);
  70. env = ENV;
  71. cb = CB;
  72. playerID = *CB->getPlayerID(); //TODO should be sth in callback
  73. wasWaitingForRealize = CB->waitTillRealize;
  74. wasUnlockingGs = CB->unlockGsWhenWaiting;
  75. CB->waitTillRealize = true;
  76. CB->unlockGsWhenWaiting = false;
  77. movesSkippedByDefense = 0;
  78. }
  79. BattleAction CBattleAI::activeStack( const CStack * stack )
  80. {
  81. LOG_TRACE_PARAMS(logAi, "stack: %s", stack->nodeName());
  82. BattleAction result = BattleAction::makeDefend(stack);
  83. setCbc(cb); //TODO: make solid sure that AIs always use their callbacks (need to take care of event handlers too)
  84. try
  85. {
  86. if(stack->type->idNumber == CreatureID::CATAPULT)
  87. return useCatapult(stack);
  88. if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON) && stack->hasBonusOfType(Bonus::HEALER))
  89. {
  90. auto healingTargets = cb->battleGetStacks(CBattleInfoEssentials::ONLY_MINE);
  91. std::map<int, const CStack*> woundHpToStack;
  92. for(auto stack : healingTargets)
  93. if(auto woundHp = stack->MaxHealth() - stack->getFirstHPleft())
  94. woundHpToStack[woundHp] = stack;
  95. if(woundHpToStack.empty())
  96. return BattleAction::makeDefend(stack);
  97. else
  98. return BattleAction::makeHeal(stack, woundHpToStack.rbegin()->second); //last element of the woundHpToStack is the most wounded stack
  99. }
  100. attemptCastingSpell();
  101. if(cb->battleIsFinished() || !stack->alive())
  102. {
  103. //spellcast may finish battle or kill active stack
  104. //send special preudo-action
  105. BattleAction cancel;
  106. cancel.actionType = EActionType::CANCEL;
  107. return cancel;
  108. }
  109. if(auto action = considerFleeingOrSurrendering())
  110. return *action;
  111. //best action is from effective owner point if view, we are effective owner as we received "activeStack"
  112. //evaluate casting spell for spellcasting stack
  113. boost::optional<PossibleSpellcast> bestSpellcast(boost::none);
  114. //TODO: faerie dragon type spell should be selected by server
  115. SpellID creatureSpellToCast = cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), stack, CBattleInfoCallback::RANDOM_AIMED);
  116. if(stack->hasBonusOfType(Bonus::SPELLCASTER) && stack->canCast() && creatureSpellToCast != SpellID::NONE)
  117. {
  118. const CSpell * spell = creatureSpellToCast.toSpell();
  119. if(spell->canBeCast(getCbc().get(), spells::Mode::CREATURE_ACTIVE, stack))
  120. {
  121. std::vector<PossibleSpellcast> possibleCasts;
  122. spells::BattleCast temp(getCbc().get(), stack, spells::Mode::CREATURE_ACTIVE, spell);
  123. for(auto & target : temp.findPotentialTargets())
  124. {
  125. PossibleSpellcast ps;
  126. ps.dest = target;
  127. ps.spell = spell;
  128. evaluateCreatureSpellcast(stack, ps);
  129. possibleCasts.push_back(ps);
  130. }
  131. std::sort(possibleCasts.begin(), possibleCasts.end(), [&](const PossibleSpellcast & lhs, const PossibleSpellcast & rhs) { return lhs.value > rhs.value; });
  132. if(!possibleCasts.empty() && possibleCasts.front().value > 0)
  133. {
  134. bestSpellcast = boost::optional<PossibleSpellcast>(possibleCasts.front());
  135. }
  136. }
  137. }
  138. HypotheticBattle hb(env.get(), cb);
  139. PotentialTargets targets(stack, hb);
  140. BattleExchangeEvaluator scoreEvaluator(cb, env);
  141. auto moveTarget = scoreEvaluator.findMoveTowardsUnreachable(stack, targets, hb);
  142. int64_t score = EvaluationResult::INEFFECTIVE_SCORE;
  143. if(!targets.possibleAttacks.empty())
  144. {
  145. #if BATTLE_TRACE_LEVEL>=1
  146. logAi->trace("Evaluating attack for %s", stack->getDescription());
  147. #endif
  148. auto evaluationResult = scoreEvaluator.findBestTarget(stack, targets, hb);
  149. auto & bestAttack = evaluationResult.bestAttack;
  150. //TODO: consider more complex spellcast evaluation, f.e. because "re-retaliation" during enemy move in same turn for melee attack etc.
  151. if(bestSpellcast.is_initialized() && bestSpellcast->value > bestAttack.damageDiff())
  152. {
  153. // return because spellcast value is damage dealt and score is dps reduce
  154. movesSkippedByDefense = 0;
  155. return BattleAction::makeCreatureSpellcast(stack, bestSpellcast->dest, bestSpellcast->spell->id);
  156. }
  157. if(evaluationResult.score > score)
  158. {
  159. score = evaluationResult.score;
  160. std::string action;
  161. if(evaluationResult.wait)
  162. {
  163. result = BattleAction::makeWait(stack);
  164. action = "wait";
  165. }
  166. else if(bestAttack.attack.shooting)
  167. {
  168. result = BattleAction::makeShotAttack(stack, bestAttack.attack.defender);
  169. action = "shot";
  170. movesSkippedByDefense = 0;
  171. }
  172. else
  173. {
  174. result = BattleAction::makeMeleeAttack(stack, bestAttack.attack.defender->getPosition(), bestAttack.from);
  175. action = "melee";
  176. movesSkippedByDefense = 0;
  177. }
  178. logAi->debug("BattleAI: %s -> %s x %d, %s, from %d curpos %d dist %d speed %d: +%lld -%lld = %lld",
  179. bestAttack.attackerState->unitType()->getJsonKey(),
  180. bestAttack.affectedUnits[0]->unitType()->getJsonKey(),
  181. (int)bestAttack.affectedUnits[0]->getCount(), action, (int)bestAttack.from, (int)bestAttack.attack.attacker->getPosition().hex,
  182. bestAttack.attack.chargeDistance, bestAttack.attack.attacker->Speed(0, true),
  183. bestAttack.defenderDamageReduce, bestAttack.attackerDamageReduce, bestAttack.attackValue()
  184. );
  185. }
  186. }
  187. else if(bestSpellcast.is_initialized())
  188. {
  189. movesSkippedByDefense = 0;
  190. return BattleAction::makeCreatureSpellcast(stack, bestSpellcast->dest, bestSpellcast->spell->id);
  191. }
  192. //ThreatMap threatsToUs(stack); // These lines may be usefull but they are't used in the code.
  193. if(moveTarget.score > score)
  194. {
  195. score = moveTarget.score;
  196. if(stack->waited())
  197. {
  198. result = goTowardsNearest(stack, moveTarget.positions);
  199. }
  200. else
  201. {
  202. result = BattleAction::makeWait(stack);
  203. }
  204. }
  205. if(score <= EvaluationResult::INEFFECTIVE_SCORE
  206. && !stack->hasBonusOfType(Bonus::FLYING)
  207. && stack->unitSide() == BattleSide::ATTACKER
  208. && cb->battleGetSiegeLevel() >= CGTownInstance::CITADEL)
  209. {
  210. auto brokenWallMoat = getBrokenWallMoatHexes();
  211. if(brokenWallMoat.size())
  212. {
  213. movesSkippedByDefense = 0;
  214. if(stack->doubleWide() && vstd::contains(brokenWallMoat, stack->getPosition()))
  215. result = BattleAction::makeMove(stack, stack->getPosition().cloneInDirection(BattleHex::RIGHT));
  216. else
  217. result = goTowardsNearest(stack, brokenWallMoat);
  218. }
  219. }
  220. }
  221. catch(boost::thread_interrupted &)
  222. {
  223. throw;
  224. }
  225. catch(std::exception &e)
  226. {
  227. logAi->error("Exception occurred in %s %s",__FUNCTION__, e.what());
  228. }
  229. if(result.actionType == EActionType::DEFEND)
  230. {
  231. movesSkippedByDefense++;
  232. }
  233. else if(result.actionType != EActionType::WAIT)
  234. {
  235. movesSkippedByDefense = 0;
  236. }
  237. return result;
  238. }
  239. BattleAction CBattleAI::goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes) const
  240. {
  241. auto reachability = cb->getReachability(stack);
  242. auto avHexes = cb->battleGetAvailableHexes(reachability, stack);
  243. if(!avHexes.size() || !hexes.size()) //we are blocked or dest is blocked
  244. {
  245. return BattleAction::makeDefend(stack);
  246. }
  247. std::sort(hexes.begin(), hexes.end(), [&](BattleHex h1, BattleHex h2) -> bool
  248. {
  249. return reachability.distances[h1] < reachability.distances[h2];
  250. });
  251. for(auto hex : hexes)
  252. {
  253. if(vstd::contains(avHexes, hex))
  254. {
  255. return BattleAction::makeMove(stack, hex);
  256. }
  257. if(stack->coversPos(hex))
  258. {
  259. logAi->warn("Warning: already standing on neighbouring tile!");
  260. //We shouldn't even be here...
  261. return BattleAction::makeDefend(stack);
  262. }
  263. }
  264. BattleHex bestNeighbor = hexes.front();
  265. if(reachability.distances[bestNeighbor] > GameConstants::BFIELD_SIZE)
  266. {
  267. return BattleAction::makeDefend(stack);
  268. }
  269. BattleExchangeEvaluator scoreEvaluator(cb, env);
  270. HypotheticBattle hb(env.get(), cb);
  271. scoreEvaluator.updateReachabilityMap(hb);
  272. if(stack->hasBonusOfType(Bonus::FLYING))
  273. {
  274. std::set<BattleHex> moatHexes;
  275. if(hb.battleGetSiegeLevel() >= BuildingID::CITADEL)
  276. {
  277. auto townMoat = hb.getDefendedTown()->town->moatHexes;
  278. moatHexes = std::set<BattleHex>(townMoat.begin(), townMoat.end());
  279. }
  280. // Flying stack doesn't go hex by hex, so we can't backtrack using predecessors.
  281. // We just check all available hexes and pick the one closest to the target.
  282. auto nearestAvailableHex = vstd::minElementByFun(avHexes, [&](BattleHex hex) -> int
  283. {
  284. const int MOAT_PENALTY = 100; // avoid landing on moat
  285. const int BLOCKED_STACK_PENALTY = 100; // avoid landing on moat
  286. auto distance = BattleHex::getDistance(bestNeighbor, hex);
  287. if(vstd::contains(moatHexes, hex))
  288. distance += MOAT_PENALTY;
  289. return scoreEvaluator.checkPositionBlocksOurStacks(hb, stack, hex) ? BLOCKED_STACK_PENALTY + distance : distance;
  290. });
  291. return BattleAction::makeMove(stack, *nearestAvailableHex);
  292. }
  293. else
  294. {
  295. BattleHex currentDest = bestNeighbor;
  296. while(1)
  297. {
  298. if(!currentDest.isValid())
  299. {
  300. return BattleAction::makeDefend(stack);
  301. }
  302. if(vstd::contains(avHexes, currentDest)
  303. && !scoreEvaluator.checkPositionBlocksOurStacks(hb, stack, currentDest))
  304. return BattleAction::makeMove(stack, currentDest);
  305. currentDest = reachability.predecessors[currentDest];
  306. }
  307. }
  308. }
  309. BattleAction CBattleAI::useCatapult(const CStack * stack)
  310. {
  311. BattleAction attack;
  312. BattleHex targetHex = BattleHex::INVALID;
  313. if(cb->battleGetGateState() == EGateState::CLOSED)
  314. {
  315. targetHex = cb->wallPartToBattleHex(EWallPart::GATE);
  316. }
  317. else
  318. {
  319. EWallPart wallParts[] = {
  320. EWallPart::KEEP,
  321. EWallPart::BOTTOM_TOWER,
  322. EWallPart::UPPER_TOWER,
  323. EWallPart::BELOW_GATE,
  324. EWallPart::OVER_GATE,
  325. EWallPart::BOTTOM_WALL,
  326. EWallPart::UPPER_WALL
  327. };
  328. for(auto wallPart : wallParts)
  329. {
  330. auto wallState = cb->battleGetWallState(wallPart);
  331. if(wallState == EWallState::REINFORCED || wallState == EWallState::INTACT || wallState == EWallState::DAMAGED)
  332. {
  333. targetHex = cb->wallPartToBattleHex(wallPart);
  334. break;
  335. }
  336. }
  337. }
  338. if(!targetHex.isValid())
  339. {
  340. return BattleAction::makeDefend(stack);
  341. }
  342. attack.aimToHex(targetHex);
  343. attack.actionType = EActionType::CATAPULT;
  344. attack.side = side;
  345. attack.stackNumber = stack->ID;
  346. movesSkippedByDefense = 0;
  347. return attack;
  348. }
  349. void CBattleAI::attemptCastingSpell()
  350. {
  351. auto hero = cb->battleGetMyHero();
  352. if(!hero)
  353. return;
  354. if(cb->battleCanCastSpell(hero, spells::Mode::HERO) != ESpellCastProblem::OK)
  355. return;
  356. LOGL("Casting spells sounds like fun. Let's see...");
  357. //Get all spells we can cast
  358. std::vector<const CSpell*> possibleSpells;
  359. vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [hero, this](const CSpell *s) -> bool
  360. {
  361. return s->canBeCast(cb.get(), spells::Mode::HERO, hero);
  362. });
  363. LOGFL("I can cast %d spells.", possibleSpells.size());
  364. vstd::erase_if(possibleSpells, [](const CSpell *s)
  365. {
  366. return spellType(s) != SpellTypes::BATTLE;
  367. });
  368. LOGFL("I know how %d of them works.", possibleSpells.size());
  369. //Get possible spell-target pairs
  370. std::vector<PossibleSpellcast> possibleCasts;
  371. for(auto spell : possibleSpells)
  372. {
  373. spells::BattleCast temp(cb.get(), hero, spells::Mode::HERO, spell);
  374. for(auto & target : temp.findPotentialTargets())
  375. {
  376. PossibleSpellcast ps;
  377. ps.dest = target;
  378. ps.spell = spell;
  379. possibleCasts.push_back(ps);
  380. }
  381. }
  382. LOGFL("Found %d spell-target combinations.", possibleCasts.size());
  383. if(possibleCasts.empty())
  384. return;
  385. using ValueMap = PossibleSpellcast::ValueMap;
  386. auto evaluateQueue = [&](ValueMap & values, const std::vector<battle::Units> & queue, HypotheticBattle & state, size_t minTurnSpan, bool * enemyHadTurnOut) -> bool
  387. {
  388. bool firstRound = true;
  389. bool enemyHadTurn = false;
  390. size_t ourTurnSpan = 0;
  391. bool stop = false;
  392. for(auto & round : queue)
  393. {
  394. if(!firstRound)
  395. state.nextRound(0);//todo: set actual value?
  396. for(auto unit : round)
  397. {
  398. if(!vstd::contains(values, unit->unitId()))
  399. values[unit->unitId()] = 0;
  400. if(!unit->alive())
  401. continue;
  402. if(state.battleGetOwner(unit) != playerID)
  403. {
  404. enemyHadTurn = true;
  405. if(!firstRound || state.battleCastSpells(unit->unitSide()) == 0)
  406. {
  407. //enemy could counter our spell at this point
  408. //anyway, we do not know what enemy will do
  409. //just stop evaluation
  410. stop = true;
  411. break;
  412. }
  413. }
  414. else if(!enemyHadTurn)
  415. {
  416. ourTurnSpan++;
  417. }
  418. state.nextTurn(unit->unitId());
  419. PotentialTargets pt(unit, state);
  420. if(!pt.possibleAttacks.empty())
  421. {
  422. AttackPossibility ap = pt.bestAction();
  423. auto swb = state.getForUpdate(unit->unitId());
  424. *swb = *ap.attackerState;
  425. if(ap.defenderDamageReduce > 0)
  426. swb->removeUnitBonus(Bonus::UntilAttack);
  427. if(ap.attackerDamageReduce > 0)
  428. swb->removeUnitBonus(Bonus::UntilBeingAttacked);
  429. for(auto affected : ap.affectedUnits)
  430. {
  431. swb = state.getForUpdate(affected->unitId());
  432. *swb = *affected;
  433. if(ap.defenderDamageReduce > 0)
  434. swb->removeUnitBonus(Bonus::UntilBeingAttacked);
  435. if(ap.attackerDamageReduce > 0 && ap.attack.defender->unitId() == affected->unitId())
  436. swb->removeUnitBonus(Bonus::UntilAttack);
  437. }
  438. }
  439. auto bav = pt.bestActionValue();
  440. //best action is from effective owner`s point if view, we need to convert to our point if view
  441. if(state.battleGetOwner(unit) != playerID)
  442. bav = -bav;
  443. values[unit->unitId()] += bav;
  444. }
  445. firstRound = false;
  446. if(stop)
  447. break;
  448. }
  449. if(enemyHadTurnOut)
  450. *enemyHadTurnOut = enemyHadTurn;
  451. return ourTurnSpan >= minTurnSpan;
  452. };
  453. ValueMap valueOfStack;
  454. ValueMap healthOfStack;
  455. TStacks all = cb->battleGetAllStacks(false);
  456. size_t ourRemainingTurns = 0;
  457. for(auto unit : all)
  458. {
  459. healthOfStack[unit->unitId()] = unit->getAvailableHealth();
  460. valueOfStack[unit->unitId()] = 0;
  461. if(cb->battleGetOwner(unit) == playerID && unit->canMove() && !unit->moved())
  462. ourRemainingTurns++;
  463. }
  464. LOGFL("I have %d turns left in this round", ourRemainingTurns);
  465. const bool castNow = ourRemainingTurns <= 1;
  466. if(castNow)
  467. print("I should try to cast a spell now");
  468. else
  469. print("I could wait better moment to cast a spell");
  470. auto amount = all.size();
  471. std::vector<battle::Units> turnOrder;
  472. cb->battleGetTurnOrder(turnOrder, amount, 2); //no more than 1 turn after current, each unit at least once
  473. {
  474. bool enemyHadTurn = false;
  475. HypotheticBattle state(env.get(), cb);
  476. evaluateQueue(valueOfStack, turnOrder, state, 0, &enemyHadTurn);
  477. if(!enemyHadTurn)
  478. {
  479. auto battleIsFinishedOpt = state.battleIsFinished();
  480. if(battleIsFinishedOpt)
  481. {
  482. print("No need to cast a spell. Battle will finish soon.");
  483. return;
  484. }
  485. }
  486. }
  487. struct ScriptsCache
  488. {
  489. //todo: re-implement scripts context cache
  490. };
  491. auto evaluateSpellcast = [&] (PossibleSpellcast * ps, std::shared_ptr<ScriptsCache>)
  492. {
  493. HypotheticBattle state(env.get(), cb);
  494. spells::BattleCast cast(&state, hero, spells::Mode::HERO, ps->spell);
  495. cast.castEval(state.getServerCallback(), ps->dest);
  496. ValueMap newHealthOfStack;
  497. ValueMap newValueOfStack;
  498. size_t ourUnits = 0;
  499. std::set<uint32_t> unitIds;
  500. state.battleGetUnitsIf([&](const battle::Unit * u)->bool
  501. {
  502. if(!u->isGhost() && !u->isTurret())
  503. unitIds.insert(u->unitId());
  504. return false;
  505. });
  506. for(auto unitId : unitIds)
  507. {
  508. auto localUnit = state.battleGetUnitByID(unitId);
  509. newHealthOfStack[unitId] = localUnit->getAvailableHealth();
  510. newValueOfStack[unitId] = 0;
  511. if(state.battleGetOwner(localUnit) == playerID && localUnit->alive() && localUnit->willMove())
  512. ourUnits++;
  513. }
  514. size_t minTurnSpan = ourUnits/3; //todo: tweak this
  515. std::vector<battle::Units> newTurnOrder;
  516. state.battleGetTurnOrder(newTurnOrder, amount, 2);
  517. const bool turnSpanOK = evaluateQueue(newValueOfStack, newTurnOrder, state, minTurnSpan, nullptr);
  518. if(turnSpanOK || castNow)
  519. {
  520. int64_t totalGain = 0;
  521. for(auto unitId : unitIds)
  522. {
  523. auto localUnit = state.battleGetUnitByID(unitId);
  524. auto newValue = getValOr(newValueOfStack, unitId, 0);
  525. auto oldValue = getValOr(valueOfStack, unitId, 0);
  526. auto healthDiff = newHealthOfStack[unitId] - healthOfStack[unitId];
  527. if(localUnit->unitOwner() != playerID)
  528. healthDiff = -healthDiff;
  529. if(healthDiff < 0)
  530. {
  531. ps->value = -1;
  532. return; //do not damage own units at all
  533. }
  534. totalGain += (newValue - oldValue + healthDiff);
  535. }
  536. ps->value = totalGain;
  537. }
  538. else
  539. {
  540. ps->value = -1;
  541. }
  542. };
  543. using EvalRunner = ThreadPool<ScriptsCache>;
  544. EvalRunner::Tasks tasks;
  545. for(PossibleSpellcast & psc : possibleCasts)
  546. tasks.push_back(std::bind(evaluateSpellcast, &psc, _1));
  547. uint32_t threadCount = boost::thread::hardware_concurrency();
  548. if(threadCount == 0)
  549. {
  550. logGlobal->warn("No information of CPU cores available");
  551. threadCount = 1;
  552. }
  553. CStopWatch timer;
  554. std::vector<std::shared_ptr<ScriptsCache>> scriptsPool;
  555. for(uint32_t idx = 0; idx < threadCount; idx++)
  556. {
  557. scriptsPool.emplace_back();
  558. }
  559. EvalRunner runner(&tasks, scriptsPool);
  560. runner.run();
  561. LOGFL("Evaluation took %d ms", timer.getDiff());
  562. auto pscValue = [](const PossibleSpellcast &ps) -> int64_t
  563. {
  564. return ps.value;
  565. };
  566. auto castToPerform = *vstd::maxElementByFun(possibleCasts, pscValue);
  567. if(castToPerform.value > 0)
  568. {
  569. LOGFL("Best spell is %s (value %d). Will cast.", castToPerform.spell->getNameTranslated() % castToPerform.value);
  570. BattleAction spellcast;
  571. spellcast.actionType = EActionType::HERO_SPELL;
  572. spellcast.actionSubtype = castToPerform.spell->id;
  573. spellcast.setTarget(castToPerform.dest);
  574. spellcast.side = side;
  575. spellcast.stackNumber = (!side) ? -1 : -2;
  576. cb->battleMakeAction(&spellcast);
  577. movesSkippedByDefense = 0;
  578. }
  579. else
  580. {
  581. LOGFL("Best spell is %s. But it is actually useless (value %d).", castToPerform.spell->getNameTranslated() % castToPerform.value);
  582. }
  583. }
  584. //Below method works only for offensive spells
  585. void CBattleAI::evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps)
  586. {
  587. using ValueMap = PossibleSpellcast::ValueMap;
  588. RNGStub rngStub;
  589. HypotheticBattle state(env.get(), cb);
  590. TStacks all = cb->battleGetAllStacks(false);
  591. ValueMap healthOfStack;
  592. ValueMap newHealthOfStack;
  593. for(auto unit : all)
  594. {
  595. healthOfStack[unit->unitId()] = unit->getAvailableHealth();
  596. }
  597. spells::BattleCast cast(&state, stack, spells::Mode::CREATURE_ACTIVE, ps.spell);
  598. cast.castEval(state.getServerCallback(), ps.dest);
  599. for(auto unit : all)
  600. {
  601. auto unitId = unit->unitId();
  602. auto localUnit = state.battleGetUnitByID(unitId);
  603. newHealthOfStack[unitId] = localUnit->getAvailableHealth();
  604. }
  605. int64_t totalGain = 0;
  606. for(auto unit : all)
  607. {
  608. auto unitId = unit->unitId();
  609. auto localUnit = state.battleGetUnitByID(unitId);
  610. auto healthDiff = newHealthOfStack[unitId] - healthOfStack[unitId];
  611. if(localUnit->unitOwner() != getCbc()->getPlayerID())
  612. healthDiff = -healthDiff;
  613. if(healthDiff < 0)
  614. {
  615. ps.value = -1;
  616. return; //do not damage own units at all
  617. }
  618. totalGain += healthDiff;
  619. }
  620. ps.value = totalGain;
  621. }
  622. void CBattleAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side)
  623. {
  624. LOG_TRACE(logAi);
  625. side = Side;
  626. }
  627. void CBattleAI::print(const std::string &text) const
  628. {
  629. logAi->trace("%s Battle AI[%p]: %s", playerID.getStr(), this, text);
  630. }
  631. boost::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering()
  632. {
  633. BattleStateInfoForRetreat bs;
  634. bs.canFlee = cb->battleCanFlee();
  635. bs.canSurrender = cb->battleCanSurrender(playerID);
  636. bs.ourSide = cb->battleGetMySide();
  637. bs.ourHero = cb->battleGetMyHero();
  638. bs.enemyHero = nullptr;
  639. for(auto stack : cb->battleGetAllStacks(false))
  640. {
  641. if(stack->alive())
  642. {
  643. if(stack->side == bs.ourSide)
  644. bs.ourStacks.push_back(stack);
  645. else
  646. {
  647. bs.enemyStacks.push_back(stack);
  648. bs.enemyHero = cb->battleGetOwnerHero(stack);
  649. }
  650. }
  651. }
  652. bs.turnsSkippedByDefense = movesSkippedByDefense / bs.ourStacks.size();
  653. if(!bs.canFlee || !bs.canSurrender)
  654. {
  655. return boost::none;
  656. }
  657. auto result = cb->makeSurrenderRetreatDecision(bs);
  658. if(!result && bs.canFlee && bs.turnsSkippedByDefense > 30)
  659. {
  660. return BattleAction::makeRetreat(bs.ourSide);
  661. }
  662. return result;
  663. }