1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-02-03 13:01:33 +02:00

BattleAI: take into account defender dragon breath and other mutitarget attacks

This commit is contained in:
Andrii Danylchenko 2024-07-22 20:39:23 +03:00
parent 4e83deca92
commit da46d5d01b
2 changed files with 87 additions and 40 deletions

View File

@ -93,6 +93,8 @@ int64_t DamageCache::getOriginalDamage(const battle::Unit * attacker, const batt
AttackPossibility::AttackPossibility(BattleHex from, BattleHex dest, const BattleAttackInfo & attack) AttackPossibility::AttackPossibility(BattleHex from, BattleHex dest, const BattleAttackInfo & attack)
: from(from), dest(dest), attack(attack) : from(from), dest(dest), attack(attack)
{ {
this->attack.attackerPos = from;
this->attack.defenderPos = dest;
} }
float AttackPossibility::damageDiff() const float AttackPossibility::damageDiff() const
@ -261,37 +263,59 @@ AttackPossibility AttackPossibility::evaluate(
if (!attackInfo.shooting) if (!attackInfo.shooting)
ap.attackerState->setPosition(hex); ap.attackerState->setPosition(hex);
std::vector<const battle::Unit*> units; std::vector<const battle::Unit *> defenderUnits;
std::vector<const battle::Unit *> retaliatedUnits = {attacker};
std::vector<const battle::Unit *> affectedUnits;
if (attackInfo.shooting) if (attackInfo.shooting)
units = state->getAttackedBattleUnits(attacker, defHex, true, BattleHex::INVALID); defenderUnits = state->getAttackedBattleUnits(attacker, defHex, true, BattleHex::INVALID);
else 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; retaliatedUnits.push_back(attacker);
break;
} }
} }
if(addDefender) // ensure the defender is also affected
units.push_back(defender); if(!vstd::contains_if(defenderUnits, [defender](const battle::Unit * u) -> bool { return u->unitId() == defender->unitId(); }))
for(auto u : units)
{ {
if(!ap.attackerState->alive()) defenderUnits.push_back(defender);
break; }
affectedUnits = defenderUnits;
vstd::concatenate(affectedUnits, retaliatedUnits);
logAi->trace("Attacked battle units count %d, %d->%d", affectedUnits.size(), hex.hex, defHex.hex);
std::map<uint32_t, std::shared_ptr<battle::CUnitState>> defenderStates;
for(auto u : affectedUnits)
{
if(u->unitId() == attacker->unitId())
continue;
auto defenderState = u->acquireState(); 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 damageDealt;
int64_t damageReceived; int64_t damageReceived;
float defenderDamageReduce; float defenderDamageReduce;
@ -307,17 +331,36 @@ AttackPossibility AttackPossibility::evaluate(
vstd::amin(retaliation.damage.max, ap.attackerState->getAvailableHealth()); vstd::amin(retaliation.damage.max, ap.attackerState->getAvailableHealth());
damageDealt = averageDmg(attackDmg.damage); damageDealt = averageDmg(attackDmg.damage);
defenderDamageReduce = calculateDamageReduce(attacker, defender, damageDealt, damageCache, state); defenderDamageReduce = calculateDamageReduce(attacker, u, damageDealt, damageCache, state);
ap.attackerState->afterAttack(attackInfo.shooting, false); ap.attackerState->afterAttack(attackInfo.shooting, false);
//FIXME: use ranged retaliation //FIXME: use ranged retaliation
damageReceived = 0; damageReceived = 0;
attackerDamageReduce = 0; attackerDamageReduce = 0;
if (!attackInfo.shooting && defenderState->ableToRetaliate() && !counterAttacksBlocked) if (!attackInfo.shooting && u->unitId() == defender->unitId() && defenderState->ableToRetaliate() && !counterAttacksBlocked)
{ {
damageReceived = averageDmg(retaliation.damage); for(auto retaliated : retaliatedUnits)
attackerDamageReduce = calculateDamageReduce(defender, attacker, damageReceived, damageCache, state); {
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); defenderState->afterAttack(attackInfo.shooting, true);
} }
@ -331,21 +374,25 @@ AttackPossibility AttackPossibility::evaluate(
if(attackerSide == u->unitSide()) if(attackerSide == u->unitSide())
ap.collateralDamageReduce += defenderDamageReduce; ap.collateralDamageReduce += defenderDamageReduce;
if(u->unitId() == defender->unitId() || if(u->unitId() == defender->unitId()
(!attackInfo.shooting && CStack::isMeleeAttackPossible(u, attacker, hex))) || (!attackInfo.shooting && CStack::isMeleeAttackPossible(u, attacker, hex)))
{ {
//FIXME: handle RANGED_RETALIATION ? //FIXME: handle RANGED_RETALIATION ?
ap.attackerDamageReduce += attackerDamageReduce; ap.attackerDamageReduce += attackerDamageReduce;
} }
ap.attackerState->damage(damageReceived);
defenderState->damage(damageDealt); 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()) if(!bestAp.dest.isValid() || ap.attackValue() > bestAp.attackValue())
bestAp = ap; bestAp = ap;
} }

View File

@ -161,13 +161,13 @@ void CAnimation::verticalFlip()
void CAnimation::horizontalFlip(size_t frame, size_t group) 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; auto i2 = i1->second.find(frame);
}
catch (const std::out_of_range &) if(i2 != i1->second.end())
{ i2->second = nullptr;
// ignore - image not loaded
} }
auto locator = getImageLocator(frame, group); 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) 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; auto i2 = i1->second.find(frame);
}
catch (const std::out_of_range &) if(i2 != i1->second.end())
{ i2->second = nullptr;
// ignore - image not loaded
} }
auto locator = getImageLocator(frame, group); auto locator = getImageLocator(frame, group);