mirror of
https://github.com/vcmi/vcmi.git
synced 2026-05-22 09:55:17 +02:00
Merge remote-tracking branch 'upstream/develop' into develop
This commit is contained in:
@@ -18,7 +18,7 @@ AttackerValue::AttackerValue()
|
||||
}
|
||||
|
||||
MoveTarget::MoveTarget()
|
||||
: positions(), cachedAttack(), score(EvaluationResult::INEFFECTIVE_SCORE), scorePerTurn(EvaluationResult::INEFFECTIVE_SCORE)
|
||||
: positions(), cachedAttack(), score(EvaluationResult::INEFFECTIVE_SCORE)
|
||||
{
|
||||
turnsToRich = 1;
|
||||
}
|
||||
@@ -42,7 +42,7 @@ float BattleExchangeVariant::trackAttack(
|
||||
for(auto affectedUnit : affectedUnits)
|
||||
{
|
||||
auto unitToUpdate = hb->getForUpdate(affectedUnit->unitId());
|
||||
auto damageDealt = unitToUpdate->getTotalHealth() - affectedUnit->getTotalHealth();
|
||||
auto damageDealt = unitToUpdate->getAvailableHealth() - affectedUnit->getAvailableHealth();
|
||||
|
||||
if(damageDealt > 0)
|
||||
{
|
||||
@@ -58,7 +58,7 @@ float BattleExchangeVariant::trackAttack(
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace(
|
||||
"%s -> %s, ap retaliation, %s, dps: %lld",
|
||||
ap.attack.defender->getDescription(),
|
||||
hb->getForUpdate(ap.attack.defender->unitId())->getDescription(),
|
||||
ap.attack.attacker->getDescription(),
|
||||
ap.attack.shooting ? "shot" : "mellee",
|
||||
damageDealt);
|
||||
@@ -277,6 +277,36 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
||||
return result;
|
||||
}
|
||||
|
||||
ReachabilityInfo getReachabilityWithEnemyBypass(
|
||||
const battle::Unit * activeStack,
|
||||
DamageCache & damageCache,
|
||||
std::shared_ptr<HypotheticBattle> state)
|
||||
{
|
||||
ReachabilityInfo::Parameters params(activeStack, activeStack->getPosition());
|
||||
|
||||
if(!params.flying)
|
||||
{
|
||||
for(const auto * unit : state->battleAliveUnits())
|
||||
{
|
||||
if(unit->unitSide() == activeStack->unitSide())
|
||||
continue;
|
||||
|
||||
auto dmg = damageCache.getOriginalDamage(activeStack, unit, state);
|
||||
auto turnsToKill = unit->getAvailableHealth() / dmg;
|
||||
|
||||
vstd::amin(turnsToKill, 100);
|
||||
|
||||
for(auto & hex : unit->getHexes())
|
||||
if(hex.isAvailable()) //towers can have <0 pos; we don't also want to overwrite side columns
|
||||
params.destructibleEnemyTurns[hex] = turnsToKill * unit->getMovementRange();
|
||||
}
|
||||
|
||||
params.bypassEnemyStacks = true;
|
||||
}
|
||||
|
||||
return state->getReachability(params);
|
||||
}
|
||||
|
||||
MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
||||
const battle::Unit * activeStack,
|
||||
PotentialTargets & targets,
|
||||
@@ -286,6 +316,8 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
||||
MoveTarget result;
|
||||
BattleExchangeVariant ev;
|
||||
|
||||
logAi->trace("Find move towards unreachable. Enemies count %d", targets.unreachableEnemies.size());
|
||||
|
||||
if(targets.unreachableEnemies.empty())
|
||||
return result;
|
||||
|
||||
@@ -296,17 +328,17 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
||||
|
||||
updateReachabilityMap(hb);
|
||||
|
||||
auto dists = cb->getReachability(activeStack);
|
||||
auto dists = getReachabilityWithEnemyBypass(activeStack, damageCache, hb);
|
||||
auto flying = activeStack->hasBonusOfType(BonusType::FLYING);
|
||||
|
||||
for(const battle::Unit * enemy : targets.unreachableEnemies)
|
||||
{
|
||||
std::vector<const battle::Unit *> adjacentStacks = getAdjacentUnits(enemy);
|
||||
auto closestStack = *vstd::minElementByFun(adjacentStacks, [&](const battle::Unit * u) -> int64_t
|
||||
{
|
||||
return dists.distToNearestNeighbour(activeStack, u) * 100000 - activeStack->getTotalHealth();
|
||||
});
|
||||
logAi->trace(
|
||||
"Checking movement towards %d of %s",
|
||||
enemy->getCount(),
|
||||
enemy->creatureId().toCreature()->getNameSingularTranslated());
|
||||
|
||||
auto distance = dists.distToNearestNeighbour(activeStack, closestStack);
|
||||
auto distance = dists.distToNearestNeighbour(activeStack, enemy);
|
||||
|
||||
if(distance >= GameConstants::BFIELD_SIZE)
|
||||
continue;
|
||||
@@ -315,30 +347,84 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
||||
continue;
|
||||
|
||||
auto turnsToRich = (distance - 1) / speed + 1;
|
||||
auto hexes = closestStack->getSurroundingHexes();
|
||||
auto enemySpeed = closestStack->getMovementRange();
|
||||
auto hexes = enemy->getSurroundingHexes();
|
||||
auto enemySpeed = enemy->getMovementRange();
|
||||
auto speedRatio = speed / static_cast<float>(enemySpeed);
|
||||
auto multiplier = speedRatio > 1 ? 1 : speedRatio;
|
||||
|
||||
if(enemy->canShoot())
|
||||
multiplier *= 1.5f;
|
||||
|
||||
for(auto hex : hexes)
|
||||
for(auto & hex : hexes)
|
||||
{
|
||||
// FIXME: provide distance info for Jousting bonus
|
||||
auto bai = BattleAttackInfo(activeStack, closestStack, 0, cb->battleCanShoot(activeStack));
|
||||
auto bai = BattleAttackInfo(activeStack, enemy, 0, cb->battleCanShoot(activeStack));
|
||||
auto attack = AttackPossibility::evaluate(bai, hex, damageCache, hb);
|
||||
|
||||
attack.shootersBlockedDmg = 0; // we do not want to count on it, it is not for sure
|
||||
|
||||
auto score = calculateExchange(attack, turnsToRich, targets, damageCache, hb);
|
||||
auto scorePerTurn = BattleScore(score.enemyDamageReduce * std::sqrt(multiplier / turnsToRich), score.ourDamageReduce);
|
||||
|
||||
if(result.scorePerTurn < scoreValue(scorePerTurn))
|
||||
score.enemyDamageReduce *= multiplier;
|
||||
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("Multiplier: %f, turns: %d, current score %f, new score %f", multiplier, turnsToRich, result.score, scoreValue(score));
|
||||
#endif
|
||||
|
||||
if(result.score < scoreValue(score)
|
||||
|| (result.turnsToRich > turnsToRich && vstd::isAlmostEqual(result.score, scoreValue(score))))
|
||||
{
|
||||
result.scorePerTurn = scoreValue(scorePerTurn);
|
||||
result.score = scoreValue(score);
|
||||
result.positions = closestStack->getAttackableHexes(activeStack);
|
||||
result.positions.clear();
|
||||
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("New high score");
|
||||
#endif
|
||||
|
||||
for(BattleHex enemyHex : enemy->getAttackableHexes(activeStack))
|
||||
{
|
||||
while(!flying && dists.distances[enemyHex] > speed)
|
||||
{
|
||||
enemyHex = dists.predecessors.at(enemyHex);
|
||||
if(dists.accessibility[enemyHex] == EAccessibility::ALIVE_STACK)
|
||||
{
|
||||
auto defenderToBypass = hb->battleGetUnitByPos(enemyHex);
|
||||
|
||||
if(defenderToBypass)
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("Found target to bypass at %d", enemyHex.hex);
|
||||
#endif
|
||||
|
||||
auto attackHex = dists.predecessors[enemyHex];
|
||||
auto baiBypass = BattleAttackInfo(activeStack, defenderToBypass, 0, cb->battleCanShoot(activeStack));
|
||||
auto attackBypass = AttackPossibility::evaluate(baiBypass, attackHex, damageCache, hb);
|
||||
|
||||
auto adjacentStacks = getAdjacentUnits(enemy);
|
||||
|
||||
adjacentStacks.push_back(defenderToBypass);
|
||||
vstd::removeDuplicates(adjacentStacks);
|
||||
|
||||
auto bypassScore = calculateExchange(
|
||||
attackBypass,
|
||||
dists.distances[attackHex],
|
||||
targets,
|
||||
damageCache,
|
||||
hb,
|
||||
adjacentStacks);
|
||||
|
||||
if(scoreValue(bypassScore) > result.score)
|
||||
{
|
||||
result.score = scoreValue(bypassScore);
|
||||
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("New high score after bypass %f", scoreValue(bypassScore));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.positions.push_back(enemyHex);
|
||||
}
|
||||
|
||||
result.cachedAttack = attack;
|
||||
result.turnsToRich = turnsToRich;
|
||||
}
|
||||
@@ -382,7 +468,8 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
||||
const AttackPossibility & ap,
|
||||
uint8_t turn,
|
||||
PotentialTargets & targets,
|
||||
std::shared_ptr<HypotheticBattle> hb) const
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
std::vector<const battle::Unit *> additionalUnits) const
|
||||
{
|
||||
ReachabilityData result;
|
||||
|
||||
@@ -390,13 +477,26 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
||||
|
||||
if(!ap.attack.shooting) hexes.push_back(ap.from);
|
||||
|
||||
std::vector<const battle::Unit *> allReachableUnits;
|
||||
|
||||
std::vector<const battle::Unit *> allReachableUnits = additionalUnits;
|
||||
|
||||
for(auto hex : hexes)
|
||||
{
|
||||
vstd::concatenate(allReachableUnits, turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex));
|
||||
}
|
||||
|
||||
for(auto hex : ap.attack.attacker->getHexes())
|
||||
{
|
||||
auto unitsReachingAttacker = turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex);
|
||||
for(auto unit : unitsReachingAttacker)
|
||||
{
|
||||
if(unit->unitSide() != ap.attack.attacker->unitSide())
|
||||
{
|
||||
allReachableUnits.push_back(unit);
|
||||
result.enemyUnitsReachingAttacker.insert(unit->unitId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vstd::removeDuplicates(allReachableUnits);
|
||||
|
||||
auto copy = allReachableUnits;
|
||||
@@ -432,7 +532,7 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
||||
|
||||
for(auto unit : allReachableUnits)
|
||||
{
|
||||
auto accessible = !unit->canShoot();
|
||||
auto accessible = !unit->canShoot() || vstd::contains(additionalUnits, unit);
|
||||
|
||||
if(!accessible)
|
||||
{
|
||||
@@ -456,14 +556,14 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
||||
for(auto unit : turnOrder[turn])
|
||||
{
|
||||
if(vstd::contains(allReachableUnits, unit))
|
||||
result.units.push_back(unit);
|
||||
result.units[turn].push_back(unit);
|
||||
}
|
||||
}
|
||||
|
||||
vstd::erase_if(result.units, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return !hb->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
vstd::erase_if(result.units[turn], [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return !hb->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -494,7 +594,8 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
uint8_t turn,
|
||||
PotentialTargets & targets,
|
||||
DamageCache & damageCache,
|
||||
std::shared_ptr<HypotheticBattle> hb) const
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
std::vector<const battle::Unit *> additionalUnits) const
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace("Battle exchange at %d", ap.attack.shooting ? ap.dest.hex : ap.from.hex);
|
||||
@@ -513,7 +614,7 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
if(hb->battleGetUnitByID(ap.attack.defender->unitId())->alive())
|
||||
enemyStacks.push_back(ap.attack.defender);
|
||||
|
||||
ReachabilityData exchangeUnits = getExchangeUnits(ap, turn, targets, hb);
|
||||
ReachabilityData exchangeUnits = getExchangeUnits(ap, turn, targets, hb, additionalUnits);
|
||||
|
||||
if(exchangeUnits.units.empty())
|
||||
{
|
||||
@@ -523,22 +624,25 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
auto exchangeBattle = std::make_shared<HypotheticBattle>(env.get(), hb);
|
||||
BattleExchangeVariant v;
|
||||
|
||||
for(auto unit : exchangeUnits.units)
|
||||
for(int exchangeTurn = 0; exchangeTurn < exchangeUnits.units.size(); exchangeTurn++)
|
||||
{
|
||||
if(unit->isTurret())
|
||||
continue;
|
||||
|
||||
bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, unit, true);
|
||||
auto & attackerQueue = isOur ? ourStacks : enemyStacks;
|
||||
auto u = exchangeBattle->getForUpdate(unit->unitId());
|
||||
|
||||
if(u->alive() && !vstd::contains(attackerQueue, unit))
|
||||
for(auto unit : exchangeUnits.units.at(exchangeTurn))
|
||||
{
|
||||
attackerQueue.push_back(unit);
|
||||
if(unit->isTurret())
|
||||
continue;
|
||||
|
||||
bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, unit, true);
|
||||
auto & attackerQueue = isOur ? ourStacks : enemyStacks;
|
||||
auto u = exchangeBattle->getForUpdate(unit->unitId());
|
||||
|
||||
if(u->alive() && !vstd::contains(attackerQueue, unit))
|
||||
{
|
||||
attackerQueue.push_back(unit);
|
||||
|
||||
#if BATTLE_TRACE_LEVEL
|
||||
logAi->trace("Exchanging: %s", u->getDescription());
|
||||
logAi->trace("Exchanging: %s", u->getDescription());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,122 +656,166 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
|
||||
bool canUseAp = true;
|
||||
|
||||
for(auto activeUnit : exchangeUnits.units)
|
||||
std::set<uint32_t> blockedShooters;
|
||||
|
||||
int totalTurnsCount = simulationTurnsCount >= turn + turnOrder.size()
|
||||
? simulationTurnsCount
|
||||
: turn + turnOrder.size();
|
||||
|
||||
for(int exchangeTurn = 0; exchangeTurn < simulationTurnsCount; exchangeTurn++)
|
||||
{
|
||||
bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, activeUnit, true);
|
||||
battle::Units & attackerQueue = isOur ? ourStacks : enemyStacks;
|
||||
battle::Units & oppositeQueue = isOur ? enemyStacks : ourStacks;
|
||||
bool isMovingTurm = exchangeTurn < turn;
|
||||
int queueTurn = exchangeTurn >= exchangeUnits.units.size()
|
||||
? exchangeUnits.units.size() - 1
|
||||
: exchangeTurn;
|
||||
|
||||
auto attacker = exchangeBattle->getForUpdate(activeUnit->unitId());
|
||||
|
||||
if(!attacker->alive())
|
||||
for(auto activeUnit : exchangeUnits.units.at(queueTurn))
|
||||
{
|
||||
bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, activeUnit, true);
|
||||
battle::Units & attackerQueue = isOur ? ourStacks : enemyStacks;
|
||||
battle::Units & oppositeQueue = isOur ? enemyStacks : ourStacks;
|
||||
|
||||
auto attacker = exchangeBattle->getForUpdate(activeUnit->unitId());
|
||||
auto shooting = exchangeBattle->battleCanShoot(attacker.get())
|
||||
&& !vstd::contains(blockedShooters, attacker->unitId());
|
||||
|
||||
if(!attacker->alive())
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace( "Attacker is dead");
|
||||
logAi->trace("Attacker is dead");
|
||||
#endif
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
auto targetUnit = ap.attack.defender;
|
||||
|
||||
if(!isOur || !exchangeBattle->battleGetUnitByID(targetUnit->unitId())->alive())
|
||||
{
|
||||
auto estimateAttack = [&](const battle::Unit * u) -> float
|
||||
{
|
||||
auto stackWithBonuses = exchangeBattle->getForUpdate(u->unitId());
|
||||
auto score = v.trackAttack(
|
||||
attacker,
|
||||
stackWithBonuses,
|
||||
exchangeBattle->battleCanShoot(stackWithBonuses.get()),
|
||||
isOur,
|
||||
damageCache,
|
||||
hb,
|
||||
true);
|
||||
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace("Best target selector %s->%s score = %2f", attacker->getDescription(), stackWithBonuses->getDescription(), score);
|
||||
#endif
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
auto unitsInOppositeQueueExceptInaccessible = oppositeQueue;
|
||||
|
||||
vstd::erase_if(unitsInOppositeQueueExceptInaccessible, [&](const battle::Unit * u)->bool
|
||||
{
|
||||
return vstd::contains(exchangeUnits.shooters, u);
|
||||
});
|
||||
|
||||
if(!unitsInOppositeQueueExceptInaccessible.empty())
|
||||
{
|
||||
targetUnit = *vstd::maxElementByFun(unitsInOppositeQueueExceptInaccessible, estimateAttack);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
|
||||
if(isMovingTurm && !shooting
|
||||
&& !vstd::contains(exchangeUnits.enemyUnitsReachingAttacker, attacker->unitId()))
|
||||
{
|
||||
auto reachable = exchangeBattle->battleGetUnitsIf([this, &exchangeBattle, &attacker](const battle::Unit * u) -> bool
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace("Attacker is moving");
|
||||
#endif
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
auto targetUnit = ap.attack.defender;
|
||||
|
||||
if(!isOur || !exchangeBattle->battleGetUnitByID(targetUnit->unitId())->alive())
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL>=2
|
||||
logAi->trace("Best target selector for %s", attacker->getDescription());
|
||||
#endif
|
||||
auto estimateAttack = [&](const battle::Unit * u) -> float
|
||||
{
|
||||
auto stackWithBonuses = exchangeBattle->getForUpdate(u->unitId());
|
||||
auto score = v.trackAttack(
|
||||
attacker,
|
||||
stackWithBonuses,
|
||||
exchangeBattle->battleCanShoot(stackWithBonuses.get()),
|
||||
isOur,
|
||||
damageCache,
|
||||
hb,
|
||||
true);
|
||||
|
||||
#if BATTLE_TRACE_LEVEL>=2
|
||||
logAi->trace("Best target selector %s->%s score = %2f", attacker->getDescription(), stackWithBonuses->getDescription(), score);
|
||||
#endif
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
auto unitsInOppositeQueueExceptInaccessible = oppositeQueue;
|
||||
|
||||
vstd::erase_if(unitsInOppositeQueueExceptInaccessible, [&](const battle::Unit * u)->bool
|
||||
{
|
||||
if(u->unitSide() == attacker->unitSide())
|
||||
return false;
|
||||
|
||||
if(!exchangeBattle->getForUpdate(u->unitId())->alive())
|
||||
return false;
|
||||
|
||||
if (!u->getPosition().isValid())
|
||||
return false; // e.g. tower shooters
|
||||
|
||||
return vstd::contains_if(reachabilityMap.at(u->getPosition()), [&attacker](const battle::Unit * other) -> bool
|
||||
{
|
||||
return attacker->unitId() == other->unitId();
|
||||
});
|
||||
return vstd::contains(exchangeUnits.shooters, u);
|
||||
});
|
||||
|
||||
if(!reachable.empty())
|
||||
if(!isOur
|
||||
&& exchangeTurn == 0
|
||||
&& exchangeUnits.units.at(exchangeTurn).at(0)->unitId() != ap.attack.attacker->unitId()
|
||||
&& !vstd::contains(exchangeUnits.enemyUnitsReachingAttacker, attacker->unitId()))
|
||||
{
|
||||
targetUnit = *vstd::maxElementByFun(reachable, estimateAttack);
|
||||
vstd::erase_if(unitsInOppositeQueueExceptInaccessible, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return u->unitId() == ap.attack.attacker->unitId();
|
||||
});
|
||||
}
|
||||
|
||||
if(!unitsInOppositeQueueExceptInaccessible.empty())
|
||||
{
|
||||
targetUnit = *vstd::maxElementByFun(unitsInOppositeQueueExceptInaccessible, estimateAttack);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto reachable = exchangeBattle->battleGetUnitsIf([this, &exchangeBattle, &attacker](const battle::Unit * u) -> bool
|
||||
{
|
||||
if(u->unitSide() == attacker->unitSide())
|
||||
return false;
|
||||
|
||||
if(!exchangeBattle->getForUpdate(u->unitId())->alive())
|
||||
return false;
|
||||
|
||||
if(!u->getPosition().isValid())
|
||||
return false; // e.g. tower shooters
|
||||
|
||||
return vstd::contains_if(reachabilityMap.at(u->getPosition()), [&attacker](const battle::Unit * other) -> bool
|
||||
{
|
||||
return attacker->unitId() == other->unitId();
|
||||
});
|
||||
});
|
||||
|
||||
if(!reachable.empty())
|
||||
{
|
||||
targetUnit = *vstd::maxElementByFun(reachable, estimateAttack);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace("Battle queue is empty and no reachable enemy.");
|
||||
logAi->trace("Battle queue is empty and no reachable enemy.");
|
||||
#endif
|
||||
|
||||
continue;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto defender = exchangeBattle->getForUpdate(targetUnit->unitId());
|
||||
auto shooting = exchangeBattle->battleCanShoot(attacker.get());
|
||||
const int totalAttacks = attacker->getTotalAttacks(shooting);
|
||||
auto defender = exchangeBattle->getForUpdate(targetUnit->unitId());
|
||||
const int totalAttacks = attacker->getTotalAttacks(shooting);
|
||||
|
||||
if(canUseAp && activeUnit->unitId() == ap.attack.attacker->unitId()
|
||||
&& targetUnit->unitId() == ap.attack.defender->unitId())
|
||||
{
|
||||
v.trackAttack(ap, exchangeBattle, damageCache);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int i = 0; i < totalAttacks; i++)
|
||||
if(canUseAp && activeUnit->unitId() == ap.attack.attacker->unitId()
|
||||
&& targetUnit->unitId() == ap.attack.defender->unitId())
|
||||
{
|
||||
v.trackAttack(attacker, defender, shooting, isOur, damageCache, exchangeBattle);
|
||||
|
||||
if(!attacker->alive() || !defender->alive())
|
||||
break;
|
||||
v.trackAttack(ap, exchangeBattle, damageCache);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int i = 0; i < totalAttacks; i++)
|
||||
{
|
||||
v.trackAttack(attacker, defender, shooting, isOur, damageCache, exchangeBattle);
|
||||
|
||||
if(!attacker->alive() || !defender->alive())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!shooting)
|
||||
blockedShooters.insert(defender->unitId());
|
||||
|
||||
canUseAp = false;
|
||||
|
||||
vstd::erase_if(attackerQueue, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
|
||||
vstd::erase_if(oppositeQueue, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
}
|
||||
|
||||
canUseAp = false;
|
||||
|
||||
vstd::erase_if(attackerQueue, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
|
||||
vstd::erase_if(oppositeQueue, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
exchangeBattle->nextRound();
|
||||
}
|
||||
|
||||
// avoid blocking path for stronger stack by weaker stack
|
||||
@@ -679,11 +827,28 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
for(auto hex : hexes)
|
||||
reachabilityMap[hex] = getOneTurnReachableUnits(turn, hex);
|
||||
|
||||
auto score = v.getScore();
|
||||
|
||||
if(simulationTurnsCount < totalTurnsCount)
|
||||
{
|
||||
float scalingRatio = simulationTurnsCount / static_cast<float>(totalTurnsCount);
|
||||
|
||||
score.enemyDamageReduce *= scalingRatio;
|
||||
score.ourDamageReduce *= scalingRatio;
|
||||
}
|
||||
|
||||
if(turn > 0)
|
||||
{
|
||||
auto turnMultiplier = 1 - std::min(0.2, 0.05 * turn);
|
||||
|
||||
score.enemyDamageReduce *= turnMultiplier;
|
||||
}
|
||||
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace("Exchange score: enemy: %2f, our -%2f", v.getScore().enemyDamageReduce, v.getScore().ourDamageReduce);
|
||||
logAi->trace("Exchange score: enemy: %2f, our -%2f", score.enemyDamageReduce, score.ourDamageReduce);
|
||||
#endif
|
||||
|
||||
return v.getScore();
|
||||
return score;
|
||||
}
|
||||
|
||||
bool BattleExchangeEvaluator::canBeHitThisTurn(const AttackPossibility & ap)
|
||||
|
||||
Reference in New Issue
Block a user