AttackPossibility.cpp 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. /*
  2. * AttackPossibility.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 "AttackPossibility.h"
  12. #include "../../lib/CStack.h" // TODO: remove
  13. // Eventually only IBattleInfoCallback and battle::Unit should be used,
  14. // CUnitState should be private and CStack should be removed completely
  15. uint64_t averageDmg(const DamageRange & range)
  16. {
  17. return (range.min + range.max) / 2;
  18. }
  19. AttackPossibility::AttackPossibility(BattleHex from, BattleHex dest, const BattleAttackInfo & attack)
  20. : from(from), dest(dest), attack(attack)
  21. {
  22. }
  23. int64_t AttackPossibility::damageDiff() const
  24. {
  25. return defenderDamageReduce - attackerDamageReduce - collateralDamageReduce + shootersBlockedDmg;
  26. }
  27. int64_t AttackPossibility::attackValue() const
  28. {
  29. return damageDiff();
  30. }
  31. /// <summary>
  32. /// How enemy damage will be reduced by this attack
  33. /// Half bounty for kill, half for making damage equal to enemy health
  34. /// Bounty - the killed creature average damage calculated against attacker
  35. /// </summary>
  36. int64_t AttackPossibility::calculateDamageReduce(
  37. const battle::Unit * attacker,
  38. const battle::Unit * defender,
  39. uint64_t damageDealt,
  40. const CBattleInfoCallback & cb)
  41. {
  42. const float HEALTH_BOUNTY = 0.5;
  43. const float KILL_BOUNTY = 1.0 - HEALTH_BOUNTY;
  44. vstd::amin(damageDealt, defender->getAvailableHealth());
  45. // FIXME: provide distance info for Jousting bonus
  46. auto enemyDamageBeforeAttack = cb.battleEstimateDamage(defender, attacker, 0);
  47. auto enemiesKilled = damageDealt / defender->MaxHealth() + (damageDealt % defender->MaxHealth() >= defender->getFirstHPleft() ? 1 : 0);
  48. auto enemyDamage = averageDmg(enemyDamageBeforeAttack.damage);
  49. auto damagePerEnemy = enemyDamage / (double)defender->getCount();
  50. return (int64_t)(damagePerEnemy * (enemiesKilled * KILL_BOUNTY + damageDealt * HEALTH_BOUNTY / (double)defender->MaxHealth()));
  51. }
  52. int64_t AttackPossibility::evaluateBlockedShootersDmg(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle & state)
  53. {
  54. int64_t res = 0;
  55. if(attackInfo.shooting)
  56. return 0;
  57. auto attacker = attackInfo.attacker;
  58. auto hexes = attacker->getSurroundingHexes(hex);
  59. for(BattleHex tile : hexes)
  60. {
  61. auto st = state.battleGetUnitByPos(tile, true);
  62. if(!st || !state.battleMatchOwner(st, attacker))
  63. continue;
  64. if(!state.battleCanShoot(st))
  65. continue;
  66. // FIXME: provide distance info for Jousting bonus
  67. BattleAttackInfo rangeAttackInfo(st, attacker, 0, true);
  68. rangeAttackInfo.defenderPos = hex;
  69. BattleAttackInfo meleeAttackInfo(st, attacker, 0, false);
  70. meleeAttackInfo.defenderPos = hex;
  71. auto rangeDmg = state.battleEstimateDamage(rangeAttackInfo);
  72. auto meleeDmg = state.battleEstimateDamage(meleeAttackInfo);
  73. int64_t gain = averageDmg(rangeDmg.damage) - averageDmg(meleeDmg.damage) + 1;
  74. res += gain;
  75. }
  76. return res;
  77. }
  78. AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle & state)
  79. {
  80. auto attacker = attackInfo.attacker;
  81. auto defender = attackInfo.defender;
  82. const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
  83. static const auto selectorBlocksRetaliation = Selector::type()(Bonus::BLOCKS_RETALIATION);
  84. const auto attackerSide = state.playerToSide(state.battleGetOwner(attacker));
  85. const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
  86. AttackPossibility bestAp(hex, BattleHex::INVALID, attackInfo);
  87. std::vector<BattleHex> defenderHex;
  88. if(attackInfo.shooting)
  89. defenderHex = defender->getHexes();
  90. else
  91. defenderHex = CStack::meleeAttackHexes(attacker, defender, hex);
  92. for(BattleHex defHex : defenderHex)
  93. {
  94. if(defHex == hex) // should be impossible but check anyway
  95. continue;
  96. AttackPossibility ap(hex, defHex, attackInfo);
  97. ap.attackerState = attacker->acquireState();
  98. ap.shootersBlockedDmg = bestAp.shootersBlockedDmg;
  99. const int totalAttacks = ap.attackerState->getTotalAttacks(attackInfo.shooting);
  100. if (!attackInfo.shooting)
  101. ap.attackerState->setPosition(hex);
  102. std::vector<const battle::Unit*> units;
  103. if (attackInfo.shooting)
  104. units = state.getAttackedBattleUnits(attacker, defHex, true, BattleHex::INVALID);
  105. else
  106. units = state.getAttackedBattleUnits(attacker, defHex, false, hex);
  107. // ensure the defender is also affected
  108. bool addDefender = true;
  109. for(auto unit : units)
  110. {
  111. if (unit->unitId() == defender->unitId())
  112. {
  113. addDefender = false;
  114. break;
  115. }
  116. }
  117. if(addDefender)
  118. units.push_back(defender);
  119. for(auto u : units)
  120. {
  121. if(!ap.attackerState->alive())
  122. break;
  123. auto defenderState = u->acquireState();
  124. ap.affectedUnits.push_back(defenderState);
  125. for(int i = 0; i < totalAttacks; i++)
  126. {
  127. int64_t damageDealt, damageReceived, defenderDamageReduce, attackerDamageReduce;
  128. DamageEstimation retaliation;
  129. auto attackDmg = state.battleEstimateDamage(ap.attack, &retaliation);
  130. vstd::amin(attackDmg.damage.min, defenderState->getAvailableHealth());
  131. vstd::amin(attackDmg.damage.max, defenderState->getAvailableHealth());
  132. vstd::amin(retaliation.damage.min, ap.attackerState->getAvailableHealth());
  133. vstd::amin(retaliation.damage.max, ap.attackerState->getAvailableHealth());
  134. damageDealt = averageDmg(attackDmg.damage);
  135. defenderDamageReduce = calculateDamageReduce(attacker, defender, damageDealt, state);
  136. ap.attackerState->afterAttack(attackInfo.shooting, false);
  137. //FIXME: use ranged retaliation
  138. damageReceived = 0;
  139. attackerDamageReduce = 0;
  140. if (!attackInfo.shooting && defenderState->ableToRetaliate() && !counterAttacksBlocked)
  141. {
  142. damageReceived = averageDmg(retaliation.damage);
  143. attackerDamageReduce = calculateDamageReduce(defender, attacker, damageReceived, state);
  144. defenderState->afterAttack(attackInfo.shooting, true);
  145. }
  146. bool isEnemy = state.battleMatchOwner(attacker, u);
  147. // this includes enemy units as well as attacker units under enemy's mind control
  148. if(isEnemy)
  149. ap.defenderDamageReduce += defenderDamageReduce;
  150. // damaging attacker's units (even those under enemy's mind control) is considered friendly fire
  151. if(attackerSide == u->unitSide())
  152. ap.collateralDamageReduce += defenderDamageReduce;
  153. if(u->unitId() == defender->unitId() ||
  154. (!attackInfo.shooting && CStack::isMeleeAttackPossible(u, attacker, hex)))
  155. {
  156. //FIXME: handle RANGED_RETALIATION ?
  157. ap.attackerDamageReduce += attackerDamageReduce;
  158. }
  159. ap.attackerState->damage(damageReceived);
  160. defenderState->damage(damageDealt);
  161. if (!ap.attackerState->alive() || !defenderState->alive())
  162. break;
  163. }
  164. }
  165. if(!bestAp.dest.isValid() || ap.attackValue() > bestAp.attackValue())
  166. bestAp = ap;
  167. }
  168. // check how much damage we gain from blocking enemy shooters on this hex
  169. bestAp.shootersBlockedDmg = evaluateBlockedShootersDmg(attackInfo, hex, state);
  170. #if BATTLE_TRACE_LEVEL>=1
  171. logAi->trace("BattleAI best AP: %s -> %s at %d from %d, affects %d units: d:%lld a:%lld c:%lld s:%lld",
  172. attackInfo.attacker->unitType()->getJsonKey(),
  173. attackInfo.defender->unitType()->getJsonKey(),
  174. (int)bestAp.dest, (int)bestAp.from, (int)bestAp.affectedUnits.size(),
  175. bestAp.defenderDamageReduce, bestAp.attackerDamageReduce, bestAp.collateralDamageReduce, bestAp.shootersBlockedDmg);
  176. #endif
  177. //TODO other damage related to attack (eg. fire shield and other abilities)
  178. return bestAp;
  179. }