AttackPossibility.cpp 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  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 attackerUnitForMeasurement = attacker;
  47. if(attackerUnitForMeasurement->isTurret())
  48. {
  49. auto ourUnits = cb.battleGetUnitsIf([&](const battle::Unit * u) -> bool
  50. {
  51. return u->unitSide() == attacker->unitSide() && !u->isTurret();
  52. });
  53. if(ourUnits.empty())
  54. attackerUnitForMeasurement = defender;
  55. else
  56. attackerUnitForMeasurement = ourUnits.front();
  57. }
  58. auto enemyDamageBeforeAttack = cb.battleEstimateDamage(defender, attackerUnitForMeasurement, 0);
  59. auto enemiesKilled = damageDealt / defender->getMaxHealth() + (damageDealt % defender->getMaxHealth() >= defender->getFirstHPleft() ? 1 : 0);
  60. auto enemyDamage = averageDmg(enemyDamageBeforeAttack.damage);
  61. auto damagePerEnemy = enemyDamage / (double)defender->getCount();
  62. return (int64_t)(damagePerEnemy * (enemiesKilled * KILL_BOUNTY + damageDealt * HEALTH_BOUNTY / (double)defender->getMaxHealth()));
  63. }
  64. int64_t AttackPossibility::evaluateBlockedShootersDmg(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle & state)
  65. {
  66. int64_t res = 0;
  67. if(attackInfo.shooting)
  68. return 0;
  69. auto attacker = attackInfo.attacker;
  70. auto hexes = attacker->getSurroundingHexes(hex);
  71. for(BattleHex tile : hexes)
  72. {
  73. auto st = state.battleGetUnitByPos(tile, true);
  74. if(!st || !state.battleMatchOwner(st, attacker))
  75. continue;
  76. if(!state.battleCanShoot(st))
  77. continue;
  78. // FIXME: provide distance info for Jousting bonus
  79. BattleAttackInfo rangeAttackInfo(st, attacker, 0, true);
  80. rangeAttackInfo.defenderPos = hex;
  81. BattleAttackInfo meleeAttackInfo(st, attacker, 0, false);
  82. meleeAttackInfo.defenderPos = hex;
  83. auto rangeDmg = state.battleEstimateDamage(rangeAttackInfo);
  84. auto meleeDmg = state.battleEstimateDamage(meleeAttackInfo);
  85. int64_t gain = averageDmg(rangeDmg.damage) - averageDmg(meleeDmg.damage) + 1;
  86. res += gain;
  87. }
  88. return res;
  89. }
  90. AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle & state)
  91. {
  92. auto attacker = attackInfo.attacker;
  93. auto defender = attackInfo.defender;
  94. const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
  95. static const auto selectorBlocksRetaliation = Selector::type()(BonusType::BLOCKS_RETALIATION);
  96. const auto attackerSide = state.playerToSide(state.battleGetOwner(attacker));
  97. const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
  98. AttackPossibility bestAp(hex, BattleHex::INVALID, attackInfo);
  99. std::vector<BattleHex> defenderHex;
  100. if(attackInfo.shooting)
  101. defenderHex = defender->getHexes();
  102. else
  103. defenderHex = CStack::meleeAttackHexes(attacker, defender, hex);
  104. for(BattleHex defHex : defenderHex)
  105. {
  106. if(defHex == hex) // should be impossible but check anyway
  107. continue;
  108. AttackPossibility ap(hex, defHex, attackInfo);
  109. ap.attackerState = attacker->acquireState();
  110. ap.shootersBlockedDmg = bestAp.shootersBlockedDmg;
  111. const int totalAttacks = ap.attackerState->getTotalAttacks(attackInfo.shooting);
  112. if (!attackInfo.shooting)
  113. ap.attackerState->setPosition(hex);
  114. std::vector<const battle::Unit*> units;
  115. if (attackInfo.shooting)
  116. units = state.getAttackedBattleUnits(attacker, defHex, true, BattleHex::INVALID);
  117. else
  118. units = state.getAttackedBattleUnits(attacker, defHex, false, hex);
  119. // ensure the defender is also affected
  120. bool addDefender = true;
  121. for(auto unit : units)
  122. {
  123. if (unit->unitId() == defender->unitId())
  124. {
  125. addDefender = false;
  126. break;
  127. }
  128. }
  129. if(addDefender)
  130. units.push_back(defender);
  131. for(auto u : units)
  132. {
  133. if(!ap.attackerState->alive())
  134. break;
  135. auto defenderState = u->acquireState();
  136. ap.affectedUnits.push_back(defenderState);
  137. for(int i = 0; i < totalAttacks; i++)
  138. {
  139. int64_t damageDealt, damageReceived, defenderDamageReduce, attackerDamageReduce;
  140. DamageEstimation retaliation;
  141. auto attackDmg = state.battleEstimateDamage(ap.attack, &retaliation);
  142. vstd::amin(attackDmg.damage.min, defenderState->getAvailableHealth());
  143. vstd::amin(attackDmg.damage.max, defenderState->getAvailableHealth());
  144. vstd::amin(retaliation.damage.min, ap.attackerState->getAvailableHealth());
  145. vstd::amin(retaliation.damage.max, ap.attackerState->getAvailableHealth());
  146. damageDealt = averageDmg(attackDmg.damage);
  147. defenderDamageReduce = calculateDamageReduce(attacker, defender, damageDealt, state);
  148. ap.attackerState->afterAttack(attackInfo.shooting, false);
  149. //FIXME: use ranged retaliation
  150. damageReceived = 0;
  151. attackerDamageReduce = 0;
  152. if (!attackInfo.shooting && defenderState->ableToRetaliate() && !counterAttacksBlocked)
  153. {
  154. damageReceived = averageDmg(retaliation.damage);
  155. attackerDamageReduce = calculateDamageReduce(defender, attacker, damageReceived, state);
  156. defenderState->afterAttack(attackInfo.shooting, true);
  157. }
  158. bool isEnemy = state.battleMatchOwner(attacker, u);
  159. // this includes enemy units as well as attacker units under enemy's mind control
  160. if(isEnemy)
  161. ap.defenderDamageReduce += defenderDamageReduce;
  162. // damaging attacker's units (even those under enemy's mind control) is considered friendly fire
  163. if(attackerSide == u->unitSide())
  164. ap.collateralDamageReduce += defenderDamageReduce;
  165. if(u->unitId() == defender->unitId() ||
  166. (!attackInfo.shooting && CStack::isMeleeAttackPossible(u, attacker, hex)))
  167. {
  168. //FIXME: handle RANGED_RETALIATION ?
  169. ap.attackerDamageReduce += attackerDamageReduce;
  170. }
  171. ap.attackerState->damage(damageReceived);
  172. defenderState->damage(damageDealt);
  173. if (!ap.attackerState->alive() || !defenderState->alive())
  174. break;
  175. }
  176. }
  177. if(!bestAp.dest.isValid() || ap.attackValue() > bestAp.attackValue())
  178. bestAp = ap;
  179. }
  180. // check how much damage we gain from blocking enemy shooters on this hex
  181. bestAp.shootersBlockedDmg = evaluateBlockedShootersDmg(attackInfo, hex, state);
  182. #if BATTLE_TRACE_LEVEL>=1
  183. logAi->trace("BattleAI best AP: %s -> %s at %d from %d, affects %d units: d:%lld a:%lld c:%lld s:%lld",
  184. attackInfo.attacker->unitType()->getJsonKey(),
  185. attackInfo.defender->unitType()->getJsonKey(),
  186. (int)bestAp.dest, (int)bestAp.from, (int)bestAp.affectedUnits.size(),
  187. bestAp.defenderDamageReduce, bestAp.attackerDamageReduce, bestAp.collateralDamageReduce, bestAp.shootersBlockedDmg);
  188. #endif
  189. //TODO other damage related to attack (eg. fire shield and other abilities)
  190. return bestAp;
  191. }