From da46d5d01bf5a5a3b49a2ec036e830caadaee08c Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Mon, 22 Jul 2024 20:39:23 +0300 Subject: [PATCH] BattleAI: take into account defender dragon breath and other mutitarget attacks --- AI/BattleAI/AttackPossibility.cpp | 103 ++++++++++++++++++++++-------- client/render/CAnimation.cpp | 24 +++---- 2 files changed, 87 insertions(+), 40 deletions(-) diff --git a/AI/BattleAI/AttackPossibility.cpp b/AI/BattleAI/AttackPossibility.cpp index b2d4c769f..1d31c9725 100644 --- a/AI/BattleAI/AttackPossibility.cpp +++ b/AI/BattleAI/AttackPossibility.cpp @@ -93,6 +93,8 @@ int64_t DamageCache::getOriginalDamage(const battle::Unit * attacker, const batt AttackPossibility::AttackPossibility(BattleHex from, BattleHex dest, const BattleAttackInfo & attack) : from(from), dest(dest), attack(attack) { + this->attack.attackerPos = from; + this->attack.defenderPos = dest; } float AttackPossibility::damageDiff() const @@ -261,37 +263,59 @@ AttackPossibility AttackPossibility::evaluate( if (!attackInfo.shooting) ap.attackerState->setPosition(hex); - std::vector units; + std::vector defenderUnits; + std::vector retaliatedUnits = {attacker}; + std::vector affectedUnits; if (attackInfo.shooting) - units = state->getAttackedBattleUnits(attacker, defHex, true, BattleHex::INVALID); + defenderUnits = 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()) + defenderUnits = state->getAttackedBattleUnits(attacker, defHex, false, hex); + retaliatedUnits = state->getAttackedBattleUnits(defender, hex, false, defender->getPosition()); + + // attacker can not melle-attack itself but still can hit that place where it was before moving + vstd::erase_if(defenderUnits, [attacker](const battle::Unit * u) -> bool { return u->unitId() == attacker->unitId(); }); + + if(!vstd::contains_if(retaliatedUnits, [attacker](const battle::Unit * u) -> bool { return u->unitId() == attacker->unitId(); })) { - addDefender = false; - break; + retaliatedUnits.push_back(attacker); } } - if(addDefender) - units.push_back(defender); - - for(auto u : units) + // ensure the defender is also affected + if(!vstd::contains_if(defenderUnits, [defender](const battle::Unit * u) -> bool { return u->unitId() == defender->unitId(); })) { - if(!ap.attackerState->alive()) - break; + defenderUnits.push_back(defender); + } + + affectedUnits = defenderUnits; + vstd::concatenate(affectedUnits, retaliatedUnits); + + logAi->trace("Attacked battle units count %d, %d->%d", affectedUnits.size(), hex.hex, defHex.hex); + + std::map> defenderStates; + + for(auto u : affectedUnits) + { + if(u->unitId() == attacker->unitId()) + continue; auto defenderState = u->acquireState(); - ap.affectedUnits.push_back(defenderState); - for(int i = 0; i < totalAttacks; i++) + ap.affectedUnits.push_back(defenderState); + defenderStates[u->unitId()] = defenderState; + } + + for(int i = 0; i < totalAttacks; i++) + { + if(!ap.attackerState->alive() || !defenderStates[defender->unitId()]->alive()) + break; + + for(auto u : defenderUnits) { + auto defenderState = defenderStates.at(u->unitId()); + int64_t damageDealt; int64_t damageReceived; float defenderDamageReduce; @@ -307,17 +331,36 @@ AttackPossibility AttackPossibility::evaluate( vstd::amin(retaliation.damage.max, ap.attackerState->getAvailableHealth()); damageDealt = averageDmg(attackDmg.damage); - defenderDamageReduce = calculateDamageReduce(attacker, defender, damageDealt, damageCache, state); + defenderDamageReduce = calculateDamageReduce(attacker, u, damageDealt, damageCache, state); ap.attackerState->afterAttack(attackInfo.shooting, false); //FIXME: use ranged retaliation damageReceived = 0; attackerDamageReduce = 0; - if (!attackInfo.shooting && defenderState->ableToRetaliate() && !counterAttacksBlocked) + if (!attackInfo.shooting && u->unitId() == defender->unitId() && defenderState->ableToRetaliate() && !counterAttacksBlocked) { - damageReceived = averageDmg(retaliation.damage); - attackerDamageReduce = calculateDamageReduce(defender, attacker, damageReceived, damageCache, state); + for(auto retaliated : retaliatedUnits) + { + damageReceived = averageDmg(retaliation.damage); + + if(retaliated->unitId() == attacker->unitId()) + { + attackerDamageReduce = calculateDamageReduce(defender, retaliated, damageReceived, damageCache, state); + ap.attackerState->damage(damageReceived); + } + else + { + if(state->battleMatchOwner(defender, u)) + defenderDamageReduce += calculateDamageReduce(defender, retaliated, damageReceived, damageCache, state); + else + ap.collateralDamageReduce += calculateDamageReduce(defender, retaliated, damageReceived, damageCache, state); + + defenderStates.at(retaliated->unitId())->damage(damageReceived); + } + + } + defenderState->afterAttack(attackInfo.shooting, true); } @@ -331,21 +374,25 @@ AttackPossibility AttackPossibility::evaluate( if(attackerSide == u->unitSide()) ap.collateralDamageReduce += defenderDamageReduce; - if(u->unitId() == defender->unitId() || - (!attackInfo.shooting && CStack::isMeleeAttackPossible(u, attacker, hex))) + if(u->unitId() == defender->unitId() + || (!attackInfo.shooting && CStack::isMeleeAttackPossible(u, attacker, hex))) { //FIXME: handle RANGED_RETALIATION ? ap.attackerDamageReduce += attackerDamageReduce; } - ap.attackerState->damage(damageReceived); defenderState->damage(damageDealt); - - if (!ap.attackerState->alive() || !defenderState->alive()) - break; } } +#if BATTLE_TRACE_LEVEL>=2 + logAi->trace("BattleAI AP: %s -> %s at %d from %d, affects %d units: d:%lld a:%lld c:%lld s:%lld", + attackInfo.attacker->unitType()->getJsonKey(), + attackInfo.defender->unitType()->getJsonKey(), + (int)ap.dest, (int)ap.from, (int)ap.affectedUnits.size(), + ap.defenderDamageReduce, ap.attackerDamageReduce, ap.collateralDamageReduce, ap.shootersBlockedDmg); +#endif + if(!bestAp.dest.isValid() || ap.attackValue() > bestAp.attackValue()) bestAp = ap; } diff --git a/client/render/CAnimation.cpp b/client/render/CAnimation.cpp index faa45e559..32ef57e60 100644 --- a/client/render/CAnimation.cpp +++ b/client/render/CAnimation.cpp @@ -161,13 +161,13 @@ void CAnimation::verticalFlip() void CAnimation::horizontalFlip(size_t frame, size_t group) { - try + auto i1 = images.find(group); + if(i1 != images.end()) { - images.at(group).at(frame) = nullptr; - } - catch (const std::out_of_range &) - { - // ignore - image not loaded + auto i2 = i1->second.find(frame); + + if(i2 != i1->second.end()) + i2->second = nullptr; } auto locator = getImageLocator(frame, group); @@ -177,13 +177,13 @@ void CAnimation::horizontalFlip(size_t frame, size_t group) void CAnimation::verticalFlip(size_t frame, size_t group) { - try + auto i1 = images.find(group); + if(i1 != images.end()) { - images.at(group).at(frame) = nullptr; - } - catch (const std::out_of_range &) - { - // ignore - image not loaded + auto i2 = i1->second.find(frame); + + if(i2 != i1->second.end()) + i2->second = nullptr; } auto locator = getImageLocator(frame, group);