diff --git a/AI/BattleAI/BattleEvaluator.cpp b/AI/BattleAI/BattleEvaluator.cpp index d591b8d7d..a9f32affd 100644 --- a/AI/BattleAI/BattleEvaluator.cpp +++ b/AI/BattleAI/BattleEvaluator.cpp @@ -119,6 +119,58 @@ std::vector BattleEvaluator::getBrokenWallMoatHexes() const return result; } +std::vector BattleEvaluator::getCastleHexes() +{ + std::vector result; + + // Loop through all wall parts + + std::vector 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 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 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 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); diff --git a/AI/BattleAI/BattleEvaluator.h b/AI/BattleAI/BattleEvaluator.h index 0f00ffc7c..7385a0809 100644 --- a/AI/BattleAI/BattleEvaluator.h +++ b/AI/BattleAI/BattleEvaluator.h @@ -53,6 +53,8 @@ public: std::optional findBestCreatureSpell(const CStack * stack); BattleAction goTowardsNearest(const CStack * stack, std::vector hexes, const PotentialTargets & targets); std::vector getBrokenWallMoatHexes() const; + static std::vector 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); diff --git a/AI/BattleAI/BattleExchangeVariant.cpp b/AI/BattleAI/BattleExchangeVariant.cpp index 07576eb1f..357df8f70 100644 --- a/AI/BattleAI/BattleExchangeVariant.cpp +++ b/AI/BattleAI/BattleExchangeVariant.cpp @@ -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 hb) + std::shared_ptr hb, + bool siegeDefense) { EvaluationResult result(targets.bestAction()); + std::vector 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::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(enemySpeed); - auto multiplier = speedRatio > 1 ? 1 : speedRatio; + auto multiplier = (speedRatio > 1 ? 1 : speedRatio) * penaltyMultiplier; for(auto & hex : hexes) { diff --git a/AI/BattleAI/BattleExchangeVariant.h b/AI/BattleAI/BattleExchangeVariant.h index 7ba3886df..dbbbaab3d 100644 --- a/AI/BattleAI/BattleExchangeVariant.h +++ b/AI/BattleAI/BattleExchangeVariant.h @@ -159,7 +159,8 @@ public: const battle::Unit * activeStack, PotentialTargets & targets, DamageCache & damageCache, - std::shared_ptr hb); + std::shared_ptr hb, + bool siegeDefense = false); float evaluateExchange( const AttackPossibility & ap,