|
@@ -18,7 +18,7 @@ AttackerValue::AttackerValue()
|
|
}
|
|
}
|
|
|
|
|
|
MoveTarget::MoveTarget()
|
|
MoveTarget::MoveTarget()
|
|
- : positions(), cachedAttack(), score(EvaluationResult::INEFFECTIVE_SCORE), scorePerTurn(EvaluationResult::INEFFECTIVE_SCORE)
|
|
|
|
|
|
+ : positions(), cachedAttack(), score(EvaluationResult::INEFFECTIVE_SCORE)
|
|
{
|
|
{
|
|
turnsToRich = 1;
|
|
turnsToRich = 1;
|
|
}
|
|
}
|
|
@@ -42,7 +42,7 @@ float BattleExchangeVariant::trackAttack(
|
|
for(auto affectedUnit : affectedUnits)
|
|
for(auto affectedUnit : affectedUnits)
|
|
{
|
|
{
|
|
auto unitToUpdate = hb->getForUpdate(affectedUnit->unitId());
|
|
auto unitToUpdate = hb->getForUpdate(affectedUnit->unitId());
|
|
- auto damageDealt = unitToUpdate->getTotalHealth() - affectedUnit->getTotalHealth();
|
|
|
|
|
|
+ auto damageDealt = unitToUpdate->getAvailableHealth() - affectedUnit->getAvailableHealth();
|
|
|
|
|
|
if(damageDealt > 0)
|
|
if(damageDealt > 0)
|
|
{
|
|
{
|
|
@@ -58,7 +58,7 @@ float BattleExchangeVariant::trackAttack(
|
|
#if BATTLE_TRACE_LEVEL>=1
|
|
#if BATTLE_TRACE_LEVEL>=1
|
|
logAi->trace(
|
|
logAi->trace(
|
|
"%s -> %s, ap retaliation, %s, dps: %lld",
|
|
"%s -> %s, ap retaliation, %s, dps: %lld",
|
|
- ap.attack.defender->getDescription(),
|
|
|
|
|
|
+ hb->getForUpdate(ap.attack.defender->unitId())->getDescription(),
|
|
ap.attack.attacker->getDescription(),
|
|
ap.attack.attacker->getDescription(),
|
|
ap.attack.shooting ? "shot" : "mellee",
|
|
ap.attack.shooting ? "shot" : "mellee",
|
|
damageDealt);
|
|
damageDealt);
|
|
@@ -277,6 +277,36 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
|
return result;
|
|
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(
|
|
MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
|
const battle::Unit * activeStack,
|
|
const battle::Unit * activeStack,
|
|
PotentialTargets & targets,
|
|
PotentialTargets & targets,
|
|
@@ -286,6 +316,8 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
|
MoveTarget result;
|
|
MoveTarget result;
|
|
BattleExchangeVariant ev;
|
|
BattleExchangeVariant ev;
|
|
|
|
|
|
|
|
+ logAi->trace("Find move towards unreachable. Enemies count %d", targets.unreachableEnemies.size());
|
|
|
|
+
|
|
if(targets.unreachableEnemies.empty())
|
|
if(targets.unreachableEnemies.empty())
|
|
return result;
|
|
return result;
|
|
|
|
|
|
@@ -296,17 +328,17 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
|
|
|
|
|
updateReachabilityMap(hb);
|
|
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)
|
|
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)
|
|
if(distance >= GameConstants::BFIELD_SIZE)
|
|
continue;
|
|
continue;
|
|
@@ -315,30 +347,84 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
|
continue;
|
|
continue;
|
|
|
|
|
|
auto turnsToRich = (distance - 1) / speed + 1;
|
|
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 speedRatio = speed / static_cast<float>(enemySpeed);
|
|
auto multiplier = speedRatio > 1 ? 1 : speedRatio;
|
|
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
|
|
// 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);
|
|
auto attack = AttackPossibility::evaluate(bai, hex, damageCache, hb);
|
|
|
|
|
|
attack.shootersBlockedDmg = 0; // we do not want to count on it, it is not for sure
|
|
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 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.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.cachedAttack = attack;
|
|
result.turnsToRich = turnsToRich;
|
|
result.turnsToRich = turnsToRich;
|
|
}
|
|
}
|
|
@@ -382,7 +468,8 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
|
const AttackPossibility & ap,
|
|
const AttackPossibility & ap,
|
|
uint8_t turn,
|
|
uint8_t turn,
|
|
PotentialTargets & targets,
|
|
PotentialTargets & targets,
|
|
- std::shared_ptr<HypotheticBattle> hb) const
|
|
|
|
|
|
+ std::shared_ptr<HypotheticBattle> hb,
|
|
|
|
+ std::vector<const battle::Unit *> additionalUnits) const
|
|
{
|
|
{
|
|
ReachabilityData result;
|
|
ReachabilityData result;
|
|
|
|
|
|
@@ -390,13 +477,26 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
|
|
|
|
|
if(!ap.attack.shooting) hexes.push_back(ap.from);
|
|
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)
|
|
for(auto hex : hexes)
|
|
{
|
|
{
|
|
vstd::concatenate(allReachableUnits, turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex));
|
|
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);
|
|
vstd::removeDuplicates(allReachableUnits);
|
|
|
|
|
|
auto copy = allReachableUnits;
|
|
auto copy = allReachableUnits;
|
|
@@ -432,7 +532,7 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
|
|
|
|
|
for(auto unit : allReachableUnits)
|
|
for(auto unit : allReachableUnits)
|
|
{
|
|
{
|
|
- auto accessible = !unit->canShoot();
|
|
|
|
|
|
+ auto accessible = !unit->canShoot() || vstd::contains(additionalUnits, unit);
|
|
|
|
|
|
if(!accessible)
|
|
if(!accessible)
|
|
{
|
|
{
|
|
@@ -456,14 +556,14 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
|
for(auto unit : turnOrder[turn])
|
|
for(auto unit : turnOrder[turn])
|
|
{
|
|
{
|
|
if(vstd::contains(allReachableUnits, unit))
|
|
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;
|
|
return result;
|
|
}
|
|
}
|
|
@@ -494,7 +594,8 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
|
uint8_t turn,
|
|
uint8_t turn,
|
|
PotentialTargets & targets,
|
|
PotentialTargets & targets,
|
|
DamageCache & damageCache,
|
|
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
|
|
#if BATTLE_TRACE_LEVEL>=1
|
|
logAi->trace("Battle exchange at %d", ap.attack.shooting ? ap.dest.hex : ap.from.hex);
|
|
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())
|
|
if(hb->battleGetUnitByID(ap.attack.defender->unitId())->alive())
|
|
enemyStacks.push_back(ap.attack.defender);
|
|
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())
|
|
if(exchangeUnits.units.empty())
|
|
{
|
|
{
|
|
@@ -523,22 +624,25 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
|
auto exchangeBattle = std::make_shared<HypotheticBattle>(env.get(), hb);
|
|
auto exchangeBattle = std::make_shared<HypotheticBattle>(env.get(), hb);
|
|
BattleExchangeVariant v;
|
|
BattleExchangeVariant v;
|
|
|
|
|
|
- for(auto unit : exchangeUnits.units)
|
|
|
|
|
|
+ for(int exchangeTurn = 0; exchangeTurn < exchangeUnits.units.size(); exchangeTurn++)
|
|
{
|
|
{
|
|
- if(unit->isTurret())
|
|
|
|
- continue;
|
|
|
|
|
|
+ for(auto unit : exchangeUnits.units.at(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());
|
|
|
|
|
|
+ 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(u->alive() && !vstd::contains(attackerQueue, unit))
|
|
|
|
+ {
|
|
|
|
+ attackerQueue.push_back(unit);
|
|
|
|
|
|
#if BATTLE_TRACE_LEVEL
|
|
#if BATTLE_TRACE_LEVEL
|
|
- logAi->trace("Exchanging: %s", u->getDescription());
|
|
|
|
|
|
+ logAi->trace("Exchanging: %s", u->getDescription());
|
|
#endif
|
|
#endif
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -552,122 +656,166 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
|
|
|
|
|
bool canUseAp = true;
|
|
bool canUseAp = true;
|
|
|
|
|
|
- for(auto activeUnit : exchangeUnits.units)
|
|
|
|
- {
|
|
|
|
- bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, activeUnit, true);
|
|
|
|
- battle::Units & attackerQueue = isOur ? ourStacks : enemyStacks;
|
|
|
|
- battle::Units & oppositeQueue = isOur ? enemyStacks : ourStacks;
|
|
|
|
|
|
+ std::set<uint32_t> blockedShooters;
|
|
|
|
|
|
- auto attacker = exchangeBattle->getForUpdate(activeUnit->unitId());
|
|
|
|
|
|
+ int totalTurnsCount = simulationTurnsCount >= turn + turnOrder.size()
|
|
|
|
+ ? simulationTurnsCount
|
|
|
|
+ : turn + turnOrder.size();
|
|
|
|
+
|
|
|
|
+ for(int exchangeTurn = 0; exchangeTurn < simulationTurnsCount; exchangeTurn++)
|
|
|
|
+ {
|
|
|
|
+ bool isMovingTurm = exchangeTurn < turn;
|
|
|
|
+ int queueTurn = exchangeTurn >= exchangeUnits.units.size()
|
|
|
|
+ ? exchangeUnits.units.size() - 1
|
|
|
|
+ : exchangeTurn;
|
|
|
|
|
|
- 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
|
|
#if BATTLE_TRACE_LEVEL>=1
|
|
- logAi->trace( "Attacker is dead");
|
|
|
|
|
|
+ logAi->trace("Attacker is dead");
|
|
#endif
|
|
#endif
|
|
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- auto targetUnit = ap.attack.defender;
|
|
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
|
|
- if(!isOur || !exchangeBattle->battleGetUnitByID(targetUnit->unitId())->alive())
|
|
|
|
- {
|
|
|
|
- auto estimateAttack = [&](const battle::Unit * u) -> float
|
|
|
|
|
|
+ if(isMovingTurm && !shooting
|
|
|
|
+ && !vstd::contains(exchangeUnits.enemyUnitsReachingAttacker, attacker->unitId()))
|
|
{
|
|
{
|
|
- 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
|
|
#if BATTLE_TRACE_LEVEL>=1
|
|
- logAi->trace("Best target selector %s->%s score = %2f", attacker->getDescription(), stackWithBonuses->getDescription(), score);
|
|
|
|
|
|
+ logAi->trace("Attacker is moving");
|
|
#endif
|
|
#endif
|
|
|
|
|
|
- return score;
|
|
|
|
- };
|
|
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
|
|
- auto unitsInOppositeQueueExceptInaccessible = oppositeQueue;
|
|
|
|
|
|
+ auto targetUnit = ap.attack.defender;
|
|
|
|
|
|
- vstd::erase_if(unitsInOppositeQueueExceptInaccessible, [&](const battle::Unit * u)->bool
|
|
|
|
- {
|
|
|
|
- return vstd::contains(exchangeUnits.shooters, u);
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- if(!unitsInOppositeQueueExceptInaccessible.empty())
|
|
|
|
|
|
+ if(!isOur || !exchangeBattle->battleGetUnitByID(targetUnit->unitId())->alive())
|
|
{
|
|
{
|
|
- 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 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
|
|
|
|
|
|
- if(!exchangeBattle->getForUpdate(u->unitId())->alive())
|
|
|
|
- return false;
|
|
|
|
|
|
+ return score;
|
|
|
|
+ };
|
|
|
|
|
|
- if (!u->getPosition().isValid())
|
|
|
|
- return false; // e.g. tower shooters
|
|
|
|
|
|
+ auto unitsInOppositeQueueExceptInaccessible = oppositeQueue;
|
|
|
|
|
|
- return vstd::contains_if(reachabilityMap.at(u->getPosition()), [&attacker](const battle::Unit * other) -> bool
|
|
|
|
- {
|
|
|
|
- return attacker->unitId() == other->unitId();
|
|
|
|
- });
|
|
|
|
|
|
+ vstd::erase_if(unitsInOppositeQueueExceptInaccessible, [&](const battle::Unit * u)->bool
|
|
|
|
+ {
|
|
|
|
+ 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()))
|
|
|
|
+ {
|
|
|
|
+ vstd::erase_if(unitsInOppositeQueueExceptInaccessible, [&](const battle::Unit * u) -> bool
|
|
|
|
+ {
|
|
|
|
+ return u->unitId() == ap.attack.attacker->unitId();
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if(!unitsInOppositeQueueExceptInaccessible.empty())
|
|
{
|
|
{
|
|
- targetUnit = *vstd::maxElementByFun(reachable, estimateAttack);
|
|
|
|
|
|
+ targetUnit = *vstd::maxElementByFun(unitsInOppositeQueueExceptInaccessible, estimateAttack);
|
|
}
|
|
}
|
|
else
|
|
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
|
|
#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
|
|
#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);
|
|
|
|
|
|
+ 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(!attacker->alive() || !defender->alive())
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
|
|
- canUseAp = false;
|
|
|
|
|
|
+ if(!shooting)
|
|
|
|
+ blockedShooters.insert(defender->unitId());
|
|
|
|
|
|
- vstd::erase_if(attackerQueue, [&](const battle::Unit * u) -> bool
|
|
|
|
- {
|
|
|
|
- return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
|
|
|
|
- });
|
|
|
|
|
|
+ canUseAp = false;
|
|
|
|
|
|
- vstd::erase_if(oppositeQueue, [&](const battle::Unit * u) -> bool
|
|
|
|
- {
|
|
|
|
- return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
|
|
|
|
- });
|
|
|
|
|
|
+ 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
|
|
// avoid blocking path for stronger stack by weaker stack
|
|
@@ -679,11 +827,28 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
|
for(auto hex : hexes)
|
|
for(auto hex : hexes)
|
|
reachabilityMap[hex] = getOneTurnReachableUnits(turn, hex);
|
|
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
|
|
#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
|
|
#endif
|
|
|
|
|
|
- return v.getScore();
|
|
|
|
|
|
+ return score;
|
|
}
|
|
}
|
|
|
|
|
|
bool BattleExchangeEvaluator::canBeHitThisTurn(const AttackPossibility & ap)
|
|
bool BattleExchangeEvaluator::canBeHitThisTurn(const AttackPossibility & ap)
|
|
@@ -756,7 +921,7 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getOneTurnReachableUn
|
|
|
|
|
|
ReachabilityInfo unitReachability = reachabilityIter != reachabilityCache.end() ? reachabilityIter->second : turnBattle.getReachability(unit);
|
|
ReachabilityInfo unitReachability = reachabilityIter != reachabilityCache.end() ? reachabilityIter->second : turnBattle.getReachability(unit);
|
|
|
|
|
|
- bool reachable = unitReachability.distances[hex] <= radius;
|
|
|
|
|
|
+ bool reachable = unitReachability.distances.at(hex) <= radius;
|
|
|
|
|
|
if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
|
|
if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
|
|
{
|
|
{
|
|
@@ -766,7 +931,7 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getOneTurnReachableUn
|
|
{
|
|
{
|
|
for(BattleHex neighbor : hex.neighbouringTiles())
|
|
for(BattleHex neighbor : hex.neighbouringTiles())
|
|
{
|
|
{
|
|
- reachable = unitReachability.distances[neighbor] <= radius;
|
|
|
|
|
|
+ reachable = unitReachability.distances.at(neighbor) <= radius;
|
|
|
|
|
|
if(reachable) break;
|
|
if(reachable) break;
|
|
}
|
|
}
|
|
@@ -816,7 +981,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
|
|
for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)
|
|
for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)
|
|
{
|
|
{
|
|
bool enemyUnit = false;
|
|
bool enemyUnit = false;
|
|
- bool reachable = unitReachability.distances[hex] <= unitSpeed;
|
|
|
|
|
|
+ bool reachable = unitReachability.distances.at(hex) <= unitSpeed;
|
|
|
|
|
|
if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
|
|
if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
|
|
{
|
|
{
|
|
@@ -828,7 +993,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
|
|
|
|
|
|
for(BattleHex neighbor : hex.neighbouringTiles())
|
|
for(BattleHex neighbor : hex.neighbouringTiles())
|
|
{
|
|
{
|
|
- reachable = unitReachability.distances[neighbor] <= unitSpeed;
|
|
|
|
|
|
+ reachable = unitReachability.distances.at(neighbor) <= unitSpeed;
|
|
|
|
|
|
if(reachable) break;
|
|
if(reachable) break;
|
|
}
|
|
}
|