1
0
mirror of https://github.com/vcmi/vcmi.git synced 2026-05-22 09:55:17 +02:00

Battle AI: fix firewall, fix haste spellcast evaluation for waits and movements, allow location spells

This commit is contained in:
Andrii Danylchenko
2024-08-25 15:49:11 +03:00
parent a819c3fc29
commit d55996cc46
5 changed files with 54 additions and 31 deletions
+33 -25
View File
@@ -66,7 +66,6 @@ BattleEvaluator::BattleEvaluator(
damageCache.buildDamageCache(hb, side);
targets = std::make_unique<PotentialTargets>(activeStack, damageCache, hb);
cachedScore = EvaluationResult::INEFFECTIVE_SCORE;
}
BattleEvaluator::BattleEvaluator(
@@ -85,7 +84,6 @@ BattleEvaluator::BattleEvaluator(
damageCache(damageCache), strengthRatio(strengthRatio), battleID(battleID), simulationTurnsCount(simulationTurnsCount)
{
targets = std::make_unique<PotentialTargets>(activeStack, damageCache, hb);
cachedScore = EvaluationResult::INEFFECTIVE_SCORE;
}
std::vector<BattleHex> BattleEvaluator::getBrokenWallMoatHexes() const
@@ -178,8 +176,10 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
auto evaluationResult = scoreEvaluator.findBestTarget(stack, *targets, damageCache, hb);
auto & bestAttack = evaluationResult.bestAttack;
cachedAttack = bestAttack;
cachedScore = evaluationResult.score;
cachedAttack.ap = bestAttack;
cachedAttack.score = evaluationResult.score;
cachedAttack.turn = 0;
cachedAttack.waited = evaluationResult.wait;
//TODO: consider more complex spellcast evaluation, f.e. because "re-retaliation" during enemy move in same turn for melee attack etc.
if(bestSpellcast.has_value() && bestSpellcast->value > bestAttack.damageDiff())
@@ -239,8 +239,9 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
if(moveTarget.score > score)
{
score = moveTarget.score;
cachedAttack = moveTarget.cachedAttack;
cachedScore = score;
cachedAttack.ap = moveTarget.cachedAttack;
cachedAttack.score = score;
cachedAttack.turn = moveTarget.turnsToRich;
if(stack->waited())
{
@@ -255,6 +256,8 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
}
else
{
cachedAttack.waited = true;
return BattleAction::makeWait(stack);
}
}
@@ -448,7 +451,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
vstd::erase_if(possibleSpells, [](const CSpell *s)
{
return spellType(s) != SpellTypes::BATTLE || s->getTargetType() == spells::AimType::LOCATION;
return spellType(s) != SpellTypes::BATTLE;
});
LOGFL("I know how %d of them works.", possibleSpells.size());
@@ -459,9 +462,6 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
{
spells::BattleCast temp(cb->getBattle(battleID).get(), hero, spells::Mode::HERO, spell);
if(spell->getTargetType() == spells::AimType::LOCATION)
continue;
const bool FAST = true;
for(auto & target : temp.findPotentialTargets(FAST))
@@ -648,9 +648,15 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
DamageCache safeCopy = damageCache;
DamageCache innerCache(&safeCopy);
innerCache.buildDamageCache(state, side);
if(needFullEval || !cachedAttack)
if(cachedAttack.ap && cachedAttack.waited)
{
state->makeWait(activeStack);
}
if(needFullEval || !cachedAttack.ap)
{
#if BATTLE_TRACE_LEVEL >= 1
logAi->trace("Full evaluation is started due to stack speed affected.");
@@ -659,29 +665,31 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
PotentialTargets innerTargets(activeStack, innerCache, state);
BattleExchangeEvaluator innerEvaluator(state, env, strengthRatio, simulationTurnsCount);
innerEvaluator.updateReachabilityMap(state);
auto moveTarget = scoreEvaluator.findMoveTowardsUnreachable(activeStack, innerTargets, innerCache, state);
if(!innerTargets.possibleAttacks.empty())
{
innerEvaluator.updateReachabilityMap(state);
auto newStackAction = innerEvaluator.findBestTarget(activeStack, innerTargets, innerCache, state);
ps.value = newStackAction.score;
ps.value = std::max(moveTarget.score, newStackAction.score);
}
else
{
ps.value = 0;
ps.value = moveTarget.score;
}
}
else
{
ps.value = scoreEvaluator.evaluateExchange(*cachedAttack, 0, *targets, innerCache, state);
ps.value = scoreEvaluator.evaluateExchange(*cachedAttack.ap, cachedAttack.turn, *targets, innerCache, state);
}
for(const auto & unit : allUnits)
{
if (!unit->isValidTarget())
if(!unit->isValidTarget())
continue;
auto newHealth = unit->getAvailableHealth();
auto oldHealth = vstd::find_or(healthOfStack, unit->unitId(), 0); // old health value may not exist for newly summoned units
@@ -692,7 +700,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
auto dpsReduce = AttackPossibility::calculateDamageReduce(
nullptr,
originalDefender && originalDefender->alive() ? originalDefender : unit,
originalDefender && originalDefender->alive() ? originalDefender : unit,
damage,
innerCache,
state);
@@ -719,6 +727,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
#endif
}
}
#if BATTLE_TRACE_LEVEL >= 1
logAi->trace("Total score: %2f", ps.value);
#endif
@@ -729,13 +738,12 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
LOGFL("Evaluation took %d ms", timer.getDiff());
auto pscValue = [](const PossibleSpellcast &ps) -> float
{
return ps.value;
};
auto castToPerform = *vstd::maxElementByFun(possibleCasts, pscValue);
auto castToPerform = *vstd::maxElementByFun(possibleCasts, [](const PossibleSpellcast & ps) -> float
{
return ps.value;
});
if(castToPerform.value > cachedScore)
if(castToPerform.value > cachedAttack.score && !vstd::isAlmostEqual(castToPerform.value, cachedAttack.score))
{
LOGFL("Best spell is %s (value %d). Will cast.", castToPerform.spell->getNameTranslated() % castToPerform.value);
BattleAction spellcast;