diff --git a/AI/BattleAI/AttackPossibility.cpp b/AI/BattleAI/AttackPossibility.cpp index d2b8a79b2..0430c9b8e 100644 --- a/AI/BattleAI/AttackPossibility.cpp +++ b/AI/BattleAI/AttackPossibility.cpp @@ -1,188 +1,190 @@ -/* - * AttackPossibility.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" -#include "AttackPossibility.h" -#include "../../lib/CStack.h"//todo: remove - -AttackPossibility::AttackPossibility(BattleHex from, BattleHex dest, const BattleAttackInfo & attack) - : from(from), dest(dest), attack(attack) -{ -} - -int64_t AttackPossibility::damageDiff() const -{ - return damageDealt - damageReceived - collateralDamage + shootersBlockedDmg; -} - -int64_t AttackPossibility::attackValue() const -{ - return damageDiff(); -} - -int64_t AttackPossibility::evaluateBlockedShootersDmg(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle * state) -{ - int64_t res = 0; - - if(attackInfo.shooting) - return 0; - - auto attacker = attackInfo.attacker; - auto hexes = attacker->getSurroundingHexes(hex); - for(BattleHex tile : hexes) - { - auto st = state->battleGetUnitByPos(tile, true); - if(!st || !state->battleMatchOwner(st, attacker)) - continue; - if(!state->battleCanShoot(st)) - continue; - - BattleAttackInfo rangeAttackInfo(st, attacker, true); - rangeAttackInfo.defenderPos = hex; - - BattleAttackInfo meleeAttackInfo(st, attacker, false); - meleeAttackInfo.defenderPos = hex; - - auto rangeDmg = getCbc()->battleEstimateDamage(rangeAttackInfo); - auto meleeDmg = getCbc()->battleEstimateDamage(meleeAttackInfo); - - int64_t gain = (rangeDmg.first + rangeDmg.second - meleeDmg.first - meleeDmg.second) / 2 + 1; - res += gain; - } - - return res; -} - -AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle * state) -{ - auto attacker = attackInfo.attacker; - auto defender = attackInfo.defender; - const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION"; - static const auto selectorBlocksRetaliation = Selector::type(Bonus::BLOCKS_RETALIATION); - const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation); +/* + * AttackPossibility.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "AttackPossibility.h" +#include "../../lib/CStack.h"//todo: remove + +AttackPossibility::AttackPossibility(BattleHex from, BattleHex dest, const BattleAttackInfo & attack) + : from(from), dest(dest), attack(attack) +{ +} + +int64_t AttackPossibility::damageDiff() const +{ + return damageDealt - damageReceived - collateralDamage + shootersBlockedDmg; +} + +int64_t AttackPossibility::attackValue() const +{ + return damageDiff(); +} + +int64_t AttackPossibility::evaluateBlockedShootersDmg(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle * state) +{ + int64_t res = 0; + + if(attackInfo.shooting) + return 0; + + auto attacker = attackInfo.attacker; + auto hexes = attacker->getSurroundingHexes(hex); + for(BattleHex tile : hexes) + { + auto st = state->battleGetUnitByPos(tile, true); + if(!st || !state->battleMatchOwner(st, attacker)) + continue; + if(!state->battleCanShoot(st)) + continue; + + BattleAttackInfo rangeAttackInfo(st, attacker, true); + rangeAttackInfo.defenderPos = hex; + + BattleAttackInfo meleeAttackInfo(st, attacker, false); + meleeAttackInfo.defenderPos = hex; + + auto rangeDmg = getCbc()->battleEstimateDamage(rangeAttackInfo); + auto meleeDmg = getCbc()->battleEstimateDamage(meleeAttackInfo); + + int64_t gain = (rangeDmg.first + rangeDmg.second - meleeDmg.first - meleeDmg.second) / 2 + 1; + res += gain; + } + + return res; +} + +AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle * state) +{ + auto attacker = attackInfo.attacker; + auto defender = attackInfo.defender; + const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION"; + static const auto selectorBlocksRetaliation = Selector::type(Bonus::BLOCKS_RETALIATION); + const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation); const bool mindControlled = [&](const battle::Unit *attacker) -> bool { - auto actualSide = getCbc()->playerToSide(getCbc()->battleGetOwner(attacker)); - if (actualSide && actualSide.get() != attacker->unitSide()) + auto actualSide = getCbc()->playerToSide(getCbc()->battleGetOwner(attacker)); + if (actualSide && actualSide.get() != attacker->unitSide()) return true; return false; } (attacker); - - AttackPossibility bestAp(hex, BattleHex::INVALID, attackInfo); - - std::vector defenderHex; - if(attackInfo.shooting) { - defenderHex = defender->getHexes(); - } else { - defenderHex = CStack::meleeAttackHexes(attacker, defender, hex); - } - - for(BattleHex defHex : defenderHex) { - if(defHex == hex) { - // should be impossible but check anyway - continue; - } - - AttackPossibility ap(hex, defHex, attackInfo); - ap.attackerState = attacker->acquireState(); - ap.shootersBlockedDmg = bestAp.shootersBlockedDmg; - - const int totalAttacks = ap.attackerState->getTotalAttacks(attackInfo.shooting); - - if (!attackInfo.shooting) - ap.attackerState->setPosition(hex); - - std::vector units; - - if (attackInfo.shooting) - units = state->getAttackedBattleUnits(attacker, defHex, true, BattleHex::INVALID); - else - units = state->getAttackedBattleUnits(attacker, defHex, false, hex); - - // ensure the defender is also affected - bool addDefender = true; - for(auto unit : units) { - if (unit->unitId() == defender->unitId()) { - addDefender = false; - break; - } - } - if(addDefender) { - units.push_back(defender); - } - - for(auto u : units) { - if(!ap.attackerState->alive()) { - break; - } - - assert(u->unitId() != attacker->unitId()); - - auto defenderState = u->acquireState(); - ap.affectedUnits.push_back(defenderState); - - for(int i = 0; i < totalAttacks; i++) { - si64 damageDealt, damageReceived; - - TDmgRange retaliation(0, 0); - auto attackDmg = getCbc()->battleEstimateDamage(ap.attack, &retaliation); - - vstd::amin(attackDmg.first, defenderState->getAvailableHealth()); - vstd::amin(attackDmg.second, defenderState->getAvailableHealth()); - - vstd::amin(retaliation.first, ap.attackerState->getAvailableHealth()); - vstd::amin(retaliation.second, ap.attackerState->getAvailableHealth()); - - damageDealt = (attackDmg.first + attackDmg.second) / 2; - ap.attackerState->afterAttack(attackInfo.shooting, false); - - //FIXME: use ranged retaliation - damageReceived = 0; - if (!attackInfo.shooting && defenderState->ableToRetaliate() && !counterAttacksBlocked) - { - damageReceived = (retaliation.first + retaliation.second) / 2; - defenderState->afterAttack(attackInfo.shooting, true); - } - - bool isEnemy = state->battleMatchOwner(attacker, u) && !mindControlled; - if(isEnemy) - ap.damageDealt += damageDealt; - else // friendly fire - ap.collateralDamage += damageDealt; - - if(u->unitId() == defender->unitId() || - (!attackInfo.shooting && CStack::isMeleeAttackPossible(u, attacker, hex))) { //FIXME: handle RANGED_RETALIATION ? - ap.damageReceived += damageReceived; - } - - ap.attackerState->damage(damageReceived); - defenderState->damage(damageDealt); - - if (!ap.attackerState->alive() || !defenderState->alive()) - break; - } - } - - if(!bestAp.dest.isValid() || ap.attackValue() > bestAp.attackValue()) { - bestAp = ap; - } - } - - // check how much damage we gain from blocking enemy shooters on this hex - bestAp.shootersBlockedDmg = evaluateBlockedShootersDmg(attackInfo, hex, state); - - logAi->debug("BattleAI best AP: %s -> %s at %d from %d, affects %d units: %d %d %d %s", - VLC->creh->creatures.at(attackInfo.attacker->acquireState()->creatureId())->identifier.c_str(), - VLC->creh->creatures.at(attackInfo.defender->acquireState()->creatureId())->identifier.c_str(), - (int)bestAp.dest, (int)bestAp.from, (int)bestAp.affectedUnits.size(), - (int)bestAp.damageDealt, (int)bestAp.damageReceived, (int)bestAp.collateralDamage, (int)bestAp.shootersBlockedDmg); - - //TODO other damage related to attack (eg. fire shield and other abilities) - return bestAp; -} + + AttackPossibility bestAp(hex, BattleHex::INVALID, attackInfo); + + std::vector defenderHex; + if(attackInfo.shooting) + defenderHex = defender->getHexes(); + else + defenderHex = CStack::meleeAttackHexes(attacker, defender, hex); + + for(BattleHex defHex : defenderHex) + { + if(defHex == hex) { + // should be impossible but check anyway + continue; + } + + AttackPossibility ap(hex, defHex, attackInfo); + ap.attackerState = attacker->acquireState(); + ap.shootersBlockedDmg = bestAp.shootersBlockedDmg; + + const int totalAttacks = ap.attackerState->getTotalAttacks(attackInfo.shooting); + + if (!attackInfo.shooting) + ap.attackerState->setPosition(hex); + + std::vector units; + + if (attackInfo.shooting) + units = state->getAttackedBattleUnits(attacker, defHex, true, BattleHex::INVALID); + else + units = state->getAttackedBattleUnits(attacker, defHex, false, hex); + + // ensure the defender is also affected + bool addDefender = true; + for(auto unit : units) { + if (unit->unitId() == defender->unitId()) { + addDefender = false; + break; + } + } + if(addDefender) { + units.push_back(defender); + } + + for(auto u : units) { + if(!ap.attackerState->alive()) { + break; + } + + assert(u->unitId() != attacker->unitId()); + + auto defenderState = u->acquireState(); + ap.affectedUnits.push_back(defenderState); + + for(int i = 0; i < totalAttacks; i++) { + si64 damageDealt, damageReceived; + + TDmgRange retaliation(0, 0); + auto attackDmg = getCbc()->battleEstimateDamage(ap.attack, &retaliation); + + vstd::amin(attackDmg.first, defenderState->getAvailableHealth()); + vstd::amin(attackDmg.second, defenderState->getAvailableHealth()); + + vstd::amin(retaliation.first, ap.attackerState->getAvailableHealth()); + vstd::amin(retaliation.second, ap.attackerState->getAvailableHealth()); + + damageDealt = (attackDmg.first + attackDmg.second) / 2; + ap.attackerState->afterAttack(attackInfo.shooting, false); + + //FIXME: use ranged retaliation + damageReceived = 0; + if (!attackInfo.shooting && defenderState->ableToRetaliate() && !counterAttacksBlocked) + { + damageReceived = (retaliation.first + retaliation.second) / 2; + defenderState->afterAttack(attackInfo.shooting, true); + } + + bool isEnemy = state->battleMatchOwner(attacker, u) && !mindControlled; + if(isEnemy) + ap.damageDealt += damageDealt; + else // friendly fire + ap.collateralDamage += damageDealt; + + if(u->unitId() == defender->unitId() || + (!attackInfo.shooting && CStack::isMeleeAttackPossible(u, attacker, hex))) + { + //FIXME: handle RANGED_RETALIATION ? + ap.damageReceived += damageReceived; + } + + ap.attackerState->damage(damageReceived); + defenderState->damage(damageDealt); + + if (!ap.attackerState->alive() || !defenderState->alive()) + break; + } + } + + if(!bestAp.dest.isValid() || ap.attackValue() > bestAp.attackValue()) { + bestAp = ap; + } + } + + // check how much damage we gain from blocking enemy shooters on this hex + bestAp.shootersBlockedDmg = evaluateBlockedShootersDmg(attackInfo, hex, state); + + logAi->debug("BattleAI best AP: %s -> %s at %d from %d, affects %d units: %d %d %d %s", + VLC->creh->creatures.at(attackInfo.attacker->acquireState()->creatureId())->identifier.c_str(), + VLC->creh->creatures.at(attackInfo.defender->acquireState()->creatureId())->identifier.c_str(), + (int)bestAp.dest, (int)bestAp.from, (int)bestAp.affectedUnits.size(), + (int)bestAp.damageDealt, (int)bestAp.damageReceived, (int)bestAp.collateralDamage, (int)bestAp.shootersBlockedDmg); + + //TODO other damage related to attack (eg. fire shield and other abilities) + return bestAp; +} diff --git a/AI/BattleAI/BattleAI.cpp b/AI/BattleAI/BattleAI.cpp index 6a043c242..0fcda607d 100644 --- a/AI/BattleAI/BattleAI.cpp +++ b/AI/BattleAI/BattleAI.cpp @@ -164,11 +164,12 @@ BattleAction CBattleAI::activeStack( const CStack * stack ) return BattleAction::makeCreatureSpellcast(stack, bestSpellcast->dest, bestSpellcast->spell->id); else if(bestAttack.attack.shooting) return BattleAction::makeShotAttack(stack, bestAttack.attack.defender); - else { + else + { auto &target = bestAttack; logAi->debug("BattleAI: %s -> %s %d from, %d curpos %d dist %d speed %d: %d %d %d", - VLC->creh->creatures.at(target.attackerState->creatureId())->identifier.c_str(), - VLC->creh->creatures.at(target.affectedUnits[0]->creatureId())->identifier.c_str(), + VLC->creh->creatures.at(target.attackerState->creatureId())->identifier, + VLC->creh->creatures.at(target.affectedUnits[0]->creatureId())->identifier, (int)target.affectedUnits.size(), (int)target.from, (int)bestAttack.attack.attacker->getPosition().hex, (int)bestAttack.attack.chargedFields, (int)bestAttack.attack.attacker->Speed(0, true), (int)target.damageDealt, (int)target.damageReceived, (int)target.attackValue() diff --git a/AI/BattleAI/PotentialTargets.cpp b/AI/BattleAI/PotentialTargets.cpp index c99fb8b30..82d9138e4 100644 --- a/AI/BattleAI/PotentialTargets.cpp +++ b/AI/BattleAI/PotentialTargets.cpp @@ -69,7 +69,8 @@ PotentialTargets::PotentialTargets(const battle::Unit * attacker, const Hypothet } else { - for(BattleHex hex : avHexes) { + for(BattleHex hex : avHexes) + { if(!CStack::isMeleeAttackPossible(attackerInfo, defender, hex)) continue;