mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-22 22:13:35 +02:00
BattleAI: fix bait for archers when need to go long way
This commit is contained in:
parent
b3fc6743d9
commit
8cdfa26fb5
@ -213,7 +213,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
moveTarget.score,
|
||||
moveTarget.scorePerTurn);
|
||||
|
||||
return goTowardsNearest(stack, moveTarget.positions);
|
||||
return goTowardsNearest(stack, moveTarget.positions, *targets);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -235,7 +235,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
if(stack->doubleWide() && vstd::contains(brokenWallMoat, stack->getPosition()))
|
||||
return BattleAction::makeMove(stack, stack->getPosition().cloneInDirection(BattleHex::RIGHT));
|
||||
else
|
||||
return goTowardsNearest(stack, brokenWallMoat);
|
||||
return goTowardsNearest(stack, brokenWallMoat, *targets);
|
||||
}
|
||||
}
|
||||
|
||||
@ -249,7 +249,7 @@ uint64_t timeElapsed(std::chrono::time_point<std::chrono::high_resolution_clock>
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
|
||||
}
|
||||
|
||||
BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes)
|
||||
BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes, const PotentialTargets & targets)
|
||||
{
|
||||
auto reachability = cb->getBattle(battleID)->getReachability(stack);
|
||||
auto avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(reachability, stack, false);
|
||||
@ -271,9 +271,29 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
|
||||
for(auto hex : targetHexes)
|
||||
{
|
||||
if(vstd::contains(avHexes, hex))
|
||||
{
|
||||
auto additionalScore = 0;
|
||||
std::optional<AttackPossibility> attackOnTheWay;
|
||||
|
||||
for(auto & target : targets.possibleAttacks)
|
||||
{
|
||||
if(!target.attack.shooting && target.from == hex && target.attackValue() > additionalScore)
|
||||
{
|
||||
additionalScore = target.attackValue();
|
||||
attackOnTheWay = target;
|
||||
}
|
||||
}
|
||||
|
||||
if(attackOnTheWay)
|
||||
{
|
||||
activeActionMade = true;
|
||||
return BattleAction::makeMeleeAttack(stack, attackOnTheWay->attack.defender->getPosition(), attackOnTheWay->from);
|
||||
}
|
||||
else
|
||||
{
|
||||
return BattleAction::makeMove(stack, hex);
|
||||
}
|
||||
}
|
||||
|
||||
if(stack->coversPos(hex))
|
||||
{
|
||||
|
@ -43,7 +43,7 @@ public:
|
||||
bool attemptCastingSpell(const CStack * stack);
|
||||
bool canCastSpell();
|
||||
std::optional<PossibleSpellcast> findBestCreatureSpell(const CStack * stack);
|
||||
BattleAction goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes);
|
||||
BattleAction goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes, const PotentialTargets & targets);
|
||||
std::vector<BattleHex> getBrokenWallMoatHexes() const;
|
||||
void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only
|
||||
void print(const std::string & text) const;
|
||||
|
@ -277,6 +277,36 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
||||
return result;
|
||||
}
|
||||
|
||||
ReachabilityInfo getReachabilityWithEnemyBypass(
|
||||
const battle::Unit * activeStack,
|
||||
DamageCache & damageCache,
|
||||
std::shared_ptr<HypotheticBattle> state)
|
||||
{
|
||||
ReachabilityInfo::Parameters params(activeStack, activeStack->getPosition());
|
||||
|
||||
if(!params.flying)
|
||||
{
|
||||
for(const auto * unit : state->battleAliveUnits())
|
||||
{
|
||||
if(unit->unitSide() == activeStack->unitSide())
|
||||
continue;
|
||||
|
||||
auto dmg = damageCache.getOriginalDamage(activeStack, unit, state);
|
||||
auto turnsToKill = unit->getAvailableHealth() / dmg + 1;
|
||||
|
||||
vstd::amin(turnsToKill, 100);
|
||||
|
||||
for(auto & hex : unit->getHexes())
|
||||
if(hex.isAvailable()) //towers can have <0 pos; we don't also want to overwrite side columns
|
||||
params.destructibleEnemyTurns[hex] = turnsToKill * unit->getMovementRange();
|
||||
}
|
||||
|
||||
params.bypassEnemyStacks = true;
|
||||
}
|
||||
|
||||
return state->getReachability(params);
|
||||
}
|
||||
|
||||
MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
||||
const battle::Unit * activeStack,
|
||||
PotentialTargets & targets,
|
||||
@ -286,6 +316,8 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
||||
MoveTarget result;
|
||||
BattleExchangeVariant ev;
|
||||
|
||||
logAi->trace("Find move towards unreachable. Enemies count %d", targets.unreachableEnemies.size());
|
||||
|
||||
if(targets.unreachableEnemies.empty())
|
||||
return result;
|
||||
|
||||
@ -296,17 +328,17 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
||||
|
||||
updateReachabilityMap(hb);
|
||||
|
||||
auto dists = cb->getReachability(activeStack);
|
||||
auto dists = getReachabilityWithEnemyBypass(activeStack, damageCache, hb);
|
||||
auto flying = activeStack->hasBonusOfType(BonusType::FLYING);
|
||||
|
||||
for(const battle::Unit * enemy : targets.unreachableEnemies)
|
||||
{
|
||||
std::vector<const battle::Unit *> adjacentStacks = getAdjacentUnits(enemy);
|
||||
auto closestStack = *vstd::minElementByFun(adjacentStacks, [&](const battle::Unit * u) -> int64_t
|
||||
{
|
||||
return dists.distToNearestNeighbour(activeStack, u) * 100000 - activeStack->getTotalHealth();
|
||||
});
|
||||
logAi->trace(
|
||||
"Checking movement towards %d of %s",
|
||||
enemy->getCount(),
|
||||
enemy->creatureId().toCreature()->getNameSingularTranslated());
|
||||
|
||||
auto distance = dists.distToNearestNeighbour(activeStack, closestStack);
|
||||
auto distance = dists.distToNearestNeighbour(activeStack, enemy);
|
||||
|
||||
if(distance >= GameConstants::BFIELD_SIZE)
|
||||
continue;
|
||||
@ -315,30 +347,94 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
||||
continue;
|
||||
|
||||
auto turnsToRich = (distance - 1) / speed + 1;
|
||||
auto hexes = closestStack->getSurroundingHexes();
|
||||
auto enemySpeed = closestStack->getMovementRange();
|
||||
auto hexes = enemy->getSurroundingHexes();
|
||||
auto enemySpeed = enemy->getMovementRange();
|
||||
auto speedRatio = speed / static_cast<float>(enemySpeed);
|
||||
auto multiplier = speedRatio > 1 ? 1 : speedRatio;
|
||||
|
||||
if(enemy->canShoot())
|
||||
multiplier *= 1.5f;
|
||||
|
||||
for(auto hex : hexes)
|
||||
for(auto & hex : hexes)
|
||||
{
|
||||
// FIXME: provide distance info for Jousting bonus
|
||||
auto bai = BattleAttackInfo(activeStack, closestStack, 0, cb->battleCanShoot(activeStack));
|
||||
auto bai = BattleAttackInfo(activeStack, enemy, 0, cb->battleCanShoot(activeStack));
|
||||
auto attack = AttackPossibility::evaluate(bai, hex, damageCache, hb);
|
||||
|
||||
attack.shootersBlockedDmg = 0; // we do not want to count on it, it is not for sure
|
||||
|
||||
auto score = calculateExchange(attack, turnsToRich, targets, damageCache, hb);
|
||||
auto scorePerTurn = BattleScore(score.enemyDamageReduce * std::sqrt(multiplier / turnsToRich), score.ourDamageReduce);
|
||||
auto scorePerTurn = BattleScore(score.enemyDamageReduce * multiplier / turnsToRich, score.ourDamageReduce);
|
||||
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("Multiplier: %f, turns: %d, current score %f, new score %f", multiplier, turnsToRich, result.scorePerTurn, scoreValue(scorePerTurn));
|
||||
#endif
|
||||
|
||||
if(result.scorePerTurn < scoreValue(scorePerTurn))
|
||||
{
|
||||
result.scorePerTurn = scoreValue(scorePerTurn);
|
||||
result.score = scoreValue(score);
|
||||
result.positions = closestStack->getAttackableHexes(activeStack);
|
||||
result.positions.clear();
|
||||
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("New high score");
|
||||
#endif
|
||||
|
||||
for(BattleHex enemyHex : enemy->getAttackableHexes(activeStack))
|
||||
{
|
||||
while(!flying && dists.distances[enemyHex] > speed)
|
||||
{
|
||||
enemyHex = dists.predecessors.at(enemyHex);
|
||||
if(dists.accessibility[enemyHex] == EAccessibility::ALIVE_STACK)
|
||||
{
|
||||
auto defenderToBypass = hb->battleGetUnitByPos(enemyHex);
|
||||
|
||||
if(defenderToBypass)
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("Found target to bypass at %d", enemyHex.hex);
|
||||
#endif
|
||||
|
||||
auto attackHex = dists.predecessors[enemyHex];
|
||||
auto baiBypass = BattleAttackInfo(activeStack, defenderToBypass, 0, cb->battleCanShoot(activeStack));
|
||||
auto attackBypass = AttackPossibility::evaluate(baiBypass, attackHex, damageCache, hb);
|
||||
|
||||
auto adjacentStacks = getAdjacentUnits(enemy);
|
||||
|
||||
adjacentStacks.push_back(defenderToBypass);
|
||||
vstd::removeDuplicates(adjacentStacks);
|
||||
|
||||
auto bypassScore = calculateExchange(
|
||||
attackBypass,
|
||||
dists.distances[attackHex],
|
||||
targets,
|
||||
damageCache,
|
||||
hb,
|
||||
adjacentStacks);
|
||||
|
||||
if(scoreValue(bypassScore) > result.score)
|
||||
{
|
||||
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));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.positions.push_back(enemyHex);
|
||||
}
|
||||
|
||||
result.cachedAttack = attack;
|
||||
result.turnsToRich = turnsToRich;
|
||||
}
|
||||
@ -382,7 +478,8 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
||||
const AttackPossibility & ap,
|
||||
uint8_t turn,
|
||||
PotentialTargets & targets,
|
||||
std::shared_ptr<HypotheticBattle> hb) const
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
std::vector<const battle::Unit *> additionalUnits) const
|
||||
{
|
||||
ReachabilityData result;
|
||||
|
||||
@ -390,7 +487,7 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
||||
|
||||
if(!ap.attack.shooting) hexes.push_back(ap.from);
|
||||
|
||||
std::vector<const battle::Unit *> allReachableUnits;
|
||||
std::vector<const battle::Unit *> allReachableUnits = additionalUnits;
|
||||
|
||||
for(auto hex : hexes)
|
||||
{
|
||||
@ -432,7 +529,7 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
||||
|
||||
for(auto unit : allReachableUnits)
|
||||
{
|
||||
auto accessible = !unit->canShoot();
|
||||
auto accessible = !unit->canShoot() || vstd::contains(additionalUnits, unit);
|
||||
|
||||
if(!accessible)
|
||||
{
|
||||
@ -494,7 +591,8 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
uint8_t turn,
|
||||
PotentialTargets & targets,
|
||||
DamageCache & damageCache,
|
||||
std::shared_ptr<HypotheticBattle> hb) const
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
std::vector<const battle::Unit *> additionalUnits) const
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace("Battle exchange at %d", ap.attack.shooting ? ap.dest.hex : ap.from.hex);
|
||||
@ -513,7 +611,7 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
if(hb->battleGetUnitByID(ap.attack.defender->unitId())->alive())
|
||||
enemyStacks.push_back(ap.attack.defender);
|
||||
|
||||
ReachabilityData exchangeUnits = getExchangeUnits(ap, turn, targets, hb);
|
||||
ReachabilityData exchangeUnits = getExchangeUnits(ap, turn, targets, hb, additionalUnits);
|
||||
|
||||
if(exchangeUnits.units.empty())
|
||||
{
|
||||
|
@ -139,7 +139,8 @@ private:
|
||||
uint8_t turn,
|
||||
PotentialTargets & targets,
|
||||
DamageCache & damageCache,
|
||||
std::shared_ptr<HypotheticBattle> hb) const;
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
std::vector<const battle::Unit *> additionalUnits = {}) const;
|
||||
|
||||
bool canBeHitThisTurn(const AttackPossibility & ap);
|
||||
|
||||
@ -171,7 +172,8 @@ public:
|
||||
const AttackPossibility & ap,
|
||||
uint8_t turn,
|
||||
PotentialTargets & targets,
|
||||
std::shared_ptr<HypotheticBattle> hb) const;
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
std::vector<const battle::Unit *> additionalUnits = {}) const;
|
||||
|
||||
bool checkPositionBlocksOurStacks(HypotheticBattle & hb, const battle::Unit * unit, BattleHex position);
|
||||
|
||||
|
@ -18,9 +18,19 @@ VCMI_LIB_NAMESPACE_BEGIN
|
||||
bool AccessibilityInfo::tileAccessibleWithGate(BattleHex tile, BattleSide side) const
|
||||
{
|
||||
//at(otherHex) != EAccessibility::ACCESSIBLE && (at(otherHex) != EAccessibility::GATE || side != BattleSide::DEFENDER)
|
||||
if(at(tile) != EAccessibility::ACCESSIBLE)
|
||||
if(at(tile) != EAccessibility::GATE || side != BattleSide::DEFENDER)
|
||||
auto accessibility = at(tile);
|
||||
|
||||
if(accessibility == EAccessibility::ALIVE_STACK)
|
||||
{
|
||||
auto destructible = destructibleEnemyTurns.find(tile);
|
||||
|
||||
return destructible != destructibleEnemyTurns.end();
|
||||
}
|
||||
|
||||
if(accessibility != EAccessibility::ACCESSIBLE)
|
||||
if(accessibility != EAccessibility::GATE || side != BattleSide::DEFENDER)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,8 @@ using TAccessibilityArray = std::array<EAccessibility, GameConstants::BFIELD_SIZ
|
||||
|
||||
struct DLL_LINKAGE AccessibilityInfo : TAccessibilityArray
|
||||
{
|
||||
std::map<BattleHex, ui8> destructibleEnemyTurns;
|
||||
|
||||
public:
|
||||
bool accessible(BattleHex tile, const battle::Unit * stack) const; //checks for both tiles if stack is double wide
|
||||
bool accessible(BattleHex tile, bool doubleWide, BattleSide side) const; //checks for both tiles if stack is double wide
|
||||
|
@ -1052,16 +1052,29 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo &accessibi
|
||||
continue;
|
||||
|
||||
const int costToNeighbour = ret.distances[curHex.hex] + 1;
|
||||
|
||||
for(BattleHex neighbour : BattleHex::neighbouringTilesCache[curHex.hex])
|
||||
{
|
||||
if(neighbour.isValid())
|
||||
{
|
||||
auto additionalCost = 0;
|
||||
|
||||
if(params.bypassEnemyStacks)
|
||||
{
|
||||
auto enemyToBypass = params.destructibleEnemyTurns.find(neighbour);
|
||||
|
||||
if(enemyToBypass != params.destructibleEnemyTurns.end())
|
||||
{
|
||||
additionalCost = enemyToBypass->second;
|
||||
}
|
||||
}
|
||||
|
||||
const int costFoundSoFar = ret.distances[neighbour.hex];
|
||||
|
||||
if(accessibleCache[neighbour.hex] && costToNeighbour < costFoundSoFar)
|
||||
if(accessibleCache[neighbour.hex] && costToNeighbour + additionalCost < costFoundSoFar)
|
||||
{
|
||||
hexq.push(neighbour);
|
||||
ret.distances[neighbour.hex] = costToNeighbour;
|
||||
ret.distances[neighbour.hex] = costToNeighbour + additionalCost;
|
||||
ret.predecessors[neighbour.hex] = curHex;
|
||||
}
|
||||
}
|
||||
@ -1236,7 +1249,13 @@ ReachabilityInfo CBattleInfoCallback::getReachability(const ReachabilityInfo::Pa
|
||||
if(params.flying)
|
||||
return getFlyingReachability(params);
|
||||
else
|
||||
return makeBFS(getAccessibility(params.knownAccessible), params);
|
||||
{
|
||||
auto accessibility = getAccessibility(params.knownAccessible);
|
||||
|
||||
accessibility.destructibleEnemyTurns = params.destructibleEnemyTurns;
|
||||
|
||||
return makeBFS(accessibility, params);
|
||||
}
|
||||
}
|
||||
|
||||
ReachabilityInfo CBattleInfoCallback::getFlyingReachability(const ReachabilityInfo::Parameters ¶ms) const
|
||||
|
@ -29,7 +29,9 @@ struct DLL_LINKAGE ReachabilityInfo
|
||||
bool doubleWide = false;
|
||||
bool flying = false;
|
||||
bool ignoreKnownAccessible = false; //Ignore obstacles if it is in accessible hexes
|
||||
bool bypassEnemyStacks = false; // in case of true will count amount of turns needed to kill enemy and thus move forward
|
||||
std::vector<BattleHex> knownAccessible; //hexes that will be treated as accessible, even if they're occupied by stack (by default - tiles occupied by stack we do reachability for, so it doesn't block itself)
|
||||
std::map<BattleHex, ui8> destructibleEnemyTurns; // hom many turns it is needed to kill enemy on specific hex
|
||||
|
||||
BattleHex startPosition; //assumed position of stack
|
||||
BattleSide perspective = BattleSide::ALL_KNOWING; //some obstacles (eg. quicksands) may be invisible for some side
|
||||
|
Loading…
Reference in New Issue
Block a user