mirror of
https://github.com/vcmi/vcmi.git
synced 2025-03-17 20:58:07 +02:00
Battle-AI-improvements
When defending the AI is now much smarter to use their defensive-structures like walls, towers and the moat to their advantage instead of allowing them to be lured out and killed in the open. A penalty-multiplier is now applied when deciding which units to walk towards. If an ally is closer than us to the enemy unit in question, we reduce our score for walking towards that unit too. This shall help against baiting a whole flock of AI-stacks to overcommit on chasing an inferior stack of the enemy.
This commit is contained in:
parent
3f073507a1
commit
df21a77857
@ -119,6 +119,58 @@ std::vector<BattleHex> BattleEvaluator::getBrokenWallMoatHexes() const
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<BattleHex> BattleEvaluator::getCastleHexes()
|
||||
{
|
||||
std::vector<BattleHex> result;
|
||||
|
||||
// Loop through all wall parts
|
||||
|
||||
std::vector<BattleHex> wallHexes;
|
||||
wallHexes.push_back(50);
|
||||
wallHexes.push_back(183);
|
||||
wallHexes.push_back(182);
|
||||
wallHexes.push_back(130);
|
||||
wallHexes.push_back(78);
|
||||
wallHexes.push_back(29);
|
||||
wallHexes.push_back(12);
|
||||
wallHexes.push_back(97);
|
||||
wallHexes.push_back(45);
|
||||
wallHexes.push_back(62);
|
||||
wallHexes.push_back(112);
|
||||
wallHexes.push_back(147);
|
||||
wallHexes.push_back(165);
|
||||
|
||||
for (BattleHex wallHex : wallHexes) {
|
||||
// Get the starting x-coordinate of the wall hex
|
||||
int startX = wallHex.getX();
|
||||
|
||||
// Initialize current hex with the wall hex
|
||||
BattleHex currentHex = wallHex;
|
||||
while (currentHex.isValid()) {
|
||||
// Check if the x-coordinate has wrapped (smaller than the starting x)
|
||||
if (currentHex.getX() < startX) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Add the hex to the result
|
||||
result.push_back(currentHex);
|
||||
|
||||
// Move to the next hex to the right
|
||||
currentHex = currentHex.cloneInDirection(BattleHex::RIGHT, false);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool BattleEvaluator::hasWorkingTowers() const
|
||||
{
|
||||
bool keepIntact = cb->getBattle(battleID)->battleGetWallState(EWallPart::KEEP) != EWallState::NONE && cb->getBattle(battleID)->battleGetWallState(EWallPart::KEEP) != EWallState::DESTROYED;
|
||||
bool upperIntact = cb->getBattle(battleID)->battleGetWallState(EWallPart::UPPER_TOWER) != EWallState::NONE && cb->getBattle(battleID)->battleGetWallState(EWallPart::UPPER_TOWER) != EWallState::DESTROYED;
|
||||
bool bottomIntact = cb->getBattle(battleID)->battleGetWallState(EWallPart::BOTTOM_TOWER) != EWallState::NONE && cb->getBattle(battleID)->battleGetWallState(EWallPart::BOTTOM_TOWER) != EWallState::DESTROYED;
|
||||
return keepIntact || upperIntact || bottomIntact;
|
||||
}
|
||||
|
||||
std::optional<PossibleSpellcast> BattleEvaluator::findBestCreatureSpell(const CStack *stack)
|
||||
{
|
||||
//TODO: faerie dragon type spell should be selected by server
|
||||
@ -161,6 +213,19 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
|
||||
auto moveTarget = scoreEvaluator.findMoveTowardsUnreachable(stack, *targets, damageCache, hb);
|
||||
float score = EvaluationResult::INEFFECTIVE_SCORE;
|
||||
auto enemyMellee = hb->getUnitsIf([this](const battle::Unit* u) -> bool
|
||||
{
|
||||
return u->unitSide() == BattleSide::ATTACKER && !hb->battleCanShoot(u);
|
||||
});
|
||||
bool siegeDefense = stack->unitSide() == BattleSide::DEFENDER
|
||||
&& !stack->canShoot()
|
||||
&& hasWorkingTowers()
|
||||
&& !enemyMellee.empty();
|
||||
std::vector<BattleHex> castleHexes = getCastleHexes();
|
||||
for (auto hex : castleHexes)
|
||||
{
|
||||
logAi->trace("Castlehex ID: %d Y: %d X: %d", hex, hex.getY(), hex.getX());
|
||||
}
|
||||
|
||||
if(targets->possibleAttacks.empty() && bestSpellcast.has_value())
|
||||
{
|
||||
@ -174,7 +239,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
logAi->trace("Evaluating attack for %s", stack->getDescription());
|
||||
#endif
|
||||
|
||||
auto evaluationResult = scoreEvaluator.findBestTarget(stack, *targets, damageCache, hb);
|
||||
auto evaluationResult = scoreEvaluator.findBestTarget(stack, *targets, damageCache, hb, siegeDefense);
|
||||
auto & bestAttack = evaluationResult.bestAttack;
|
||||
|
||||
cachedAttack.ap = bestAttack;
|
||||
@ -227,15 +292,13 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
return BattleAction::makeDefend(stack);
|
||||
}
|
||||
|
||||
auto enemyMellee = hb->getUnitsIf([this](const battle::Unit * u) -> bool
|
||||
{
|
||||
return u->unitSide() == BattleSide::ATTACKER && !hb->battleCanShoot(u);
|
||||
bool isTargetOutsideFort = std::none_of(castleHexes.begin(), castleHexes.end(),
|
||||
[&](const BattleHex& hex) {
|
||||
return hex == bestAttack.from;
|
||||
});
|
||||
|
||||
bool isTargetOutsideFort = bestAttack.dest.getY() < GameConstants::BFIELD_WIDTH - 4;
|
||||
bool siegeDefense = stack->unitSide() == BattleSide::DEFENDER
|
||||
&& !bestAttack.attack.shooting
|
||||
&& hb->battleGetFortifications().hasMoat
|
||||
&& hasWorkingTowers()
|
||||
&& !enemyMellee.empty()
|
||||
&& isTargetOutsideFort;
|
||||
|
||||
@ -349,6 +412,28 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
|
||||
auto reachability = cb->getBattle(battleID)->getReachability(stack);
|
||||
auto avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(reachability, stack, false);
|
||||
|
||||
auto enemyMellee = hb->getUnitsIf([this](const battle::Unit* u) -> bool
|
||||
{
|
||||
return u->unitSide() == BattleSide::ATTACKER && !hb->battleCanShoot(u);
|
||||
});
|
||||
|
||||
bool siegeDefense = stack->unitSide() == BattleSide::DEFENDER
|
||||
&& hasWorkingTowers()
|
||||
&& !enemyMellee.empty();
|
||||
|
||||
if (siegeDefense)
|
||||
{
|
||||
vstd::erase_if(avHexes, [&](const BattleHex& hex) {
|
||||
std::vector<BattleHex> castleHexes = getCastleHexes();
|
||||
|
||||
bool isOutsideWall = std::none_of(castleHexes.begin(), castleHexes.end(),
|
||||
[&](const BattleHex& checkhex) {
|
||||
return checkhex == hex;
|
||||
});
|
||||
return isOutsideWall;
|
||||
});
|
||||
}
|
||||
|
||||
if(!avHexes.size() || !hexes.size()) //we are blocked or dest is blocked
|
||||
{
|
||||
return BattleAction::makeDefend(stack);
|
||||
|
@ -53,6 +53,8 @@ public:
|
||||
std::optional<PossibleSpellcast> findBestCreatureSpell(const CStack * stack);
|
||||
BattleAction goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes, const PotentialTargets & targets);
|
||||
std::vector<BattleHex> getBrokenWallMoatHexes() const;
|
||||
static std::vector<BattleHex> getCastleHexes();
|
||||
bool hasWorkingTowers() const;
|
||||
void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only
|
||||
void print(const std::string & text) const;
|
||||
BattleAction moveOrAttack(const CStack * stack, BattleHex hex, const PotentialTargets & targets);
|
||||
|
@ -9,6 +9,7 @@
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "BattleExchangeVariant.h"
|
||||
#include "BattleEvaluator.h"
|
||||
#include "../../lib/CStack.h"
|
||||
|
||||
AttackerValue::AttackerValue()
|
||||
@ -213,9 +214,11 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
||||
const battle::Unit * activeStack,
|
||||
PotentialTargets & targets,
|
||||
DamageCache & damageCache,
|
||||
std::shared_ptr<HypotheticBattle> hb)
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
bool siegeDefense)
|
||||
{
|
||||
EvaluationResult result(targets.bestAction());
|
||||
std::vector<BattleHex> castleHexes = BattleEvaluator::getCastleHexes();
|
||||
|
||||
if(!activeStack->waited() && !activeStack->acquireState()->hadMorale)
|
||||
{
|
||||
@ -231,6 +234,9 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
||||
|
||||
for(auto & ap : targets.possibleAttacks)
|
||||
{
|
||||
if (siegeDefense && std::find(castleHexes.begin(), castleHexes.end(), ap.from) == castleHexes.end())
|
||||
continue;
|
||||
|
||||
float score = evaluateExchange(ap, 0, targets, damageCache, hbWaited);
|
||||
|
||||
if(score > result.score)
|
||||
@ -263,6 +269,9 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
||||
|
||||
for(auto & ap : targets.possibleAttacks)
|
||||
{
|
||||
if (siegeDefense && std::find(castleHexes.begin(), castleHexes.end(), ap.from) == castleHexes.end())
|
||||
continue;
|
||||
|
||||
float score = evaluateExchange(ap, 0, targets, damageCache, hb);
|
||||
bool sameScoreButWaited = vstd::isAlmostEqual(score, result.score) && result.wait;
|
||||
|
||||
@ -350,11 +359,32 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
||||
if(distance <= speed)
|
||||
continue;
|
||||
|
||||
float penaltyMultiplier = 1.0f; // Default multiplier, no penalty
|
||||
float closestAllyDistance = std::numeric_limits<float>::max();
|
||||
|
||||
for (const battle::Unit* ally : hb->battleAliveUnits()) {
|
||||
if (ally == activeStack)
|
||||
continue;
|
||||
if (ally->unitSide() != activeStack->unitSide())
|
||||
continue;
|
||||
|
||||
float allyDistance = dists.distToNearestNeighbour(ally, enemy);
|
||||
if (allyDistance < closestAllyDistance)
|
||||
{
|
||||
closestAllyDistance = allyDistance;
|
||||
}
|
||||
}
|
||||
|
||||
// If an ally is closer to the enemy, compute the penaltyMultiplier
|
||||
if (closestAllyDistance < distance) {
|
||||
penaltyMultiplier = closestAllyDistance / distance; // Ratio of distances
|
||||
}
|
||||
|
||||
auto turnsToRich = (distance - 1) / speed + 1;
|
||||
auto hexes = enemy->getSurroundingHexes();
|
||||
auto enemySpeed = enemy->getMovementRange();
|
||||
auto speedRatio = speed / static_cast<float>(enemySpeed);
|
||||
auto multiplier = speedRatio > 1 ? 1 : speedRatio;
|
||||
auto multiplier = (speedRatio > 1 ? 1 : speedRatio) * penaltyMultiplier;
|
||||
|
||||
for(auto & hex : hexes)
|
||||
{
|
||||
|
@ -159,7 +159,8 @@ public:
|
||||
const battle::Unit * activeStack,
|
||||
PotentialTargets & targets,
|
||||
DamageCache & damageCache,
|
||||
std::shared_ptr<HypotheticBattle> hb);
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
bool siegeDefense = false);
|
||||
|
||||
float evaluateExchange(
|
||||
const AttackPossibility & ap,
|
||||
|
Loading…
x
Reference in New Issue
Block a user