From cf3eee5d8ab1732e1f398ae4dbcba1bf4c3de8db Mon Sep 17 00:00:00 2001 From: Xilmi Date: Wed, 22 Jan 2025 16:53:41 +0100 Subject: [PATCH] AI-adjustments AI no longer rushes towns that don't have a citadel or better when there is a scary enemy hero around. AI will no longer try to maximize defenses by using the strongest possible defender. Instead it will try to use the most appropriate defender. The most appropriate is considered to have roughly 75% power of the threat and the score will be lower but still above zero the bigger the deviation is. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 23 +++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index bd686e68e..49f55b91d 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -1077,6 +1077,8 @@ public: evaluationContext.isHero = true; if (target->getOwner().isValidPlayer() && ai->cb->getPlayerRelations(ai->playerID, target->getOwner()) == PlayerRelations::ENEMIES) evaluationContext.isEnemy = true; + if (target->ID == Obj::TOWN) + evaluationContext.defenseValue = dynamic_cast(target)->fortLevel(); evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army); if(evaluationContext.danger > 0) evaluationContext.skillReward += (float)evaluationContext.danger / (float)hero->getArmyStrength(); @@ -1465,6 +1467,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) return 0; if (evaluationContext.movementCost >= 1) return 0; + if (evaluationContext.defenseValue < 2 && evaluationContext.enemyHeroDangerRatio > dangerThreshold) + return 0; if(evaluationContext.conquestValue > 0) score = evaluationContext.armyInvolvement; if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > dangerThreshold && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty())) @@ -1487,13 +1491,30 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) if (evaluationContext.isEnemy && evaluationContext.turn > 0) return 0; if (evaluationContext.isDefend && evaluationContext.threatTurns <= evaluationContext.turn) - score = evaluationContext.armyInvolvement / (evaluationContext.turn + 1); + { + const float OPTIMAL_PERCENTAGE = 0.75f; // We want army to be 75% of the threat + float optimalStrength = evaluationContext.threat * OPTIMAL_PERCENTAGE; + + // Calculate how far the army is from optimal strength + float deviation = std::abs(evaluationContext.armyInvolvement - optimalStrength); + + // Convert deviation to a percentage of the threat to normalize it + float deviationPercentage = deviation / evaluationContext.threat; + + // Calculate score: 1.0 is perfect, decreasing as deviation increases + score = 1.0f / (1.0f + deviationPercentage); + + // Apply turn penalty to still prefer earlier moves when scores are close + score = score / (evaluationContext.turn + 1); + } break; } case PriorityTier::KILL: //Take towns / kill heroes that are further away //FALL_THROUGH case PriorityTier::FAR_KILL: { + if (evaluationContext.defenseValue < 2 && evaluationContext.enemyHeroDangerRatio > dangerThreshold) + return 0; if (evaluationContext.turn > 0 && evaluationContext.isHero) return 0; if (arriveNextWeek && evaluationContext.isEnemy)