BattleAI.cpp 27 KB

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