diff --git a/AI/BattleAI/AttackPossibility.cpp b/AI/BattleAI/AttackPossibility.cpp index 5e3f4b022..e2fb50dc7 100644 --- a/AI/BattleAI/AttackPossibility.cpp +++ b/AI/BattleAI/AttackPossibility.cpp @@ -201,6 +201,8 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg( if(attackInfo.shooting) return 0; + std::set checkedUnits; + auto attacker = attackInfo.attacker; auto hexes = attacker->getSurroundingHexes(hex); for(BattleHex tile : hexes) @@ -208,9 +210,13 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg( auto st = state->battleGetUnitByPos(tile, true); if(!st || !state->battleMatchOwner(st, attacker)) continue; + if(vstd::contains(checkedUnits, st->unitId())) + continue; if(!state->battleCanShoot(st)) continue; + checkedUnits.insert(st->unitId()); + // FIXME: provide distance info for Jousting bonus BattleAttackInfo rangeAttackInfo(st, attacker, 0, true); rangeAttackInfo.defenderPos = hex; @@ -220,9 +226,10 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg( auto rangeDmg = state->battleEstimateDamage(rangeAttackInfo); auto meleeDmg = state->battleEstimateDamage(meleeAttackInfo); + auto cachedDmg = damageCache.getOriginalDamage(st, attacker, state); int64_t gain = averageDmg(rangeDmg.damage) - averageDmg(meleeDmg.damage) + 1; - res += gain; + res += gain * cachedDmg / std::max(1, averageDmg(rangeDmg.damage)); } return res; diff --git a/AI/BattleAI/BattleEvaluator.cpp b/AI/BattleAI/BattleEvaluator.cpp index 5fdf05f49..3bb731692 100644 --- a/AI/BattleAI/BattleEvaluator.cpp +++ b/AI/BattleAI/BattleEvaluator.cpp @@ -167,7 +167,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack) score ); - if (moveTarget.scorePerTurn <= score) + if (moveTarget.score <= score) { if(evaluationResult.wait) { @@ -197,7 +197,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack) } //ThreatMap threatsToUs(stack); // These lines may be useful but they are't used in the code. - if(moveTarget.scorePerTurn > score) + if(moveTarget.score > score) { score = moveTarget.score; cachedAttack = moveTarget.cachedAttack; @@ -206,12 +206,11 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack) if(stack->waited()) { logAi->debug( - "Moving %s towards hex %s[%d], score: %2f/%2f", + "Moving %s towards hex %s[%d], score: %2f", stack->getDescription(), moveTarget.cachedAttack->attack.defender->getDescription(), moveTarget.cachedAttack->attack.defender->getPosition().hex, - moveTarget.score, - moveTarget.scorePerTurn); + moveTarget.score); return goTowardsNearest(stack, moveTarget.positions, *targets); } diff --git a/AI/BattleAI/BattleExchangeVariant.cpp b/AI/BattleAI/BattleExchangeVariant.cpp index 758d732d2..cf23f604e 100644 --- a/AI/BattleAI/BattleExchangeVariant.cpp +++ b/AI/BattleAI/BattleExchangeVariant.cpp @@ -18,7 +18,7 @@ AttackerValue::AttackerValue() } MoveTarget::MoveTarget() - : positions(), cachedAttack(), score(EvaluationResult::INEFFECTIVE_SCORE), scorePerTurn(EvaluationResult::INEFFECTIVE_SCORE) + : positions(), cachedAttack(), score(EvaluationResult::INEFFECTIVE_SCORE) { turnsToRich = 1; } @@ -292,7 +292,7 @@ ReachabilityInfo getReachabilityWithEnemyBypass( continue; auto dmg = damageCache.getOriginalDamage(activeStack, unit, state); - auto turnsToKill = unit->getAvailableHealth() / dmg + 1; + auto turnsToKill = unit->getAvailableHealth() / dmg; vstd::amin(turnsToKill, 100); @@ -364,15 +364,16 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable( attack.shootersBlockedDmg = 0; // we do not want to count on it, it is not for sure auto score = calculateExchange(attack, turnsToRich, targets, damageCache, hb); - auto scorePerTurn = BattleScore(score.enemyDamageReduce * multiplier / turnsToRich, score.ourDamageReduce); + + score.enemyDamageReduce *= multiplier; #if BATTLE_TRACE_LEVEL >= 1 - logAi->trace("Multiplier: %f, turns: %d, current score %f, new score %f", multiplier, turnsToRich, result.scorePerTurn, scoreValue(scorePerTurn)); + logAi->trace("Multiplier: %f, turns: %d, current score %f, new score %f", multiplier, turnsToRich, result.score, scoreValue(score)); #endif - if(result.scorePerTurn < scoreValue(scorePerTurn)) + if(result.score < scoreValue(score) + || (result.turnsToRich > turnsToRich && vstd::isAlmostEqual(result.score, scoreValue(score)))) { - result.scorePerTurn = scoreValue(scorePerTurn); result.score = scoreValue(score); result.positions.clear(); @@ -414,18 +415,10 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable( if(scoreValue(bypassScore) > result.score) { - auto newMultiplier = multiplier * speed * turnsToRich / dists.distances[attackHex]; - result.score = scoreValue(bypassScore); - scorePerTurn = BattleScore( - score.enemyDamageReduce * newMultiplier, - score.ourDamageReduce); - - result.scorePerTurn = scoreValue(scorePerTurn); - #if BATTLE_TRACE_LEVEL >= 1 - logAi->trace("New high score after bypass %f", scoreValue(scorePerTurn)); + logAi->trace("New high score after bypass %f", scoreValue(bypassScore)); #endif } } @@ -488,12 +481,25 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits( if(!ap.attack.shooting) hexes.push_back(ap.from); std::vector allReachableUnits = additionalUnits; - + for(auto hex : hexes) { vstd::concatenate(allReachableUnits, turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex)); } + for(auto hex : ap.attack.attacker->getHexes()) + { + auto unitsReachingAttacker = turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex); + for(auto unit : unitsReachingAttacker) + { + if(unit->unitSide() != ap.attack.attacker->unitSide()) + { + allReachableUnits.push_back(unit); + result.enemyUnitsReachingAttacker.insert(unit->unitId()); + } + } + } + vstd::removeDuplicates(allReachableUnits); auto copy = allReachableUnits; @@ -621,9 +627,9 @@ BattleScore BattleExchangeEvaluator::calculateExchange( auto exchangeBattle = std::make_shared(env.get(), hb); BattleExchangeVariant v; - for(int turn = 0; turn < exchangeUnits.units.size(); turn++) + for(int exchangeTurn = 0; exchangeTurn < exchangeUnits.units.size(); exchangeTurn++) { - for(auto unit : exchangeUnits.units.at(turn)) + for(auto unit : exchangeUnits.units.at(exchangeTurn)) { if(unit->isTurret()) continue; @@ -652,16 +658,26 @@ BattleScore BattleExchangeEvaluator::calculateExchange( }); bool canUseAp = true; + const int totalTurnsCount = 10; - for(int turn = 0; turn < exchangeUnits.units.size(); turn++) + std::set blockedShooters; + + for(int exchangeTurn = 0; exchangeTurn < totalTurnsCount; exchangeTurn++) { - for(auto activeUnit : exchangeUnits.units.at(turn)) + bool isMovingTurm = exchangeTurn < turn; + int queueTurn = exchangeTurn >= exchangeUnits.units.size() + ? exchangeUnits.units.size() - 1 + : exchangeTurn; + + 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()) { @@ -672,10 +688,23 @@ BattleScore BattleExchangeEvaluator::calculateExchange( continue; } + if(isMovingTurm && !shooting + && !vstd::contains(exchangeUnits.enemyUnitsReachingAttacker, attacker->unitId())) + { +#if BATTLE_TRACE_LEVEL>=1 + logAi->trace("Attacker is moving"); +#endif + + continue; + } + auto targetUnit = ap.attack.defender; if(!isOur || !exchangeBattle->battleGetUnitByID(targetUnit->unitId())->alive()) { +#if BATTLE_TRACE_LEVEL>=2 + logAi->trace("Best target selector for %s", attacker->getDescription()); +#endif auto estimateAttack = [&](const battle::Unit * u) -> float { auto stackWithBonuses = exchangeBattle->getForUpdate(u->unitId()); @@ -688,7 +717,7 @@ BattleScore BattleExchangeEvaluator::calculateExchange( hb, true); -#if BATTLE_TRACE_LEVEL>=1 +#if BATTLE_TRACE_LEVEL>=2 logAi->trace("Best target selector %s->%s score = %2f", attacker->getDescription(), stackWithBonuses->getDescription(), score); #endif @@ -741,7 +770,6 @@ BattleScore BattleExchangeEvaluator::calculateExchange( } auto defender = exchangeBattle->getForUpdate(targetUnit->unitId()); - auto shooting = exchangeBattle->battleCanShoot(attacker.get()); const int totalAttacks = attacker->getTotalAttacks(shooting); if(canUseAp && activeUnit->unitId() == ap.attack.attacker->unitId() @@ -760,6 +788,9 @@ BattleScore BattleExchangeEvaluator::calculateExchange( } } + if(!shooting) + blockedShooters.insert(defender->unitId()); + canUseAp = false; vstd::erase_if(attackerQueue, [&](const battle::Unit * u) -> bool @@ -785,11 +816,20 @@ BattleScore BattleExchangeEvaluator::calculateExchange( for(auto hex : hexes) reachabilityMap[hex] = getOneTurnReachableUnits(turn, hex); + auto score = v.getScore(); + + if(turn > 0) + { + auto turnMultiplier = 1 - std::min(0.2, 0.05 * turn); + + score.enemyDamageReduce *= turnMultiplier; + } + #if BATTLE_TRACE_LEVEL>=1 - logAi->trace("Exchange score: enemy: %2f, our -%2f", v.getScore().enemyDamageReduce, v.getScore().ourDamageReduce); + logAi->trace("Exchange score: enemy: %2f, our -%2f", score.enemyDamageReduce, score.ourDamageReduce); #endif - return v.getScore(); + return score; } bool BattleExchangeEvaluator::canBeHitThisTurn(const AttackPossibility & ap) diff --git a/AI/BattleAI/BattleExchangeVariant.h b/AI/BattleAI/BattleExchangeVariant.h index e3d8f95d7..30edddfe0 100644 --- a/AI/BattleAI/BattleExchangeVariant.h +++ b/AI/BattleAI/BattleExchangeVariant.h @@ -54,7 +54,6 @@ struct AttackerValue struct MoveTarget { float score; - float scorePerTurn; std::vector positions; std::optional cachedAttack; uint8_t turnsToRich; @@ -64,7 +63,7 @@ struct MoveTarget struct EvaluationResult { - static const int64_t INEFFECTIVE_SCORE = -10000; + static const int64_t INEFFECTIVE_SCORE = -100000000; AttackPossibility bestAttack; MoveTarget bestMove; @@ -120,6 +119,8 @@ struct ReachabilityData // far shooters std::vector shooters; + + std::set enemyUnitsReachingAttacker; }; class BattleExchangeEvaluator diff --git a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp index a929dfa90..cd9c4806d 100644 --- a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp @@ -89,6 +89,14 @@ void DangerHitMapAnalyzer::updateHitMap() heroes[hero->tempOwner][hero] = HeroRole::MAIN; } + + if(obj->ID == Obj::TOWN) + { + auto town = dynamic_cast(obj); + + if(town->garrisonHero) + heroes[town->garrisonHero->tempOwner][town->garrisonHero] = HeroRole::MAIN; + } } auto ourTowns = cb->getTownsInfo();