BattleAI.cpp 18 KB

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