1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-07-17 01:32:21 +02:00

Battle AI: add some comments + refactoring

This commit is contained in:
Andrii Danylchenko
2022-10-17 19:47:16 +03:00
parent 601ced3749
commit ebf4854801
8 changed files with 174 additions and 144 deletions

View File

@ -13,6 +13,11 @@
// Eventually only IBattleInfoCallback and battle::Unit should be used, // Eventually only IBattleInfoCallback and battle::Unit should be used,
// CUnitState should be private and CStack should be removed completely // CUnitState should be private and CStack should be removed completely
uint64_t averageDmg(const TDmgRange & range)
{
return (range.first + range.second) / 2;
}
AttackPossibility::AttackPossibility(BattleHex from, BattleHex dest, const BattleAttackInfo & attack) AttackPossibility::AttackPossibility(BattleHex from, BattleHex dest, const BattleAttackInfo & attack)
: from(from), dest(dest), attack(attack) : from(from), dest(dest), attack(attack)
{ {
@ -20,7 +25,7 @@ AttackPossibility::AttackPossibility(BattleHex from, BattleHex dest, const Battl
int64_t AttackPossibility::damageDiff() const int64_t AttackPossibility::damageDiff() const
{ {
return damageDealt - damageReceived - collateralDamage + shootersBlockedDmg; return defenderDamageReduce - attackerDamageReduce - collateralDamageReduce + shootersBlockedDmg;
} }
int64_t AttackPossibility::attackValue() const int64_t AttackPossibility::attackValue() const
@ -28,23 +33,31 @@ int64_t AttackPossibility::attackValue() const
return damageDiff(); return damageDiff();
} }
int64_t AttackPossibility::calculateDpsReduce( /// <summary>
/// How enemy damage will be reduced by this attack
/// Half bounty for kill, half for making damage equal to enemy health
/// Bounty - the killed creature average damage calculated against attacker
/// </summary>
int64_t AttackPossibility::calculateDamageReduce(
const battle::Unit * attacker, const battle::Unit * attacker,
const battle::Unit * defender, const battle::Unit * defender,
uint64_t damageDealt, uint64_t damageDealt,
std::shared_ptr<CBattleInfoCallback> cb) const CBattleInfoCallback & cb)
{ {
const float HEALTH_BOUNTY = 0.5;
const float KILL_BOUNTY = 1.0 - HEALTH_BOUNTY;
vstd::amin(damageDealt, defender->getAvailableHealth()); vstd::amin(damageDealt, defender->getAvailableHealth());
auto enemyDamageBeforeAttack = cb->battleEstimateDamage(BattleAttackInfo(defender, attacker, defender->canShoot())); auto enemyDamageBeforeAttack = cb.battleEstimateDamage(BattleAttackInfo(defender, attacker, defender->canShoot()));
auto enemiesKilled = damageDealt / defender->MaxHealth() + (damageDealt % defender->MaxHealth() >= defender->getFirstHPleft() ? 1 : 0); auto enemiesKilled = damageDealt / defender->MaxHealth() + (damageDealt % defender->MaxHealth() >= defender->getFirstHPleft() ? 1 : 0);
auto enemyDps = (enemyDamageBeforeAttack.first + enemyDamageBeforeAttack.second) / 2; auto enemyDamage = averageDmg(enemyDamageBeforeAttack);
auto dpsPerEnemy = enemyDps / (double)defender->getCount(); auto damagePerEnemy = enemyDamage / (double)defender->getCount();
return (int64_t)(dpsPerEnemy * (enemiesKilled + damageDealt / (double)defender->MaxHealth()) / 2); return (int64_t)(damagePerEnemy * (enemiesKilled * KILL_BOUNTY + damageDealt * HEALTH_BOUNTY / (double)defender->MaxHealth()));
} }
int64_t AttackPossibility::evaluateBlockedShootersDmg(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle * state) int64_t AttackPossibility::evaluateBlockedShootersDmg(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle & state)
{ {
int64_t res = 0; int64_t res = 0;
@ -55,10 +68,10 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(const BattleAttackInfo & a
auto hexes = attacker->getSurroundingHexes(hex); auto hexes = attacker->getSurroundingHexes(hex);
for(BattleHex tile : hexes) for(BattleHex tile : hexes)
{ {
auto st = state->battleGetUnitByPos(tile, true); auto st = state.battleGetUnitByPos(tile, true);
if(!st || !state->battleMatchOwner(st, attacker)) if(!st || !state.battleMatchOwner(st, attacker))
continue; continue;
if(!state->battleCanShoot(st)) if(!state.battleCanShoot(st))
continue; continue;
BattleAttackInfo rangeAttackInfo(st, attacker, true); BattleAttackInfo rangeAttackInfo(st, attacker, true);
@ -67,23 +80,23 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(const BattleAttackInfo & a
BattleAttackInfo meleeAttackInfo(st, attacker, false); BattleAttackInfo meleeAttackInfo(st, attacker, false);
meleeAttackInfo.defenderPos = hex; meleeAttackInfo.defenderPos = hex;
auto rangeDmg = getCbc()->battleEstimateDamage(rangeAttackInfo); auto rangeDmg = state.battleEstimateDamage(rangeAttackInfo);
auto meleeDmg = getCbc()->battleEstimateDamage(meleeAttackInfo); auto meleeDmg = state.battleEstimateDamage(meleeAttackInfo);
int64_t gain = (rangeDmg.first + rangeDmg.second - meleeDmg.first - meleeDmg.second) / 2 + 1; int64_t gain = averageDmg(rangeDmg) - averageDmg(meleeDmg) + 1;
res += gain; res += gain;
} }
return res; return res;
} }
AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle * state) AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle & state)
{ {
auto attacker = attackInfo.attacker; auto attacker = attackInfo.attacker;
auto defender = attackInfo.defender; auto defender = attackInfo.defender;
const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION"; const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
static const auto selectorBlocksRetaliation = Selector::type()(Bonus::BLOCKS_RETALIATION); static const auto selectorBlocksRetaliation = Selector::type()(Bonus::BLOCKS_RETALIATION);
const auto attackerSide = getCbc()->playerToSide(getCbc()->battleGetOwner(attacker)); const auto attackerSide = state.playerToSide(state.battleGetOwner(attacker));
const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation); const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
AttackPossibility bestAp(hex, BattleHex::INVALID, attackInfo); AttackPossibility bestAp(hex, BattleHex::INVALID, attackInfo);
@ -111,9 +124,9 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInf
std::vector<const battle::Unit*> units; std::vector<const battle::Unit*> units;
if (attackInfo.shooting) if (attackInfo.shooting)
units = state->getAttackedBattleUnits(attacker, defHex, true, BattleHex::INVALID); units = state.getAttackedBattleUnits(attacker, defHex, true, BattleHex::INVALID);
else else
units = state->getAttackedBattleUnits(attacker, defHex, false, hex); units = state.getAttackedBattleUnits(attacker, defHex, false, hex);
// ensure the defender is also affected // ensure the defender is also affected
bool addDefender = true; bool addDefender = true;
@ -139,11 +152,11 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInf
for(int i = 0; i < totalAttacks; i++) for(int i = 0; i < totalAttacks; i++)
{ {
int64_t damageDealt, damageReceived, enemyDpsReduce, ourDpsReduce; int64_t damageDealt, damageReceived, defenderDamageReduce, attackerDamageReduce;
TDmgRange retaliation(0, 0); TDmgRange retaliation(0, 0);
auto attackDmg = getCbc()->battleEstimateDamage(ap.attack, &retaliation); auto attackDmg = state.battleEstimateDamage(ap.attack, &retaliation);
TDmgRange enemyDamageBeforeAttack = getCbc()->battleEstimateDamage(BattleAttackInfo(u, attacker, u->canShoot())); TDmgRange defenderDamageBeforeAttack = state.battleEstimateDamage(BattleAttackInfo(u, attacker, u->canShoot()));
vstd::amin(attackDmg.first, defenderState->getAvailableHealth()); vstd::amin(attackDmg.first, defenderState->getAvailableHealth());
vstd::amin(attackDmg.second, defenderState->getAvailableHealth()); vstd::amin(attackDmg.second, defenderState->getAvailableHealth());
@ -151,36 +164,36 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInf
vstd::amin(retaliation.first, ap.attackerState->getAvailableHealth()); vstd::amin(retaliation.first, ap.attackerState->getAvailableHealth());
vstd::amin(retaliation.second, ap.attackerState->getAvailableHealth()); vstd::amin(retaliation.second, ap.attackerState->getAvailableHealth());
damageDealt = (attackDmg.first + attackDmg.second) / 2; damageDealt = averageDmg(attackDmg);
enemyDpsReduce = calculateDpsReduce(attacker, defender, damageDealt, getCbc()); defenderDamageReduce = calculateDamageReduce(attacker, defender, damageDealt, state);
ap.attackerState->afterAttack(attackInfo.shooting, false); ap.attackerState->afterAttack(attackInfo.shooting, false);
//FIXME: use ranged retaliation //FIXME: use ranged retaliation
damageReceived = 0; damageReceived = 0;
ourDpsReduce = 0; attackerDamageReduce = 0;
if (!attackInfo.shooting && defenderState->ableToRetaliate() && !counterAttacksBlocked) if (!attackInfo.shooting && defenderState->ableToRetaliate() && !counterAttacksBlocked)
{ {
damageReceived = (retaliation.first + retaliation.second) / 2; damageReceived = averageDmg(retaliation);
ourDpsReduce = calculateDpsReduce(defender, attacker, damageReceived, getCbc()); attackerDamageReduce = calculateDamageReduce(defender, attacker, damageReceived, state);
defenderState->afterAttack(attackInfo.shooting, true); defenderState->afterAttack(attackInfo.shooting, true);
} }
bool isEnemy = state->battleMatchOwner(attacker, u); bool isEnemy = state.battleMatchOwner(attacker, u);
// this includes enemy units as well as attacker units under enemy's mind control // this includes enemy units as well as attacker units under enemy's mind control
if(isEnemy) if(isEnemy)
ap.damageDealt += enemyDpsReduce; ap.defenderDamageReduce += defenderDamageReduce;
// damaging attacker's units (even those under enemy's mind control) is considered friendly fire // damaging attacker's units (even those under enemy's mind control) is considered friendly fire
if(attackerSide == u->unitSide()) if(attackerSide == u->unitSide())
ap.collateralDamage += enemyDpsReduce; ap.collateralDamageReduce += defenderDamageReduce;
if(u->unitId() == defender->unitId() || if(u->unitId() == defender->unitId() ||
(!attackInfo.shooting && CStack::isMeleeAttackPossible(u, attacker, hex))) (!attackInfo.shooting && CStack::isMeleeAttackPossible(u, attacker, hex)))
{ {
//FIXME: handle RANGED_RETALIATION ? //FIXME: handle RANGED_RETALIATION ?
ap.damageReceived += ourDpsReduce; ap.attackerDamageReduce += attackerDamageReduce;
} }
ap.attackerState->damage(damageReceived); ap.attackerState->damage(damageReceived);
@ -198,11 +211,11 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInf
// check how much damage we gain from blocking enemy shooters on this hex // check how much damage we gain from blocking enemy shooters on this hex
bestAp.shootersBlockedDmg = evaluateBlockedShootersDmg(attackInfo, hex, state); bestAp.shootersBlockedDmg = evaluateBlockedShootersDmg(attackInfo, hex, state);
logAi->debug("BattleAI best AP: %s -> %s at %d from %d, affects %d units: %lld %lld %lld %lld", logAi->debug("BattleAI best AP: %s -> %s at %d from %d, affects %d units: d:%lld a:%lld c:%lld s:%lld",
attackInfo.attacker->unitType()->identifier, attackInfo.attacker->unitType()->identifier,
attackInfo.defender->unitType()->identifier, attackInfo.defender->unitType()->identifier,
(int)bestAp.dest, (int)bestAp.from, (int)bestAp.affectedUnits.size(), (int)bestAp.dest, (int)bestAp.from, (int)bestAp.affectedUnits.size(),
bestAp.damageDealt, bestAp.damageReceived, bestAp.collateralDamage, bestAp.shootersBlockedDmg); bestAp.defenderDamageReduce, bestAp.attackerDamageReduce, bestAp.collateralDamageReduce, bestAp.shootersBlockedDmg);
//TODO other damage related to attack (eg. fire shield and other abilities) //TODO other damage related to attack (eg. fire shield and other abilities)
return bestAp; return bestAp;

View File

@ -15,6 +15,10 @@
#define BATTLE_TRACE_LEVEL 0 #define BATTLE_TRACE_LEVEL 0
/// <summary>
/// Evaluate attack value of one particular attack taking into account various effects like
/// retaliation, 2-hex breath, collateral damage, shooters blocked damage
/// </summary>
class AttackPossibility class AttackPossibility
{ {
public: public:
@ -26,9 +30,9 @@ public:
std::vector<std::shared_ptr<battle::CUnitState>> affectedUnits; std::vector<std::shared_ptr<battle::CUnitState>> affectedUnits;
int64_t damageDealt = 0; int64_t defenderDamageReduce = 0;
int64_t damageReceived = 0; //usually by counter-attack int64_t attackerDamageReduce = 0; //usually by counter-attack
int64_t collateralDamage = 0; // friendly fire (usually by two-hex attacks) int64_t collateralDamageReduce = 0; // friendly fire (usually by two-hex attacks)
int64_t shootersBlockedDmg = 0; int64_t shootersBlockedDmg = 0;
AttackPossibility(BattleHex from, BattleHex dest, const BattleAttackInfo & attack_); AttackPossibility(BattleHex from, BattleHex dest, const BattleAttackInfo & attack_);
@ -36,14 +40,14 @@ public:
int64_t damageDiff() const; int64_t damageDiff() const;
int64_t attackValue() const; int64_t attackValue() const;
static AttackPossibility evaluate(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle * state); static AttackPossibility evaluate(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle & state);
static int64_t calculateDpsReduce( static int64_t calculateDamageReduce(
const battle::Unit * attacker, const battle::Unit * attacker,
const battle::Unit * defender, const battle::Unit * defender,
uint64_t damageDealt, uint64_t damageDealt,
std::shared_ptr<CBattleInfoCallback> cb); const CBattleInfoCallback & cb);
private: private:
static int64_t evaluateBlockedShootersDmg(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle * state); static int64_t evaluateBlockedShootersDmg(const BattleAttackInfo & attackInfo, BattleHex hex, const HypotheticBattle & state);
}; };

View File

@ -161,9 +161,8 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
} }
HypotheticBattle hb(env.get(), cb); HypotheticBattle hb(env.get(), cb);
int turn = 0;
PotentialTargets targets(stack, &hb); PotentialTargets targets(stack, hb);
BattleExchangeEvaluator scoreEvaluator(cb, env); BattleExchangeEvaluator scoreEvaluator(cb, env);
auto moveTarget = scoreEvaluator.findMoveTowardsUnreachable(stack, targets, hb); auto moveTarget = scoreEvaluator.findMoveTowardsUnreachable(stack, targets, hb);
@ -171,7 +170,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
if(!targets.possibleAttacks.empty()) if(!targets.possibleAttacks.empty())
{ {
#if BATTLE_TRACE_LEVEL==1 #if BATTLE_TRACE_LEVEL>=1
logAi->trace("Evaluating attack for %s", stack->getDescription()); logAi->trace("Evaluating attack for %s", stack->getDescription());
#endif #endif
@ -205,15 +204,15 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
else else
{ {
result = BattleAction::makeMeleeAttack(stack, bestAttack.attack.defender->getPosition(), bestAttack.from); result = BattleAction::makeMeleeAttack(stack, bestAttack.attack.defender->getPosition(), bestAttack.from);
action = "mellee"; action = "melee";
} }
logAi->debug("BattleAI: %s -> %s x %d, %s, from %d curpos %d dist %d speed %d: %lld %lld %lld", logAi->debug("BattleAI: %s -> %s x %d, %s, from %d curpos %d dist %d speed %d: +%lld -%lld = %lld",
bestAttack.attackerState->unitType()->identifier, bestAttack.attackerState->unitType()->identifier,
bestAttack.affectedUnits[0]->unitType()->identifier, bestAttack.affectedUnits[0]->unitType()->identifier,
(int)bestAttack.affectedUnits[0]->getCount(), action, (int)bestAttack.from, (int)bestAttack.attack.attacker->getPosition().hex, (int)bestAttack.affectedUnits[0]->getCount(), action, (int)bestAttack.from, (int)bestAttack.attack.attacker->getPosition().hex,
bestAttack.attack.chargedFields, bestAttack.attack.attacker->Speed(0, true), bestAttack.attack.chargedFields, bestAttack.attack.attacker->Speed(0, true),
bestAttack.damageDealt, bestAttack.damageReceived, bestAttack.attackValue() bestAttack.defenderDamageReduce, bestAttack.attackerDamageReduce, bestAttack.attackValue()
); );
} }
} }
@ -323,12 +322,15 @@ BattleAction CBattleAI::goTowardsNearest(const CStack * stack, std::vector<Battl
// We just check all available hexes and pick the one closest to the target. // We just check all available hexes and pick the one closest to the target.
auto nearestAvailableHex = vstd::minElementByFun(avHexes, [&](BattleHex hex) -> int auto nearestAvailableHex = vstd::minElementByFun(avHexes, [&](BattleHex hex) -> int
{ {
const int MOAT_PENALTY = 100; // avoid landing on moat
const int BLOCKED_STACK_PENALTY = 100; // avoid landing on moat
auto distance = BattleHex::getDistance(bestNeighbor, hex); auto distance = BattleHex::getDistance(bestNeighbor, hex);
if(vstd::contains(moatHexes, hex)) if(vstd::contains(moatHexes, hex))
distance += 100; distance += MOAT_PENALTY;
return scoreEvaluator.checkPositionBlocksOurStacks(hb, stack, hex) ? 100 + distance : distance; return scoreEvaluator.checkPositionBlocksOurStacks(hb, stack, hex) ? BLOCKED_STACK_PENALTY + distance : distance;
}); });
return BattleAction::makeMove(stack, *nearestAvailableHex); return BattleAction::makeMove(stack, *nearestAvailableHex);
@ -444,7 +446,7 @@ void CBattleAI::attemptCastingSpell()
using ValueMap = PossibleSpellcast::ValueMap; using ValueMap = PossibleSpellcast::ValueMap;
auto evaluateQueue = [&](ValueMap & values, const std::vector<battle::Units> & queue, HypotheticBattle * state, size_t minTurnSpan, bool * enemyHadTurnOut) -> bool auto evaluateQueue = [&](ValueMap & values, const std::vector<battle::Units> & queue, HypotheticBattle & state, size_t minTurnSpan, bool * enemyHadTurnOut) -> bool
{ {
bool firstRound = true; bool firstRound = true;
bool enemyHadTurn = false; bool enemyHadTurn = false;
@ -455,7 +457,7 @@ void CBattleAI::attemptCastingSpell()
for(auto & round : queue) for(auto & round : queue)
{ {
if(!firstRound) if(!firstRound)
state->nextRound(0);//todo: set actual value? state.nextRound(0);//todo: set actual value?
for(auto unit : round) for(auto unit : round)
{ {
if(!vstd::contains(values, unit->unitId())) if(!vstd::contains(values, unit->unitId()))
@ -464,11 +466,11 @@ void CBattleAI::attemptCastingSpell()
if(!unit->alive()) if(!unit->alive())
continue; continue;
if(state->battleGetOwner(unit) != playerID) if(state.battleGetOwner(unit) != playerID)
{ {
enemyHadTurn = true; enemyHadTurn = true;
if(!firstRound || state->battleCastSpells(unit->unitSide()) == 0) if(!firstRound || state.battleCastSpells(unit->unitSide()) == 0)
{ {
//enemy could counter our spell at this point //enemy could counter our spell at this point
//anyway, we do not know what enemy will do //anyway, we do not know what enemy will do
@ -482,7 +484,7 @@ void CBattleAI::attemptCastingSpell()
ourTurnSpan++; ourTurnSpan++;
} }
state->nextTurn(unit->unitId()); state.nextTurn(unit->unitId());
PotentialTargets pt(unit, state); PotentialTargets pt(unit, state);
@ -490,22 +492,22 @@ void CBattleAI::attemptCastingSpell()
{ {
AttackPossibility ap = pt.bestAction(); AttackPossibility ap = pt.bestAction();
auto swb = state->getForUpdate(unit->unitId()); auto swb = state.getForUpdate(unit->unitId());
*swb = *ap.attackerState; *swb = *ap.attackerState;
if(ap.damageDealt > 0) if(ap.defenderDamageReduce > 0)
swb->removeUnitBonus(Bonus::UntilAttack); swb->removeUnitBonus(Bonus::UntilAttack);
if(ap.damageReceived > 0) if(ap.attackerDamageReduce > 0)
swb->removeUnitBonus(Bonus::UntilBeingAttacked); swb->removeUnitBonus(Bonus::UntilBeingAttacked);
for(auto affected : ap.affectedUnits) for(auto affected : ap.affectedUnits)
{ {
swb = state->getForUpdate(affected->unitId()); swb = state.getForUpdate(affected->unitId());
*swb = *affected; *swb = *affected;
if(ap.damageDealt > 0) if(ap.defenderDamageReduce > 0)
swb->removeUnitBonus(Bonus::UntilBeingAttacked); swb->removeUnitBonus(Bonus::UntilBeingAttacked);
if(ap.damageReceived > 0 && ap.attack.defender->unitId() == affected->unitId()) if(ap.attackerDamageReduce > 0 && ap.attack.defender->unitId() == affected->unitId())
swb->removeUnitBonus(Bonus::UntilAttack); swb->removeUnitBonus(Bonus::UntilAttack);
} }
} }
@ -513,7 +515,7 @@ void CBattleAI::attemptCastingSpell()
auto bav = pt.bestActionValue(); auto bav = pt.bestActionValue();
//best action is from effective owner`s point if view, we need to convert to our point if view //best action is from effective owner`s point if view, we need to convert to our point if view
if(state->battleGetOwner(unit) != playerID) if(state.battleGetOwner(unit) != playerID)
bav = -bav; bav = -bav;
values[unit->unitId()] += bav; values[unit->unitId()] += bav;
} }
@ -566,7 +568,7 @@ void CBattleAI::attemptCastingSpell()
HypotheticBattle state(env.get(), cb); HypotheticBattle state(env.get(), cb);
evaluateQueue(valueOfStack, turnOrder, &state, 0, &enemyHadTurn); evaluateQueue(valueOfStack, turnOrder, state, 0, &enemyHadTurn);
if(!enemyHadTurn) if(!enemyHadTurn)
{ {
@ -614,7 +616,7 @@ void CBattleAI::attemptCastingSpell()
state.battleGetTurnOrder(newTurnOrder, amount, 2); state.battleGetTurnOrder(newTurnOrder, amount, 2);
const bool turnSpanOK = evaluateQueue(newValueOfStack, newTurnOrder, &state, minTurnSpan, nullptr); const bool turnSpanOK = evaluateQueue(newValueOfStack, newTurnOrder, state, minTurnSpan, nullptr);
if(turnSpanOK || castNow) if(turnSpanOK || castNow)
{ {

View File

@ -18,12 +18,12 @@ AttackerValue::AttackerValue()
} }
MoveTarget::MoveTarget() MoveTarget::MoveTarget()
:positions() : positions()
{ {
score = EvaluationResult::INEFFECTIVE_SCORE; score = EvaluationResult::INEFFECTIVE_SCORE;
} }
int64_t BattleExchangeVariant::trackAttack(const AttackPossibility & ap, HypotheticBattle * state) int64_t BattleExchangeVariant::trackAttack(const AttackPossibility & ap, HypotheticBattle & state)
{ {
auto affectedUnits = ap.affectedUnits; auto affectedUnits = ap.affectedUnits;
@ -31,7 +31,7 @@ int64_t BattleExchangeVariant::trackAttack(const AttackPossibility & ap, Hypothe
for(auto affectedUnit : affectedUnits) for(auto affectedUnit : affectedUnits)
{ {
auto unitToUpdate = state->getForUpdate(affectedUnit->unitId()); auto unitToUpdate = state.getForUpdate(affectedUnit->unitId());
unitToUpdate->health = affectedUnit->health; unitToUpdate->health = affectedUnit->health;
unitToUpdate->shots = affectedUnit->shots; unitToUpdate->shots = affectedUnit->shots;
@ -43,9 +43,9 @@ int64_t BattleExchangeVariant::trackAttack(const AttackPossibility & ap, Hypothe
dpsScore += attackValue; dpsScore += attackValue;
#if BATTLE_TRACE_LEVEL==1 #if BATTLE_TRACE_LEVEL>=1
logAi->trace( logAi->trace(
"%s -> %s, ap attack, %s, dps: %d, score: %d", "%s -> %s, ap attack, %s, dps: %lld, score: %lld",
ap.attack.attacker->getDescription(), ap.attack.attacker->getDescription(),
ap.attack.defender->getDescription(), ap.attack.defender->getDescription(),
ap.attack.shooting ? "shot" : "mellee", ap.attack.shooting ? "shot" : "mellee",
@ -61,14 +61,14 @@ int64_t BattleExchangeVariant::trackAttack(
std::shared_ptr<StackWithBonuses> defender, std::shared_ptr<StackWithBonuses> defender,
bool shooting, bool shooting,
bool isOurAttack, bool isOurAttack,
std::shared_ptr<CBattleInfoCallback> cb, const CBattleInfoCallback & cb,
bool evaluateOnly) bool evaluateOnly)
{ {
const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION"; const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
static const auto selectorBlocksRetaliation = Selector::type()(Bonus::BLOCKS_RETALIATION); static const auto selectorBlocksRetaliation = Selector::type()(Bonus::BLOCKS_RETALIATION);
const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation); const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
TDmgRange retalitation; TDmgRange retaliation;
BattleAttackInfo bai(attacker.get(), defender.get(), shooting); BattleAttackInfo bai(attacker.get(), defender.get(), shooting);
if(shooting) if(shooting)
@ -76,30 +76,30 @@ int64_t BattleExchangeVariant::trackAttack(
bai.attackerPos.setXY(8, 5); bai.attackerPos.setXY(8, 5);
} }
auto attack = cb->battleEstimateDamage(bai, &retalitation); auto attack = cb.battleEstimateDamage(bai, &retaliation);
int64_t attackDamage = (attack.first + attack.second) / 2; int64_t attackDamage = (attack.first + attack.second) / 2;
int64_t defenderDpsReduce = AttackPossibility::calculateDpsReduce(attacker.get(), defender.get(), attackDamage, cb); int64_t defenderDamageReduce = AttackPossibility::calculateDamageReduce(attacker.get(), defender.get(), attackDamage, cb);
int64_t attackerDpsReduce = 0; int64_t 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: %d, %d", "%s -> %s, normal attack, %s, dps: %lld, %lld",
attacker->getDescription(), attacker->getDescription(),
defender->getDescription(), defender->getDescription(),
shooting ? "shot" : "mellee", shooting ? "shot" : "mellee",
attackDamage, attackDamage,
defenderDpsReduce); defenderDamageReduce);
#endif #endif
if(isOurAttack) if(isOurAttack)
{ {
dpsScore += defenderDpsReduce; dpsScore += defenderDamageReduce;
attackerValue[attacker->unitId()].value += defenderDpsReduce; attackerValue[attacker->unitId()].value += defenderDamageReduce;
} }
else else
dpsScore -= defenderDpsReduce; dpsScore -= defenderDamageReduce;
defender->damage(attackDamage); defender->damage(attackDamage);
attacker->afterAttack(shooting, false); attacker->afterAttack(shooting, false);
@ -107,45 +107,45 @@ int64_t BattleExchangeVariant::trackAttack(
if(defender->alive() && defender->ableToRetaliate() && !counterAttacksBlocked && !shooting) if(defender->alive() && defender->ableToRetaliate() && !counterAttacksBlocked && !shooting)
{ {
if(retalitation.second != 0) if(retaliation.second != 0)
{ {
auto retalitationDamage = (retalitation.first + retalitation.second) / 2; auto retaliationDamage = (retaliation.first + retaliation.second) / 2;
attackerDpsReduce = AttackPossibility::calculateDpsReduce(defender.get(), attacker.get(), retalitationDamage, cb); attackerDamageReduce = AttackPossibility::calculateDamageReduce(defender.get(), attacker.get(), retaliationDamage, cb);
if(!evaluateOnly) if(!evaluateOnly)
{ {
#if BATTLE_TRACE_LEVEL==1 #if BATTLE_TRACE_LEVEL>=1
logAi->trace( logAi->trace(
"%s -> %s, retalitation, dps: %d, %d", "%s -> %s, retaliation, dps: %lld, %lld",
defender->getDescription(), defender->getDescription(),
attacker->getDescription(), attacker->getDescription(),
retalitationDamage, retaliationDamage,
attackerDpsReduce); attackerDamageReduce);
#endif #endif
if(isOurAttack) if(isOurAttack)
{ {
dpsScore -= attackerDpsReduce; dpsScore -= attackerDamageReduce;
attackerValue[attacker->unitId()].isRetalitated = true; attackerValue[attacker->unitId()].isRetalitated = true;
} }
else else
{ {
dpsScore += attackerDpsReduce; dpsScore += attackerDamageReduce;
attackerValue[defender->unitId()].value += attackerDpsReduce; attackerValue[defender->unitId()].value += attackerDamageReduce;
} }
attacker->damage(retalitationDamage); attacker->damage(retaliationDamage);
defender->afterAttack(false, true); defender->afterAttack(false, true);
} }
} }
} }
auto score = defenderDpsReduce - attackerDpsReduce; auto score = defenderDamageReduce - attackerDamageReduce;
#if BATTLE_TRACE_LEVEL==1 #if BATTLE_TRACE_LEVEL>=1
if(!score) if(!score)
{ {
logAi->trace("Zero %d %d", defenderDpsReduce, attackerDpsReduce); logAi->trace("Attack has zero score d:%lld a:%lld", defenderDamageReduce, attackerDamageReduce);
} }
#endif #endif
@ -171,7 +171,7 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(const battle::Unit * ac
if(!activeStack->waited()) if(!activeStack->waited())
{ {
#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
@ -233,7 +233,7 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(const battle::Uni
for(auto hex : hexes) for(auto hex : hexes)
{ {
auto bai = BattleAttackInfo(activeStack, closestStack, cb->battleCanShoot(activeStack)); auto bai = BattleAttackInfo(activeStack, closestStack, cb->battleCanShoot(activeStack));
auto attack = AttackPossibility::evaluate(bai, hex, &hb); auto attack = AttackPossibility::evaluate(bai, hex, 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
@ -323,7 +323,7 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getExchangeUnits(
if(allReachableUnits.size() < 2) if(allReachableUnits.size() < 2)
{ {
#if BATTLE_TRACE_LEVEL==1 #if BATTLE_TRACE_LEVEL>=1
logAi->trace("Reachability map contains only %d stacks", allReachableUnits.size()); logAi->trace("Reachability map contains only %d stacks", allReachableUnits.size());
#endif #endif
@ -347,8 +347,8 @@ int64_t BattleExchangeEvaluator::calculateExchange(
PotentialTargets & targets, PotentialTargets & targets,
HypotheticBattle & hb) HypotheticBattle & hb)
{ {
#if BATTLE_TRACE_LEVEL==1 #if BATTLE_TRACE_LEVEL>=1
logAi->trace("Battle exchange at %d", ap.attack.shooting ? ap.dest : ap.from); logAi->trace("Battle exchange at %lld", ap.attack.shooting ? ap.dest : ap.from);
#endif #endif
std::vector<const battle::Unit *> ourStacks; std::vector<const battle::Unit *> ourStacks;
@ -396,7 +396,7 @@ int64_t BattleExchangeEvaluator::calculateExchange(
if(!attacker->alive()) 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
@ -415,11 +415,11 @@ int64_t BattleExchangeEvaluator::calculateExchange(
stackWithBonuses, stackWithBonuses,
exchangeBattle.battleCanShoot(stackWithBonuses.get()), exchangeBattle.battleCanShoot(stackWithBonuses.get()),
isOur, isOur,
cb, *cb,
true); true);
#if BATTLE_TRACE_LEVEL==1 #if BATTLE_TRACE_LEVEL>=1
logAi->trace("Best target selector %s->%s score = %d", attacker->getDescription(), u->getDescription(), score); logAi->trace("Best target selector %s->%s score = %lld", attacker->getDescription(), u->getDescription(), score);
#endif #endif
return score; return score;
@ -448,7 +448,7 @@ int64_t BattleExchangeEvaluator::calculateExchange(
} }
else 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
@ -463,13 +463,13 @@ int64_t BattleExchangeEvaluator::calculateExchange(
if(canUseAp && activeUnit == ap.attack.attacker && targetUnit == ap.attack.defender) if(canUseAp && activeUnit == ap.attack.attacker && targetUnit == ap.attack.defender)
{ {
v.trackAttack(ap, &exchangeBattle); v.trackAttack(ap, exchangeBattle);
} }
else else
{ {
for(int i = 0; i < totalAttacks; i++) for(int i = 0; i < totalAttacks; i++)
{ {
v.trackAttack(attacker, defender, shooting, isOur, cb); v.trackAttack(attacker, defender, shooting, isOur, exchangeBattle);
if(!attacker->alive() || !defender->alive()) if(!attacker->alive() || !defender->alive())
break; break;
@ -489,10 +489,12 @@ int64_t BattleExchangeEvaluator::calculateExchange(
}); });
} }
// avoid blocking path for stronger stack by weaker stack
// the method checks if all stacks can be placed around enemy
v.adjustPositions(melleeAttackers, ap, reachabilityMap); v.adjustPositions(melleeAttackers, ap, reachabilityMap);
#if BATTLE_TRACE_LEVEL==1 #if BATTLE_TRACE_LEVEL>=1
logAi->trace("Exchange score: %ld", v.getScore()); logAi->trace("Exchange score: %lld", v.getScore());
#endif #endif
return v.getScore(); return v.getScore();
@ -522,7 +524,7 @@ void BattleExchangeVariant::adjustPositions(
vstd::erase_if_present(hexes, ap.attack.attacker->occupiedHex(ap.attack.attackerPos)); vstd::erase_if_present(hexes, ap.attack.attacker->occupiedHex(ap.attack.attackerPos));
} }
int64_t notRealizedDps = 0; int64_t notRealizedDamage = 0;
for(auto unit : attackers) for(auto unit : attackers)
{ {
@ -534,7 +536,7 @@ void BattleExchangeVariant::adjustPositions(
return vstd::contains(reachabilityMap[h], unit); return vstd::contains(reachabilityMap[h], unit);
})) }))
{ {
notRealizedDps += attackerValue[unit->unitId()].value; notRealizedDamage += attackerValue[unit->unitId()].value;
continue; continue;
} }
@ -542,7 +544,7 @@ void BattleExchangeVariant::adjustPositions(
{ {
auto score = vstd::contains(reachabilityMap[h], unit) auto score = vstd::contains(reachabilityMap[h], unit)
? reachabilityMap[h].size() ? reachabilityMap[h].size()
: 1000; : 0;
if(unit->doubleWide()) if(unit->doubleWide())
{ {
@ -558,7 +560,7 @@ void BattleExchangeVariant::adjustPositions(
hexes.erase(desiredPosition); hexes.erase(desiredPosition);
} }
if(notRealizedDps > ap.attackValue() && notRealizedDps > attackerValue[ap.attack.attacker->unitId()].value) if(notRealizedDamage > ap.attackValue() && notRealizedDamage > attackerValue[ap.attack.attacker->unitId()].value)
{ {
dpsScore = EvaluationResult::INEFFECTIVE_SCORE; dpsScore = EvaluationResult::INEFFECTIVE_SCORE;
} }
@ -566,9 +568,11 @@ void BattleExchangeVariant::adjustPositions(
void BattleExchangeEvaluator::updateReachabilityMap(HypotheticBattle & hb) void BattleExchangeEvaluator::updateReachabilityMap(HypotheticBattle & hb)
{ {
const int TURN_DEPTH = 2;
turnOrder.clear(); turnOrder.clear();
hb.battleGetTurnOrder(turnOrder, 1000, 2); hb.battleGetTurnOrder(turnOrder, std::numeric_limits<int>::max(), TURN_DEPTH);
reachabilityMap.clear(); reachabilityMap.clear();
for(int turn = 0; turn < turnOrder.size(); turn++) for(int turn = 0; turn < turnOrder.size(); turn++)
@ -618,9 +622,14 @@ void BattleExchangeEvaluator::updateReachabilityMap(HypotheticBattle & hb)
} }
} }
// avoid blocking path for stronger stack by weaker stack
bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb, const battle::Unit * activeUnit, BattleHex position) bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb, const battle::Unit * activeUnit, BattleHex position)
{ {
int blockingScore = 0; const int BLOCKING_THRESHOLD = 70;
const int BLOCKING_OWN_ATTACK_PENALTY = 100;
const int BLOCKING_OWN_MOVE_PENALTY = 1;
float blockingScore = 0;
auto activeUnitDamage = activeUnit->getMinDamage(hb.battleCanShoot(activeUnit)) * activeUnit->getCount(); auto activeUnitDamage = activeUnit->getMinDamage(hb.battleCanShoot(activeUnit)) * activeUnit->getCount();
@ -638,9 +647,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
continue; continue;
auto blockedUnitDamage = unit->getMinDamage(hb.battleCanShoot(unit)) * unit->getCount(); auto blockedUnitDamage = unit->getMinDamage(hb.battleCanShoot(unit)) * unit->getCount();
auto ratio = blockedUnitDamage / (blockedUnitDamage + activeUnitDamage);
if(blockedUnitDamage < activeUnitDamage)
continue;
auto unitReachability = turnBattle.getReachability(unit); auto unitReachability = turnBattle.getReachability(unit);
@ -668,15 +675,15 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
if(!reachable && vstd::contains(reachabilityMap[hex], unit)) if(!reachable && vstd::contains(reachabilityMap[hex], unit))
{ {
blockingScore += enemyUnit ? 100 : 1; blockingScore += ratio * (enemyUnit ? BLOCKING_OWN_ATTACK_PENALTY : BLOCKING_OWN_MOVE_PENALTY);
} }
} }
} }
} }
#if BATTLE_TRACE_LEVEL==1 #if BATTLE_TRACE_LEVEL>=1
logAi->trace("Position %d, blocking score %d", position.hex, blockingScore); logAi->trace("Position %d, blocking score %f", position.hex, blockingScore);
#endif #endif
return blockingScore > 50; return blockingScore > BLOCKING_THRESHOLD;
} }

View File

@ -47,6 +47,12 @@ struct EvaluationResult
} }
}; };
/// <summary>
/// The class represents evaluation of attack value
/// of exchanges between all stacks which can access particular hex
/// starting from initial attack represented by AttackPossibility and further according turn order.
/// Negative score value means we get more demage than deal
/// </summary>
class BattleExchangeVariant class BattleExchangeVariant
{ {
public: public:
@ -55,14 +61,14 @@ public:
{ {
} }
int64_t trackAttack(const AttackPossibility & ap, HypotheticBattle * state); int64_t trackAttack(const AttackPossibility & ap, HypotheticBattle & state);
int64_t trackAttack( int64_t 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,
std::shared_ptr<CBattleInfoCallback> cb, const CBattleInfoCallback & cb,
bool evaluateOnly = false); bool evaluateOnly = false);
int64_t getScore() const { return dpsScore; } int64_t getScore() const { return dpsScore; }

View File

@ -11,11 +11,11 @@
#include "PotentialTargets.h" #include "PotentialTargets.h"
#include "../../lib/CStack.h"//todo: remove #include "../../lib/CStack.h"//todo: remove
PotentialTargets::PotentialTargets(const battle::Unit * attacker, const HypotheticBattle * state) PotentialTargets::PotentialTargets(const battle::Unit * attacker, const HypotheticBattle & state)
{ {
auto attackerInfo = state->battleGetUnitByID(attacker->unitId()); auto attackerInfo = state.battleGetUnitByID(attacker->unitId());
auto reachability = state->getReachability(attackerInfo); auto reachability = state.getReachability(attackerInfo);
auto avHexes = state->battleGetAvailableHexes(reachability, attackerInfo); auto avHexes = state.battleGetAvailableHexes(reachability, attackerInfo);
//FIXME: this should part of battleGetAvailableHexes //FIXME: this should part of battleGetAvailableHexes
bool forceTarget = false; bool forceTarget = false;
@ -25,7 +25,7 @@ PotentialTargets::PotentialTargets(const battle::Unit * attacker, const Hypothet
if(attackerInfo->hasBonusOfType(Bonus::ATTACKS_NEAREST_CREATURE)) if(attackerInfo->hasBonusOfType(Bonus::ATTACKS_NEAREST_CREATURE))
{ {
forceTarget = true; forceTarget = true;
auto nearest = state->getNearestStack(attackerInfo); auto nearest = state.getNearestStack(attackerInfo);
if(nearest.first != nullptr) if(nearest.first != nullptr)
{ {
@ -34,14 +34,14 @@ PotentialTargets::PotentialTargets(const battle::Unit * attacker, const Hypothet
} }
} }
auto aliveUnits = state->battleGetUnitsIf([=](const battle::Unit * unit) auto aliveUnits = state.battleGetUnitsIf([=](const battle::Unit * unit)
{ {
return unit->isValidTarget() && unit->unitId() != attackerInfo->unitId(); return unit->isValidTarget() && unit->unitId() != attackerInfo->unitId();
}); });
for(auto defender : aliveUnits) for(auto defender : aliveUnits)
{ {
if(!forceTarget && !state->battleMatchOwner(attackerInfo, defender)) if(!forceTarget && !state.battleMatchOwner(attackerInfo, defender))
continue; continue;
auto GenerateAttackInfo = [&](bool shooting, BattleHex hex) -> AttackPossibility auto GenerateAttackInfo = [&](bool shooting, BattleHex hex) -> AttackPossibility
@ -61,7 +61,7 @@ PotentialTargets::PotentialTargets(const battle::Unit * attacker, const Hypothet
else else
unreachableEnemies.push_back(defender); unreachableEnemies.push_back(defender);
} }
else if(state->battleCanShoot(attackerInfo, defender->getPosition())) else if(state.battleCanShoot(attackerInfo, defender->getPosition()))
{ {
possibleAttacks.push_back(GenerateAttackInfo(true, BattleHex::INVALID)); possibleAttacks.push_back(GenerateAttackInfo(true, BattleHex::INVALID));
} }
@ -84,22 +84,18 @@ PotentialTargets::PotentialTargets(const battle::Unit * attacker, const Hypothet
boost::sort(possibleAttacks, [](const AttackPossibility & lhs, const AttackPossibility & rhs) -> bool boost::sort(possibleAttacks, [](const AttackPossibility & lhs, const AttackPossibility & rhs) -> bool
{ {
if(lhs.collateralDamage > rhs.collateralDamage) return lhs.damageDiff() > rhs.damageDiff();
return false;
if(lhs.collateralDamage < rhs.collateralDamage)
return true;
return (lhs.damageDealt + lhs.shootersBlockedDmg - lhs.damageReceived > rhs.damageDealt + rhs.shootersBlockedDmg - rhs.damageReceived);
}); });
if (!possibleAttacks.empty()) if (!possibleAttacks.empty())
{ {
auto & bestAp = possibleAttacks[0]; auto & bestAp = possibleAttacks[0];
logGlobal->info("Battle AI best: %s -> %s at %d from %d, affects %d units: %lld %lld %lld %lld", logGlobal->info("Battle AI best: %s -> %s at %d from %d, affects %d units: d:%lld a:%lld c:%lld s:%lld",
bestAp.attack.attacker->unitType()->identifier, bestAp.attack.attacker->unitType()->identifier,
state->battleGetUnitByPos(bestAp.dest)->unitType()->identifier, state.battleGetUnitByPos(bestAp.dest)->unitType()->identifier,
(int)bestAp.dest, (int)bestAp.from, (int)bestAp.affectedUnits.size(), (int)bestAp.dest, (int)bestAp.from, (int)bestAp.affectedUnits.size(),
bestAp.damageDealt, bestAp.damageReceived, bestAp.collateralDamage, bestAp.shootersBlockedDmg); bestAp.defenderDamageReduce, bestAp.attackerDamageReduce, bestAp.collateralDamageReduce, bestAp.shootersBlockedDmg);
} }
} }
@ -107,6 +103,7 @@ int64_t PotentialTargets::bestActionValue() const
{ {
if(possibleAttacks.empty()) if(possibleAttacks.empty())
return 0; return 0;
return bestAction().attackValue(); return bestAction().attackValue();
} }
@ -114,6 +111,6 @@ const AttackPossibility & PotentialTargets::bestAction() const
{ {
if(possibleAttacks.empty()) if(possibleAttacks.empty())
throw std::runtime_error("No best action, since we don't have any actions"); throw std::runtime_error("No best action, since we don't have any actions");
return possibleAttacks.at(0);
//return *vstd::maxElementByFun(possibleAttacks, [](const AttackPossibility &ap) { return ap.attackValue(); } ); return possibleAttacks.front();
} }

View File

@ -17,7 +17,7 @@ public:
std::vector<const battle::Unit *> unreachableEnemies; std::vector<const battle::Unit *> unreachableEnemies;
PotentialTargets(){}; PotentialTargets(){};
PotentialTargets(const battle::Unit * attacker, const HypotheticBattle * state); PotentialTargets(const battle::Unit * attacker, const HypotheticBattle & state);
const AttackPossibility & bestAction() const; const AttackPossibility & bestAction() const;
int64_t bestActionValue() const; int64_t bestActionValue() const;

View File

@ -180,13 +180,14 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
for(auto actorPtr : actors) for(auto actorPtr : actors)
{ {
ChainActor * actor = actorPtr.get(); ChainActor * actor = actorPtr.get();
AIPathNode * initialNode =
getOrCreateNode(actor->initialPosition, actor->layer, actor)
.get();
if(!initialNode) auto allocated = getOrCreateNode(actor->initialPosition, actor->layer, actor);
if(!allocated)
continue; continue;
AIPathNode * initialNode = allocated.get();
initialNode->inPQ = false; initialNode->inPQ = false;
initialNode->pq = nullptr; initialNode->pq = nullptr;
initialNode->turns = actor->initialTurn; initialNode->turns = actor->initialTurn;