|
@@ -12,81 +12,143 @@
|
|
|
#include "../../lib/CStack.h"
|
|
#include "../../lib/CStack.h"
|
|
|
|
|
|
|
|
AttackerValue::AttackerValue()
|
|
AttackerValue::AttackerValue()
|
|
|
|
|
+ : value(0),
|
|
|
|
|
+ isRetalitated(false)
|
|
|
{
|
|
{
|
|
|
- value = 0;
|
|
|
|
|
- isRetalitated = false;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
MoveTarget::MoveTarget()
|
|
MoveTarget::MoveTarget()
|
|
|
- : positions()
|
|
|
|
|
|
|
+ : positions(), cachedAttack(), score(EvaluationResult::INEFFECTIVE_SCORE), scorePerTurn(EvaluationResult::INEFFECTIVE_SCORE)
|
|
|
{
|
|
{
|
|
|
- score = EvaluationResult::INEFFECTIVE_SCORE;
|
|
|
|
|
|
|
+ turnsToRich = 1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-int64_t BattleExchangeVariant::trackAttack(const AttackPossibility & ap, HypotheticBattle & state)
|
|
|
|
|
|
|
+float BattleExchangeVariant::trackAttack(
|
|
|
|
|
+ const AttackPossibility & ap,
|
|
|
|
|
+ std::shared_ptr<HypotheticBattle> hb,
|
|
|
|
|
+ DamageCache & damageCache)
|
|
|
{
|
|
{
|
|
|
|
|
+ auto attacker = hb->getForUpdate(ap.attack.attacker->unitId());
|
|
|
|
|
+
|
|
|
|
|
+ const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
|
|
|
|
|
+ static const auto selectorBlocksRetaliation = Selector::type()(BonusType::BLOCKS_RETALIATION);
|
|
|
|
|
+ const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
|
|
|
|
|
+
|
|
|
|
|
+ float attackValue = 0;
|
|
|
auto affectedUnits = ap.affectedUnits;
|
|
auto affectedUnits = ap.affectedUnits;
|
|
|
|
|
|
|
|
affectedUnits.push_back(ap.attackerState);
|
|
affectedUnits.push_back(ap.attackerState);
|
|
|
|
|
|
|
|
for(auto affectedUnit : affectedUnits)
|
|
for(auto affectedUnit : affectedUnits)
|
|
|
{
|
|
{
|
|
|
- auto unitToUpdate = state.getForUpdate(affectedUnit->unitId());
|
|
|
|
|
|
|
+ auto unitToUpdate = hb->getForUpdate(affectedUnit->unitId());
|
|
|
|
|
|
|
|
- unitToUpdate->health = affectedUnit->health;
|
|
|
|
|
- unitToUpdate->shots = affectedUnit->shots;
|
|
|
|
|
- unitToUpdate->counterAttacks = affectedUnit->counterAttacks;
|
|
|
|
|
- unitToUpdate->movedThisRound = affectedUnit->movedThisRound;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if(unitToUpdate->unitSide() == attacker->unitSide())
|
|
|
|
|
+ {
|
|
|
|
|
+ if(unitToUpdate->unitId() == attacker->unitId())
|
|
|
|
|
+ {
|
|
|
|
|
+ auto defender = hb->getForUpdate(ap.attack.defender->unitId());
|
|
|
|
|
+
|
|
|
|
|
+ if(!defender->alive() || counterAttacksBlocked || ap.attack.shooting || !defender->ableToRetaliate())
|
|
|
|
|
+ continue;
|
|
|
|
|
|
|
|
- auto attackValue = ap.attackValue();
|
|
|
|
|
|
|
+ auto retaliationDamage = damageCache.getDamage(defender.get(), unitToUpdate.get(), hb);
|
|
|
|
|
+ auto attackerDamageReduce = AttackPossibility::calculateDamageReduce(defender.get(), unitToUpdate.get(), retaliationDamage, damageCache, hb);
|
|
|
|
|
|
|
|
- dpsScore += attackValue;
|
|
|
|
|
|
|
+ attackValue -= attackerDamageReduce;
|
|
|
|
|
+ dpsScore.ourDamageReduce += attackerDamageReduce;
|
|
|
|
|
+ attackerValue[unitToUpdate->unitId()].isRetalitated = true;
|
|
|
|
|
+
|
|
|
|
|
+ unitToUpdate->damage(retaliationDamage);
|
|
|
|
|
+ defender->afterAttack(false, true);
|
|
|
|
|
|
|
|
#if BATTLE_TRACE_LEVEL>=1
|
|
#if BATTLE_TRACE_LEVEL>=1
|
|
|
- logAi->trace(
|
|
|
|
|
- "%s -> %s, ap attack, %s, dps: %lld, score: %lld",
|
|
|
|
|
- ap.attack.attacker->getDescription(),
|
|
|
|
|
- ap.attack.defender->getDescription(),
|
|
|
|
|
- ap.attack.shooting ? "shot" : "mellee",
|
|
|
|
|
- ap.damageDealt,
|
|
|
|
|
- attackValue);
|
|
|
|
|
|
|
+ logAi->trace(
|
|
|
|
|
+ "%s -> %s, ap retalitation, %s, dps: %2f, score: %2f",
|
|
|
|
|
+ defender->getDescription(),
|
|
|
|
|
+ unitToUpdate->getDescription(),
|
|
|
|
|
+ ap.attack.shooting ? "shot" : "mellee",
|
|
|
|
|
+ retaliationDamage,
|
|
|
|
|
+ attackerDamageReduce);
|
|
|
#endif
|
|
#endif
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ auto collateralDamage = damageCache.getDamage(attacker.get(), unitToUpdate.get(), hb);
|
|
|
|
|
+ auto collateralDamageReduce = AttackPossibility::calculateDamageReduce(attacker.get(), unitToUpdate.get(), collateralDamage, damageCache, hb);
|
|
|
|
|
+
|
|
|
|
|
+ attackValue -= collateralDamageReduce;
|
|
|
|
|
+ dpsScore.ourDamageReduce += collateralDamageReduce;
|
|
|
|
|
+
|
|
|
|
|
+ unitToUpdate->damage(collateralDamage);
|
|
|
|
|
+
|
|
|
|
|
+#if BATTLE_TRACE_LEVEL>=1
|
|
|
|
|
+ logAi->trace(
|
|
|
|
|
+ "%s -> %s, ap collateral, %s, dps: %2f, score: %2f",
|
|
|
|
|
+ attacker->getDescription(),
|
|
|
|
|
+ unitToUpdate->getDescription(),
|
|
|
|
|
+ ap.attack.shooting ? "shot" : "mellee",
|
|
|
|
|
+ collateralDamage,
|
|
|
|
|
+ collateralDamageReduce);
|
|
|
|
|
+#endif
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ int64_t attackDamage = damageCache.getDamage(attacker.get(), unitToUpdate.get(), hb);
|
|
|
|
|
+ float defenderDamageReduce = AttackPossibility::calculateDamageReduce(attacker.get(), unitToUpdate.get(), attackDamage, damageCache, hb);
|
|
|
|
|
+
|
|
|
|
|
+ attackValue += defenderDamageReduce;
|
|
|
|
|
+ dpsScore.enemyDamageReduce += defenderDamageReduce;
|
|
|
|
|
+ attackerValue[attacker->unitId()].value += defenderDamageReduce;
|
|
|
|
|
+
|
|
|
|
|
+ unitToUpdate->damage(attackDamage);
|
|
|
|
|
+
|
|
|
|
|
+#if BATTLE_TRACE_LEVEL>=1
|
|
|
|
|
+ logAi->trace(
|
|
|
|
|
+ "%s -> %s, ap attack, %s, dps: %2f, score: %2f",
|
|
|
|
|
+ attacker->getDescription(),
|
|
|
|
|
+ unitToUpdate->getDescription(),
|
|
|
|
|
+ ap.attack.shooting ? "shot" : "mellee",
|
|
|
|
|
+ attackDamage,
|
|
|
|
|
+ defenderDamageReduce);
|
|
|
|
|
+#endif
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+#if BATTLE_TRACE_LEVEL >= 1
|
|
|
|
|
+ logAi->trace("ap shooters blocking: %lld", ap.shootersBlockedDmg);
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
|
|
|
|
+ attackValue += ap.shootersBlockedDmg;
|
|
|
|
|
+ dpsScore.enemyDamageReduce += ap.shootersBlockedDmg;
|
|
|
|
|
+ attacker->afterAttack(ap.attack.shooting, false);
|
|
|
|
|
|
|
|
return attackValue;
|
|
return attackValue;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-int64_t BattleExchangeVariant::trackAttack(
|
|
|
|
|
|
|
+float BattleExchangeVariant::trackAttack(
|
|
|
std::shared_ptr<StackWithBonuses> attacker,
|
|
std::shared_ptr<StackWithBonuses> attacker,
|
|
|
std::shared_ptr<StackWithBonuses> defender,
|
|
std::shared_ptr<StackWithBonuses> defender,
|
|
|
bool shooting,
|
|
bool shooting,
|
|
|
bool isOurAttack,
|
|
bool isOurAttack,
|
|
|
- const CBattleInfoCallback & cb,
|
|
|
|
|
|
|
+ DamageCache & damageCache,
|
|
|
|
|
+ std::shared_ptr<HypotheticBattle> hb,
|
|
|
bool evaluateOnly)
|
|
bool evaluateOnly)
|
|
|
{
|
|
{
|
|
|
const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
|
|
const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
|
|
|
static const auto selectorBlocksRetaliation = Selector::type()(BonusType::BLOCKS_RETALIATION);
|
|
static const auto selectorBlocksRetaliation = Selector::type()(BonusType::BLOCKS_RETALIATION);
|
|
|
const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
|
|
const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
|
|
|
|
|
|
|
|
- DamageEstimation retaliation;
|
|
|
|
|
- // FIXME: provide distance info for Jousting bonus
|
|
|
|
|
- BattleAttackInfo bai(attacker.get(), defender.get(), 0, shooting);
|
|
|
|
|
-
|
|
|
|
|
- if(shooting)
|
|
|
|
|
- {
|
|
|
|
|
- bai.attackerPos.setXY(8, 5);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- auto attack = cb.battleEstimateDamage(bai, &retaliation);
|
|
|
|
|
- int64_t attackDamage = (attack.damage.min + attack.damage.max) / 2;
|
|
|
|
|
- int64_t defenderDamageReduce = AttackPossibility::calculateDamageReduce(attacker.get(), defender.get(), attackDamage, cb);
|
|
|
|
|
- int64_t attackerDamageReduce = 0;
|
|
|
|
|
|
|
+ int64_t attackDamage = damageCache.getDamage(attacker.get(), defender.get(), hb);
|
|
|
|
|
+ float defenderDamageReduce = AttackPossibility::calculateDamageReduce(attacker.get(), defender.get(), attackDamage, damageCache, hb);
|
|
|
|
|
+ float attackerDamageReduce = 0;
|
|
|
|
|
|
|
|
if(!evaluateOnly)
|
|
if(!evaluateOnly)
|
|
|
{
|
|
{
|
|
|
#if BATTLE_TRACE_LEVEL>=1
|
|
#if BATTLE_TRACE_LEVEL>=1
|
|
|
logAi->trace(
|
|
logAi->trace(
|
|
|
- "%s -> %s, normal attack, %s, dps: %lld, %lld",
|
|
|
|
|
|
|
+ "%s -> %s, normal attack, %s, dps: %lld, %2f",
|
|
|
attacker->getDescription(),
|
|
attacker->getDescription(),
|
|
|
defender->getDescription(),
|
|
defender->getDescription(),
|
|
|
shooting ? "shot" : "mellee",
|
|
shooting ? "shot" : "mellee",
|
|
@@ -96,49 +158,43 @@ int64_t BattleExchangeVariant::trackAttack(
|
|
|
|
|
|
|
|
if(isOurAttack)
|
|
if(isOurAttack)
|
|
|
{
|
|
{
|
|
|
- dpsScore += defenderDamageReduce;
|
|
|
|
|
|
|
+ dpsScore.enemyDamageReduce += defenderDamageReduce;
|
|
|
attackerValue[attacker->unitId()].value += defenderDamageReduce;
|
|
attackerValue[attacker->unitId()].value += defenderDamageReduce;
|
|
|
}
|
|
}
|
|
|
else
|
|
else
|
|
|
- dpsScore -= defenderDamageReduce;
|
|
|
|
|
|
|
+ dpsScore.ourDamageReduce += defenderDamageReduce;
|
|
|
|
|
|
|
|
defender->damage(attackDamage);
|
|
defender->damage(attackDamage);
|
|
|
attacker->afterAttack(shooting, false);
|
|
attacker->afterAttack(shooting, false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if(defender->alive() && defender->ableToRetaliate() && !counterAttacksBlocked && !shooting)
|
|
|
|
|
|
|
+ if(!evaluateOnly && defender->alive() && defender->ableToRetaliate() && !counterAttacksBlocked && !shooting)
|
|
|
{
|
|
{
|
|
|
- if(retaliation.damage.max != 0)
|
|
|
|
|
- {
|
|
|
|
|
- auto retaliationDamage = (retaliation.damage.min + retaliation.damage.max) / 2;
|
|
|
|
|
- attackerDamageReduce = AttackPossibility::calculateDamageReduce(defender.get(), attacker.get(), retaliationDamage, cb);
|
|
|
|
|
|
|
+ auto retaliationDamage = damageCache.getDamage(defender.get(), attacker.get(), hb);
|
|
|
|
|
+ attackerDamageReduce = AttackPossibility::calculateDamageReduce(defender.get(), attacker.get(), retaliationDamage, damageCache, hb);
|
|
|
|
|
|
|
|
- if(!evaluateOnly)
|
|
|
|
|
- {
|
|
|
|
|
#if BATTLE_TRACE_LEVEL>=1
|
|
#if BATTLE_TRACE_LEVEL>=1
|
|
|
- logAi->trace(
|
|
|
|
|
- "%s -> %s, retaliation, dps: %lld, %lld",
|
|
|
|
|
- defender->getDescription(),
|
|
|
|
|
- attacker->getDescription(),
|
|
|
|
|
- retaliationDamage,
|
|
|
|
|
- attackerDamageReduce);
|
|
|
|
|
|
|
+ logAi->trace(
|
|
|
|
|
+ "%s -> %s, retaliation, dps: %lld, %2f",
|
|
|
|
|
+ defender->getDescription(),
|
|
|
|
|
+ attacker->getDescription(),
|
|
|
|
|
+ retaliationDamage,
|
|
|
|
|
+ attackerDamageReduce);
|
|
|
#endif
|
|
#endif
|
|
|
|
|
|
|
|
- if(isOurAttack)
|
|
|
|
|
- {
|
|
|
|
|
- dpsScore -= attackerDamageReduce;
|
|
|
|
|
- attackerValue[attacker->unitId()].isRetalitated = true;
|
|
|
|
|
- }
|
|
|
|
|
- else
|
|
|
|
|
- {
|
|
|
|
|
- dpsScore += attackerDamageReduce;
|
|
|
|
|
- attackerValue[defender->unitId()].value += attackerDamageReduce;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- attacker->damage(retaliationDamage);
|
|
|
|
|
- defender->afterAttack(false, true);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if(isOurAttack)
|
|
|
|
|
+ {
|
|
|
|
|
+ dpsScore.ourDamageReduce += attackerDamageReduce;
|
|
|
|
|
+ attackerValue[attacker->unitId()].isRetalitated = true;
|
|
|
}
|
|
}
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ dpsScore.enemyDamageReduce += attackerDamageReduce;
|
|
|
|
|
+ attackerValue[defender->unitId()].value += attackerDamageReduce;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ attacker->damage(retaliationDamage);
|
|
|
|
|
+ defender->afterAttack(false, true);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
auto score = defenderDamageReduce - attackerDamageReduce;
|
|
auto score = defenderDamageReduce - attackerDamageReduce;
|
|
@@ -146,58 +202,92 @@ int64_t BattleExchangeVariant::trackAttack(
|
|
|
#if BATTLE_TRACE_LEVEL>=1
|
|
#if BATTLE_TRACE_LEVEL>=1
|
|
|
if(!score)
|
|
if(!score)
|
|
|
{
|
|
{
|
|
|
- logAi->trace("Attack has zero score d:%lld a:%lld", defenderDamageReduce, attackerDamageReduce);
|
|
|
|
|
|
|
+ logAi->trace("Attack has zero score def:%2f att:%2f", defenderDamageReduce, attackerDamageReduce);
|
|
|
}
|
|
}
|
|
|
#endif
|
|
#endif
|
|
|
|
|
|
|
|
return score;
|
|
return score;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-EvaluationResult BattleExchangeEvaluator::findBestTarget(const battle::Unit * activeStack, PotentialTargets & targets, HypotheticBattle & hb)
|
|
|
|
|
|
|
+float BattleExchangeEvaluator::scoreValue(const BattleScore & score) const
|
|
|
{
|
|
{
|
|
|
- EvaluationResult result(targets.bestAction());
|
|
|
|
|
-
|
|
|
|
|
- updateReachabilityMap(hb);
|
|
|
|
|
-
|
|
|
|
|
- for(auto & ap : targets.possibleAttacks)
|
|
|
|
|
- {
|
|
|
|
|
- int64_t score = calculateExchange(ap, targets, hb);
|
|
|
|
|
|
|
+ return score.enemyDamageReduce * getPositiveEffectMultiplier() - score.ourDamageReduce * getNegativeEffectMultiplier();
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- if(score > result.score)
|
|
|
|
|
- {
|
|
|
|
|
- result.score = score;
|
|
|
|
|
- result.bestAttack = ap;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
|
|
|
|
+ const battle::Unit * activeStack,
|
|
|
|
|
+ PotentialTargets & targets,
|
|
|
|
|
+ DamageCache & damageCache,
|
|
|
|
|
+ std::shared_ptr<HypotheticBattle> hb)
|
|
|
|
|
+{
|
|
|
|
|
+ EvaluationResult result(targets.bestAction());
|
|
|
|
|
|
|
|
- if(!activeStack->waited())
|
|
|
|
|
|
|
+ if(!activeStack->waited() && !activeStack->acquireState()->hadMorale)
|
|
|
{
|
|
{
|
|
|
#if BATTLE_TRACE_LEVEL>=1
|
|
#if BATTLE_TRACE_LEVEL>=1
|
|
|
logAi->trace("Evaluating waited attack for %s", activeStack->getDescription());
|
|
logAi->trace("Evaluating waited attack for %s", activeStack->getDescription());
|
|
|
#endif
|
|
#endif
|
|
|
|
|
|
|
|
- hb.getForUpdate(activeStack->unitId())->waiting = true;
|
|
|
|
|
- hb.getForUpdate(activeStack->unitId())->waitedThisTurn = true;
|
|
|
|
|
|
|
+ auto hbWaited = std::make_shared<HypotheticBattle>(env.get(), hb);
|
|
|
|
|
|
|
|
- updateReachabilityMap(hb);
|
|
|
|
|
|
|
+ hbWaited->getForUpdate(activeStack->unitId())->waiting = true;
|
|
|
|
|
+ hbWaited->getForUpdate(activeStack->unitId())->waitedThisTurn = true;
|
|
|
|
|
+
|
|
|
|
|
+ updateReachabilityMap(hbWaited);
|
|
|
|
|
|
|
|
for(auto & ap : targets.possibleAttacks)
|
|
for(auto & ap : targets.possibleAttacks)
|
|
|
{
|
|
{
|
|
|
- int64_t score = calculateExchange(ap, targets, hb);
|
|
|
|
|
|
|
+ float score = evaluateExchange(ap, 0, targets, damageCache, hbWaited);
|
|
|
|
|
|
|
|
if(score > result.score)
|
|
if(score > result.score)
|
|
|
{
|
|
{
|
|
|
result.score = score;
|
|
result.score = score;
|
|
|
result.bestAttack = ap;
|
|
result.bestAttack = ap;
|
|
|
result.wait = true;
|
|
result.wait = true;
|
|
|
|
|
+
|
|
|
|
|
+#if BATTLE_TRACE_LEVEL >= 1
|
|
|
|
|
+ logAi->trace("New high score %2f", result.score);
|
|
|
|
|
+#endif
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+#if BATTLE_TRACE_LEVEL>=1
|
|
|
|
|
+ logAi->trace("Evaluating normal attack for %s", activeStack->getDescription());
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
|
|
|
|
+ updateReachabilityMap(hb);
|
|
|
|
|
+
|
|
|
|
|
+ if(result.bestAttack.attack.shooting && hb->battleHasShootingPenalty(activeStack, result.bestAttack.dest))
|
|
|
|
|
+ {
|
|
|
|
|
+ if(!canBeHitThisTurn(result.bestAttack))
|
|
|
|
|
+ return result; // lets wait
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for(auto & ap : targets.possibleAttacks)
|
|
|
|
|
+ {
|
|
|
|
|
+ float score = evaluateExchange(ap, 0, targets, damageCache, hb);
|
|
|
|
|
+
|
|
|
|
|
+ if(score > result.score || (score == result.score && result.wait))
|
|
|
|
|
+ {
|
|
|
|
|
+ result.score = score;
|
|
|
|
|
+ result.bestAttack = ap;
|
|
|
|
|
+ result.wait = false;
|
|
|
|
|
+
|
|
|
|
|
+#if BATTLE_TRACE_LEVEL >= 1
|
|
|
|
|
+ logAi->trace("New high score %2f", result.score);
|
|
|
|
|
+#endif
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(const battle::Unit * activeStack, PotentialTargets & targets, HypotheticBattle & hb)
|
|
|
|
|
|
|
+MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
|
|
|
|
+ const battle::Unit * activeStack,
|
|
|
|
|
+ PotentialTargets & targets,
|
|
|
|
|
+ DamageCache & damageCache,
|
|
|
|
|
+ std::shared_ptr<HypotheticBattle> hb)
|
|
|
{
|
|
{
|
|
|
MoveTarget result;
|
|
MoveTarget result;
|
|
|
BattleExchangeVariant ev;
|
|
BattleExchangeVariant ev;
|
|
@@ -232,21 +322,31 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(const battle::Uni
|
|
|
|
|
|
|
|
auto turnsToRich = (distance - 1) / speed + 1;
|
|
auto turnsToRich = (distance - 1) / speed + 1;
|
|
|
auto hexes = closestStack->getSurroundingHexes();
|
|
auto hexes = closestStack->getSurroundingHexes();
|
|
|
|
|
+ auto enemySpeed = closestStack->speed();
|
|
|
|
|
+ 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
|
|
// FIXME: provide distance info for Jousting bonus
|
|
|
auto bai = BattleAttackInfo(activeStack, closestStack, 0, cb->battleCanShoot(activeStack));
|
|
auto bai = BattleAttackInfo(activeStack, closestStack, 0, cb->battleCanShoot(activeStack));
|
|
|
- auto attack = AttackPossibility::evaluate(bai, hex, 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, targets, hb) / turnsToRich;
|
|
|
|
|
|
|
+ auto score = calculateExchange(attack, turnsToRich, targets, damageCache, hb);
|
|
|
|
|
+ auto scorePerTurn = BattleScore(score.enemyDamageReduce * std::sqrt(multiplier / turnsToRich), score.ourDamageReduce);
|
|
|
|
|
|
|
|
- if(result.score < score)
|
|
|
|
|
|
|
+ if(result.scorePerTurn < scoreValue(scorePerTurn))
|
|
|
{
|
|
{
|
|
|
- result.score = score;
|
|
|
|
|
|
|
+ result.scorePerTurn = scoreValue(scorePerTurn);
|
|
|
|
|
+ result.score = scoreValue(score);
|
|
|
result.positions = closestStack->getAttackableHexes(activeStack);
|
|
result.positions = closestStack->getAttackableHexes(activeStack);
|
|
|
|
|
+ result.cachedAttack = attack;
|
|
|
|
|
+ result.turnsToRich = turnsToRich;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -254,7 +354,7 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(const battle::Uni
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-std::vector<const battle::Unit *> BattleExchangeEvaluator::getAdjacentUnits(const battle::Unit * blockerUnit)
|
|
|
|
|
|
|
+std::vector<const battle::Unit *> BattleExchangeEvaluator::getAdjacentUnits(const battle::Unit * blockerUnit) const
|
|
|
{
|
|
{
|
|
|
std::queue<const battle::Unit *> queue;
|
|
std::queue<const battle::Unit *> queue;
|
|
|
std::vector<const battle::Unit *> checkedStacks;
|
|
std::vector<const battle::Unit *> checkedStacks;
|
|
@@ -284,21 +384,23 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getAdjacentUnits(cons
|
|
|
return checkedStacks;
|
|
return checkedStacks;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-std::vector<const battle::Unit *> BattleExchangeEvaluator::getExchangeUnits(
|
|
|
|
|
|
|
+ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
|
|
const AttackPossibility & ap,
|
|
const AttackPossibility & ap,
|
|
|
|
|
+ uint8_t turn,
|
|
|
PotentialTargets & targets,
|
|
PotentialTargets & targets,
|
|
|
- HypotheticBattle & hb)
|
|
|
|
|
|
|
+ std::shared_ptr<HypotheticBattle> hb)
|
|
|
{
|
|
{
|
|
|
- auto hexes = ap.attack.defender->getHexes();
|
|
|
|
|
|
|
+ ReachabilityData result;
|
|
|
|
|
+
|
|
|
|
|
+ auto hexes = ap.attack.defender->getSurroundingHexes();
|
|
|
|
|
|
|
|
if(!ap.attack.shooting) hexes.push_back(ap.from);
|
|
if(!ap.attack.shooting) hexes.push_back(ap.from);
|
|
|
|
|
|
|
|
- std::vector<const battle::Unit *> exchangeUnits;
|
|
|
|
|
std::vector<const battle::Unit *> allReachableUnits;
|
|
std::vector<const battle::Unit *> allReachableUnits;
|
|
|
|
|
|
|
|
for(auto hex : hexes)
|
|
for(auto hex : hexes)
|
|
|
{
|
|
{
|
|
|
- vstd::concatenate(allReachableUnits, reachabilityMap[hex]);
|
|
|
|
|
|
|
+ vstd::concatenate(allReachableUnits, turn == 0 ? reachabilityMap[hex] : getOneTurnReachableUnits(turn, hex));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
vstd::removeDuplicates(allReachableUnits);
|
|
vstd::removeDuplicates(allReachableUnits);
|
|
@@ -308,7 +410,7 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getExchangeUnits(
|
|
|
{
|
|
{
|
|
|
for(auto adjacentUnit : getAdjacentUnits(unit))
|
|
for(auto adjacentUnit : getAdjacentUnits(unit))
|
|
|
{
|
|
{
|
|
|
- auto unitWithBonuses = hb.battleGetUnitByID(adjacentUnit->unitId());
|
|
|
|
|
|
|
+ auto unitWithBonuses = hb->battleGetUnitByID(adjacentUnit->unitId());
|
|
|
|
|
|
|
|
if(vstd::contains(targets.unreachableEnemies, adjacentUnit)
|
|
if(vstd::contains(targets.unreachableEnemies, adjacentUnit)
|
|
|
&& !vstd::contains(allReachableUnits, unitWithBonuses))
|
|
&& !vstd::contains(allReachableUnits, unitWithBonuses))
|
|
@@ -331,7 +433,28 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getExchangeUnits(
|
|
|
logAi->trace("Reachability map contains only %d stacks", allReachableUnits.size());
|
|
logAi->trace("Reachability map contains only %d stacks", allReachableUnits.size());
|
|
|
#endif
|
|
#endif
|
|
|
|
|
|
|
|
- return exchangeUnits;
|
|
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for(auto unit : allReachableUnits)
|
|
|
|
|
+ {
|
|
|
|
|
+ auto accessible = !unit->canShoot();
|
|
|
|
|
+
|
|
|
|
|
+ if(!accessible)
|
|
|
|
|
+ {
|
|
|
|
|
+ for(auto hex : unit->getSurroundingHexes())
|
|
|
|
|
+ {
|
|
|
|
|
+ if(ap.attack.defender->coversPos(hex))
|
|
|
|
|
+ {
|
|
|
|
|
+ accessible = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if(accessible)
|
|
|
|
|
+ result.melleeAccessible.push_back(unit);
|
|
|
|
|
+ else
|
|
|
|
|
+ result.shooters.push_back(unit);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
for(int turn = 0; turn < turnOrder.size(); turn++)
|
|
for(int turn = 0; turn < turnOrder.size(); turn++)
|
|
@@ -339,75 +462,114 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getExchangeUnits(
|
|
|
for(auto unit : turnOrder[turn])
|
|
for(auto unit : turnOrder[turn])
|
|
|
{
|
|
{
|
|
|
if(vstd::contains(allReachableUnits, unit))
|
|
if(vstd::contains(allReachableUnits, unit))
|
|
|
- exchangeUnits.push_back(unit);
|
|
|
|
|
|
|
+ result.units.push_back(unit);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return exchangeUnits;
|
|
|
|
|
|
|
+ vstd::erase_if(result.units, [&](const battle::Unit * u) -> bool
|
|
|
|
|
+ {
|
|
|
|
|
+ return !hb->battleGetUnitByID(u->unitId())->alive();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return result;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+float BattleExchangeEvaluator::evaluateExchange(
|
|
|
|
|
+ const AttackPossibility & ap,
|
|
|
|
|
+ uint8_t turn,
|
|
|
|
|
+ PotentialTargets & targets,
|
|
|
|
|
+ DamageCache & damageCache,
|
|
|
|
|
+ std::shared_ptr<HypotheticBattle> hb)
|
|
|
|
|
+{
|
|
|
|
|
+ if(ap.from.hex == 127)
|
|
|
|
|
+ {
|
|
|
|
|
+ logAi->trace("x");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ BattleScore score = calculateExchange(ap, turn, targets, damageCache, hb);
|
|
|
|
|
+
|
|
|
|
|
+#if BATTLE_TRACE_LEVEL >= 1
|
|
|
|
|
+ logAi->trace(
|
|
|
|
|
+ "calculateExchange score +%2f -%2fx%2f = %2f",
|
|
|
|
|
+ score.enemyDamageReduce,
|
|
|
|
|
+ score.ourDamageReduce,
|
|
|
|
|
+ getNegativeEffectMultiplier(),
|
|
|
|
|
+ scoreValue(score));
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
|
|
|
|
+ return scoreValue(score);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-int64_t BattleExchangeEvaluator::calculateExchange(
|
|
|
|
|
|
|
+BattleScore BattleExchangeEvaluator::calculateExchange(
|
|
|
const AttackPossibility & ap,
|
|
const AttackPossibility & ap,
|
|
|
|
|
+ uint8_t turn,
|
|
|
PotentialTargets & targets,
|
|
PotentialTargets & targets,
|
|
|
- HypotheticBattle & hb)
|
|
|
|
|
|
|
+ DamageCache & damageCache,
|
|
|
|
|
+ std::shared_ptr<HypotheticBattle> hb)
|
|
|
{
|
|
{
|
|
|
#if BATTLE_TRACE_LEVEL>=1
|
|
#if BATTLE_TRACE_LEVEL>=1
|
|
|
- logAi->trace("Battle exchange at %lld", ap.attack.shooting ? ap.dest : ap.from);
|
|
|
|
|
|
|
+ logAi->trace("Battle exchange at %d", ap.attack.shooting ? ap.dest.hex : ap.from.hex);
|
|
|
#endif
|
|
#endif
|
|
|
|
|
|
|
|
if(cb->battleGetMySide() == BattlePerspective::LEFT_SIDE
|
|
if(cb->battleGetMySide() == BattlePerspective::LEFT_SIDE
|
|
|
&& cb->battleGetGateState() == EGateState::BLOCKED
|
|
&& cb->battleGetGateState() == EGateState::BLOCKED
|
|
|
- && ap.attack.defender->coversPos(ESiegeHex::GATE_BRIDGE))
|
|
|
|
|
|
|
+ && ap.attack.defender->coversPos(BattleHex::GATE_BRIDGE))
|
|
|
{
|
|
{
|
|
|
- return EvaluationResult::INEFFECTIVE_SCORE;
|
|
|
|
|
|
|
+ return BattleScore(EvaluationResult::INEFFECTIVE_SCORE, 0);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
std::vector<const battle::Unit *> ourStacks;
|
|
std::vector<const battle::Unit *> ourStacks;
|
|
|
std::vector<const battle::Unit *> enemyStacks;
|
|
std::vector<const battle::Unit *> enemyStacks;
|
|
|
|
|
|
|
|
- enemyStacks.push_back(ap.attack.defender);
|
|
|
|
|
|
|
+ if(hb->battleGetUnitByID(ap.attack.defender->unitId())->alive())
|
|
|
|
|
+ enemyStacks.push_back(ap.attack.defender);
|
|
|
|
|
|
|
|
- std::vector<const battle::Unit *> exchangeUnits = getExchangeUnits(ap, targets, hb);
|
|
|
|
|
|
|
+ ReachabilityData exchangeUnits = getExchangeUnits(ap, turn, targets, hb);
|
|
|
|
|
|
|
|
- if(exchangeUnits.empty())
|
|
|
|
|
|
|
+ if(exchangeUnits.units.empty())
|
|
|
{
|
|
{
|
|
|
- return 0;
|
|
|
|
|
|
|
+ return BattleScore();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- HypotheticBattle exchangeBattle(env.get(), cb);
|
|
|
|
|
|
|
+ auto exchangeBattle = std::make_shared<HypotheticBattle>(env.get(), hb);
|
|
|
BattleExchangeVariant v;
|
|
BattleExchangeVariant v;
|
|
|
- auto melleeAttackers = ourStacks;
|
|
|
|
|
|
|
|
|
|
- vstd::removeDuplicates(melleeAttackers);
|
|
|
|
|
- vstd::erase_if(melleeAttackers, [&](const battle::Unit * u) -> bool
|
|
|
|
|
- {
|
|
|
|
|
- return !cb->battleCanShoot(u);
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- for(auto unit : exchangeUnits)
|
|
|
|
|
|
|
+ for(auto unit : exchangeUnits.units)
|
|
|
{
|
|
{
|
|
|
if(unit->isTurret())
|
|
if(unit->isTurret())
|
|
|
continue;
|
|
continue;
|
|
|
|
|
|
|
|
- bool isOur = cb->battleMatchOwner(ap.attack.attacker, unit, true);
|
|
|
|
|
|
|
+ bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, unit, true);
|
|
|
auto & attackerQueue = isOur ? ourStacks : enemyStacks;
|
|
auto & attackerQueue = isOur ? ourStacks : enemyStacks;
|
|
|
|
|
+ auto u = exchangeBattle->getForUpdate(unit->unitId());
|
|
|
|
|
|
|
|
-
|
|
|
|
|
- if(!vstd::contains(attackerQueue, unit))
|
|
|
|
|
|
|
+ if(u->alive() && !vstd::contains(attackerQueue, unit))
|
|
|
{
|
|
{
|
|
|
attackerQueue.push_back(unit);
|
|
attackerQueue.push_back(unit);
|
|
|
|
|
+
|
|
|
|
|
+#if BATTLE_TRACE_LEVEL
|
|
|
|
|
+ logAi->trace("Exchanging: %s", u->getDescription());
|
|
|
|
|
+#endif
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ auto melleeAttackers = ourStacks;
|
|
|
|
|
+
|
|
|
|
|
+ vstd::removeDuplicates(melleeAttackers);
|
|
|
|
|
+ vstd::erase_if(melleeAttackers, [&](const battle::Unit * u) -> bool
|
|
|
|
|
+ {
|
|
|
|
|
+ return cb->battleCanShoot(u);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
bool canUseAp = true;
|
|
bool canUseAp = true;
|
|
|
|
|
|
|
|
- for(auto activeUnit : exchangeUnits)
|
|
|
|
|
|
|
+ for(auto activeUnit : exchangeUnits.units)
|
|
|
{
|
|
{
|
|
|
- bool isOur = cb->battleMatchOwner(ap.attack.attacker, activeUnit, true);
|
|
|
|
|
|
|
+ bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, activeUnit, true);
|
|
|
battle::Units & attackerQueue = isOur ? ourStacks : enemyStacks;
|
|
battle::Units & attackerQueue = isOur ? ourStacks : enemyStacks;
|
|
|
battle::Units & oppositeQueue = isOur ? enemyStacks : ourStacks;
|
|
battle::Units & oppositeQueue = isOur ? enemyStacks : ourStacks;
|
|
|
|
|
|
|
|
- auto attacker = exchangeBattle.getForUpdate(activeUnit->unitId());
|
|
|
|
|
|
|
+ auto attacker = exchangeBattle->getForUpdate(activeUnit->unitId());
|
|
|
|
|
|
|
|
if(!attacker->alive())
|
|
if(!attacker->alive())
|
|
|
{
|
|
{
|
|
@@ -420,35 +582,46 @@ int64_t BattleExchangeEvaluator::calculateExchange(
|
|
|
|
|
|
|
|
auto targetUnit = ap.attack.defender;
|
|
auto targetUnit = ap.attack.defender;
|
|
|
|
|
|
|
|
- if(!isOur || !exchangeBattle.getForUpdate(targetUnit->unitId())->alive())
|
|
|
|
|
|
|
+ if(!isOur || !exchangeBattle->battleGetUnitByID(targetUnit->unitId())->alive())
|
|
|
{
|
|
{
|
|
|
- auto estimateAttack = [&](const battle::Unit * u) -> int64_t
|
|
|
|
|
|
|
+ auto estimateAttack = [&](const battle::Unit * u) -> float
|
|
|
{
|
|
{
|
|
|
- auto stackWithBonuses = exchangeBattle.getForUpdate(u->unitId());
|
|
|
|
|
|
|
+ auto stackWithBonuses = exchangeBattle->getForUpdate(u->unitId());
|
|
|
auto score = v.trackAttack(
|
|
auto score = v.trackAttack(
|
|
|
attacker,
|
|
attacker,
|
|
|
stackWithBonuses,
|
|
stackWithBonuses,
|
|
|
- exchangeBattle.battleCanShoot(stackWithBonuses.get()),
|
|
|
|
|
|
|
+ exchangeBattle->battleCanShoot(stackWithBonuses.get()),
|
|
|
isOur,
|
|
isOur,
|
|
|
- *cb,
|
|
|
|
|
|
|
+ damageCache,
|
|
|
|
|
+ hb,
|
|
|
true);
|
|
true);
|
|
|
|
|
|
|
|
#if BATTLE_TRACE_LEVEL>=1
|
|
#if BATTLE_TRACE_LEVEL>=1
|
|
|
- logAi->trace("Best target selector %s->%s score = %lld", attacker->getDescription(), u->getDescription(), score);
|
|
|
|
|
|
|
+ logAi->trace("Best target selector %s->%s score = %2f", attacker->getDescription(), stackWithBonuses->getDescription(), score);
|
|
|
#endif
|
|
#endif
|
|
|
|
|
|
|
|
return score;
|
|
return score;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- if(!oppositeQueue.empty())
|
|
|
|
|
|
|
+ auto unitsInOppositeQueueExceptInaccessible = oppositeQueue;
|
|
|
|
|
+
|
|
|
|
|
+ vstd::erase_if(unitsInOppositeQueueExceptInaccessible, [&](const battle::Unit * u)->bool
|
|
|
|
|
+ {
|
|
|
|
|
+ return vstd::contains(exchangeUnits.shooters, u);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if(!unitsInOppositeQueueExceptInaccessible.empty())
|
|
|
{
|
|
{
|
|
|
- targetUnit = *vstd::maxElementByFun(oppositeQueue, estimateAttack);
|
|
|
|
|
|
|
+ targetUnit = *vstd::maxElementByFun(unitsInOppositeQueueExceptInaccessible, estimateAttack);
|
|
|
}
|
|
}
|
|
|
else
|
|
else
|
|
|
{
|
|
{
|
|
|
- auto reachable = exchangeBattle.battleGetUnitsIf([&](const battle::Unit * u) -> bool
|
|
|
|
|
|
|
+ auto reachable = exchangeBattle->battleGetUnitsIf([&](const battle::Unit * u) -> bool
|
|
|
{
|
|
{
|
|
|
- if(!u->alive() || u->unitSide() == attacker->unitSide())
|
|
|
|
|
|
|
+ if(u->unitSide() == attacker->unitSide())
|
|
|
|
|
+ return false;
|
|
|
|
|
+
|
|
|
|
|
+ if(!exchangeBattle->getForUpdate(u->unitId())->alive())
|
|
|
return false;
|
|
return false;
|
|
|
|
|
|
|
|
return vstd::contains_if(reachabilityMap[u->getPosition()], [&](const battle::Unit * other) -> bool
|
|
return vstd::contains_if(reachabilityMap[u->getPosition()], [&](const battle::Unit * other) -> bool
|
|
@@ -472,19 +645,20 @@ int64_t BattleExchangeEvaluator::calculateExchange(
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- auto defender = exchangeBattle.getForUpdate(targetUnit->unitId());
|
|
|
|
|
- auto shooting = cb->battleCanShoot(attacker.get());
|
|
|
|
|
|
|
+ auto defender = exchangeBattle->getForUpdate(targetUnit->unitId());
|
|
|
|
|
+ auto shooting = exchangeBattle->battleCanShoot(attacker.get());
|
|
|
const int totalAttacks = attacker->getTotalAttacks(shooting);
|
|
const int totalAttacks = attacker->getTotalAttacks(shooting);
|
|
|
|
|
|
|
|
- if(canUseAp && activeUnit == ap.attack.attacker && targetUnit == ap.attack.defender)
|
|
|
|
|
|
|
+ if(canUseAp && activeUnit->unitId() == ap.attack.attacker->unitId()
|
|
|
|
|
+ && targetUnit->unitId() == ap.attack.defender->unitId())
|
|
|
{
|
|
{
|
|
|
- v.trackAttack(ap, exchangeBattle);
|
|
|
|
|
|
|
+ v.trackAttack(ap, exchangeBattle, damageCache);
|
|
|
}
|
|
}
|
|
|
else
|
|
else
|
|
|
{
|
|
{
|
|
|
for(int i = 0; i < totalAttacks; i++)
|
|
for(int i = 0; i < totalAttacks; i++)
|
|
|
{
|
|
{
|
|
|
- v.trackAttack(attacker, defender, shooting, isOur, exchangeBattle);
|
|
|
|
|
|
|
+ v.trackAttack(attacker, defender, shooting, isOur, damageCache, exchangeBattle);
|
|
|
|
|
|
|
|
if(!attacker->alive() || !defender->alive())
|
|
if(!attacker->alive() || !defender->alive())
|
|
|
break;
|
|
break;
|
|
@@ -495,21 +669,31 @@ int64_t BattleExchangeEvaluator::calculateExchange(
|
|
|
|
|
|
|
|
vstd::erase_if(attackerQueue, [&](const battle::Unit * u) -> bool
|
|
vstd::erase_if(attackerQueue, [&](const battle::Unit * u) -> bool
|
|
|
{
|
|
{
|
|
|
- return !exchangeBattle.getForUpdate(u->unitId())->alive();
|
|
|
|
|
|
|
+ return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
vstd::erase_if(oppositeQueue, [&](const battle::Unit * u) -> bool
|
|
vstd::erase_if(oppositeQueue, [&](const battle::Unit * u) -> bool
|
|
|
{
|
|
{
|
|
|
- return !exchangeBattle.getForUpdate(u->unitId())->alive();
|
|
|
|
|
|
|
+ return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// avoid blocking path for stronger stack by weaker stack
|
|
// avoid blocking path for stronger stack by weaker stack
|
|
|
// the method checks if all stacks can be placed around enemy
|
|
// the method checks if all stacks can be placed around enemy
|
|
|
- v.adjustPositions(melleeAttackers, ap, reachabilityMap);
|
|
|
|
|
|
|
+ std::map<BattleHex, battle::Units> reachabilityMap;
|
|
|
|
|
+
|
|
|
|
|
+ auto hexes = ap.attack.defender->getSurroundingHexes();
|
|
|
|
|
+
|
|
|
|
|
+ for(auto hex : hexes)
|
|
|
|
|
+ reachabilityMap[hex] = getOneTurnReachableUnits(turn, hex);
|
|
|
|
|
+
|
|
|
|
|
+ if(!ap.attack.shooting)
|
|
|
|
|
+ {
|
|
|
|
|
+ v.adjustPositions(melleeAttackers, ap, reachabilityMap);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
#if BATTLE_TRACE_LEVEL>=1
|
|
#if BATTLE_TRACE_LEVEL>=1
|
|
|
- logAi->trace("Exchange score: %lld", v.getScore());
|
|
|
|
|
|
|
+ logAi->trace("Exchange score: enemy: %2f, our -%2f", v.getScore().enemyDamageReduce, v.getScore().ourDamageReduce);
|
|
|
#endif
|
|
#endif
|
|
|
|
|
|
|
|
return v.getScore();
|
|
return v.getScore();
|
|
@@ -533,13 +717,10 @@ void BattleExchangeVariant::adjustPositions(
|
|
|
return attackerValue[u1->unitId()].value > attackerValue[u2->unitId()].value;
|
|
return attackerValue[u1->unitId()].value > attackerValue[u2->unitId()].value;
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- if(!ap.attack.shooting)
|
|
|
|
|
- {
|
|
|
|
|
- vstd::erase_if_present(hexes, ap.from);
|
|
|
|
|
- vstd::erase_if_present(hexes, ap.attack.attacker->occupiedHex(ap.attack.attackerPos));
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ vstd::erase_if_present(hexes, ap.from);
|
|
|
|
|
+ vstd::erase_if_present(hexes, ap.attack.attacker->occupiedHex(ap.attack.attackerPos));
|
|
|
|
|
|
|
|
- int64_t notRealizedDamage = 0;
|
|
|
|
|
|
|
+ float notRealizedDamage = 0;
|
|
|
|
|
|
|
|
for(auto unit : attackers)
|
|
for(auto unit : attackers)
|
|
|
{
|
|
{
|
|
@@ -555,7 +736,7 @@ void BattleExchangeVariant::adjustPositions(
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- auto desiredPosition = vstd::minElementByFun(hexes, [&](BattleHex h) -> int64_t
|
|
|
|
|
|
|
+ auto desiredPosition = vstd::minElementByFun(hexes, [&](BattleHex h) -> float
|
|
|
{
|
|
{
|
|
|
auto score = vstd::contains(reachabilityMap[h], unit)
|
|
auto score = vstd::contains(reachabilityMap[h], unit)
|
|
|
? reachabilityMap[h].size()
|
|
? reachabilityMap[h].size()
|
|
@@ -577,22 +758,58 @@ void BattleExchangeVariant::adjustPositions(
|
|
|
|
|
|
|
|
if(notRealizedDamage > ap.attackValue() && notRealizedDamage > attackerValue[ap.attack.attacker->unitId()].value)
|
|
if(notRealizedDamage > ap.attackValue() && notRealizedDamage > attackerValue[ap.attack.attacker->unitId()].value)
|
|
|
{
|
|
{
|
|
|
- dpsScore = EvaluationResult::INEFFECTIVE_SCORE;
|
|
|
|
|
|
|
+ dpsScore = BattleScore(EvaluationResult::INEFFECTIVE_SCORE, 0);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-void BattleExchangeEvaluator::updateReachabilityMap(HypotheticBattle & hb)
|
|
|
|
|
|
|
+bool BattleExchangeEvaluator::canBeHitThisTurn(const AttackPossibility & ap)
|
|
|
|
|
+{
|
|
|
|
|
+ for(auto pos : ap.attack.attacker->getSurroundingHexes())
|
|
|
|
|
+ {
|
|
|
|
|
+ for(auto u : reachabilityMap[pos])
|
|
|
|
|
+ {
|
|
|
|
|
+ if(u->unitSide() != ap.attack.attacker->unitSide())
|
|
|
|
|
+ {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return false;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void BattleExchangeEvaluator::updateReachabilityMap(std::shared_ptr<HypotheticBattle> hb)
|
|
|
{
|
|
{
|
|
|
const int TURN_DEPTH = 2;
|
|
const int TURN_DEPTH = 2;
|
|
|
|
|
|
|
|
turnOrder.clear();
|
|
turnOrder.clear();
|
|
|
-
|
|
|
|
|
- hb.battleGetTurnOrder(turnOrder, std::numeric_limits<int>::max(), TURN_DEPTH);
|
|
|
|
|
- reachabilityMap.clear();
|
|
|
|
|
|
|
|
|
|
- for(int turn = 0; turn < turnOrder.size(); turn++)
|
|
|
|
|
|
|
+ hb->battleGetTurnOrder(turnOrder, std::numeric_limits<int>::max(), TURN_DEPTH);
|
|
|
|
|
+
|
|
|
|
|
+ for(auto turn : turnOrder)
|
|
|
{
|
|
{
|
|
|
- auto & turnQueue = turnOrder[turn];
|
|
|
|
|
|
|
+ for(auto u : turn)
|
|
|
|
|
+ {
|
|
|
|
|
+ if(!vstd::contains(reachabilityCache, u->unitId()))
|
|
|
|
|
+ {
|
|
|
|
|
+ reachabilityCache[u->unitId()] = hb->getReachability(u);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)
|
|
|
|
|
+ {
|
|
|
|
|
+ reachabilityMap[hex] = getOneTurnReachableUnits(0, hex);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::vector<const battle::Unit *> BattleExchangeEvaluator::getOneTurnReachableUnits(uint8_t turn, BattleHex hex)
|
|
|
|
|
+{
|
|
|
|
|
+ std::vector<const battle::Unit *> result;
|
|
|
|
|
+
|
|
|
|
|
+ for(int i = 0; i < turnOrder.size(); i++, turn++)
|
|
|
|
|
+ {
|
|
|
|
|
+ auto & turnQueue = turnOrder[i];
|
|
|
HypotheticBattle turnBattle(env.get(), cb);
|
|
HypotheticBattle turnBattle(env.get(), cb);
|
|
|
|
|
|
|
|
for(const battle::Unit * unit : turnQueue)
|
|
for(const battle::Unit * unit : turnQueue)
|
|
@@ -600,46 +817,49 @@ void BattleExchangeEvaluator::updateReachabilityMap(HypotheticBattle & hb)
|
|
|
if(unit->isTurret())
|
|
if(unit->isTurret())
|
|
|
continue;
|
|
continue;
|
|
|
|
|
|
|
|
- auto unitSpeed = unit->speed(turn);
|
|
|
|
|
-
|
|
|
|
|
if(turnBattle.battleCanShoot(unit))
|
|
if(turnBattle.battleCanShoot(unit))
|
|
|
{
|
|
{
|
|
|
- for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)
|
|
|
|
|
- {
|
|
|
|
|
- reachabilityMap[hex].push_back(unit);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ result.push_back(unit);
|
|
|
|
|
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- auto unitReachability = turnBattle.getReachability(unit);
|
|
|
|
|
|
|
+ auto unitSpeed = unit->speed(turn);
|
|
|
|
|
+ auto radius = unitSpeed * (turn + 1);
|
|
|
|
|
|
|
|
- for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)
|
|
|
|
|
|
|
+ ReachabilityInfo unitReachability = vstd::getOrCompute(
|
|
|
|
|
+ reachabilityCache,
|
|
|
|
|
+ unit->unitId(),
|
|
|
|
|
+ [&](ReachabilityInfo & data)
|
|
|
|
|
+ {
|
|
|
|
|
+ data = turnBattle.getReachability(unit);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ bool reachable = unitReachability.distances[hex] <= radius;
|
|
|
|
|
+
|
|
|
|
|
+ if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
|
|
|
{
|
|
{
|
|
|
- bool reachable = unitReachability.distances[hex] <= unitSpeed;
|
|
|
|
|
|
|
+ const battle::Unit * hexStack = cb->battleGetUnitByPos(hex);
|
|
|
|
|
|
|
|
- if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
|
|
|
|
|
|
|
+ if(hexStack && cb->battleMatchOwner(unit, hexStack, false))
|
|
|
{
|
|
{
|
|
|
- const battle::Unit * hexStack = cb->battleGetUnitByPos(hex);
|
|
|
|
|
-
|
|
|
|
|
- if(hexStack && cb->battleMatchOwner(unit, hexStack, false))
|
|
|
|
|
|
|
+ for(BattleHex neighbor : hex.neighbouringTiles())
|
|
|
{
|
|
{
|
|
|
- for(BattleHex neighbor : hex.neighbouringTiles())
|
|
|
|
|
- {
|
|
|
|
|
- reachable = unitReachability.distances[neighbor] <= unitSpeed;
|
|
|
|
|
|
|
+ reachable = unitReachability.distances[neighbor] <= radius;
|
|
|
|
|
|
|
|
- if(reachable) break;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if(reachable) break;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if(reachable)
|
|
|
|
|
- {
|
|
|
|
|
- reachabilityMap[hex].push_back(unit);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if(reachable)
|
|
|
|
|
+ {
|
|
|
|
|
+ result.push_back(unit);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// avoid blocking path for stronger stack by weaker stack
|
|
// avoid blocking path for stronger stack by weaker stack
|
|
@@ -670,11 +890,12 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
|
|
|
auto ratio = blockedUnitDamage / (blockedUnitDamage + activeUnitDamage);
|
|
auto ratio = blockedUnitDamage / (blockedUnitDamage + activeUnitDamage);
|
|
|
|
|
|
|
|
auto unitReachability = turnBattle.getReachability(unit);
|
|
auto unitReachability = turnBattle.getReachability(unit);
|
|
|
|
|
+ auto unitSpeed = unit->speed(turn); // Cached value, to avoid performance hit
|
|
|
|
|
|
|
|
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] <= unit->speed(turn);
|
|
|
|
|
|
|
+ bool reachable = unitReachability.distances[hex] <= unitSpeed;
|
|
|
|
|
|
|
|
if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
|
|
if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
|
|
|
{
|
|
{
|
|
@@ -686,7 +907,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
|
|
|
|
|
|
|
|
for(BattleHex neighbor : hex.neighbouringTiles())
|
|
for(BattleHex neighbor : hex.neighbouringTiles())
|
|
|
{
|
|
{
|
|
|
- reachable = unitReachability.distances[neighbor] <= unit->speed(turn);
|
|
|
|
|
|
|
+ reachable = unitReachability.distances[neighbor] <= unitSpeed;
|
|
|
|
|
|
|
|
if(reachable) break;
|
|
if(reachable) break;
|
|
|
}
|
|
}
|