BattleAI.cpp 20 KB

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