From 765658ec83cd9b1a221185199683a1cbf3ae5d25 Mon Sep 17 00:00:00 2001 From: Xilmi Date: Sat, 28 Dec 2024 12:48:13 +0100 Subject: [PATCH] Various Evaluation improvements AI-heroes are reduce their confidence for attacks based on how many of the units they currently have stacks from exist somewhere else in their empire. So for example if an AI has 2 black-drakes while 3 more are waiting in their castle, they act as if their army was only 2/5th as strong as it is. This should make them more likely to first reinforce their army before attacking. Changed the formula of scoring dwelling-upgrades in a way that doesn't overvalue upgrading vs. producing new units so much. AI will no longer be so overconfident when fighting enemy heroes near their own castle. This lead to a lot of horrible trades, especially between different AI which made FFAs way easier in the long run. AI should be better at preventing their enemies finding their towns undefended due to considering enemies that are not immediately in their castle's range too. If only gold is missing but no special-resources the AI will no longer devalue what building it wants to build next and build something cheaper instead. This should lead to AI quicker reaching their tier 7 units and skipping some unnecessary lower tiers on the way to it. --- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 90 ++++++++++++++++++---- AI/Nullkiller/Engine/PriorityEvaluator.h | 1 + 2 files changed, 74 insertions(+), 17 deletions(-) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index d8858c8fb..015c5ad22 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -66,7 +66,8 @@ EvaluationContext::EvaluationContext(const Nullkiller* ai) isArmyUpgrade(false), isHero(false), isEnemy(false), - explorePriority(0) + explorePriority(0), + powerRatio(0) { } @@ -1018,6 +1019,45 @@ public: if(heroRole == HeroRole::MAIN) evaluationContext.heroRole = heroRole; + if (hero) + { + // Assuming Slots() returns a collection of slots with slot.second->getCreatureID() and slot.second->getPower() + float heroPower = 0; + float totalPower = 0; + + // Map to store the aggregated power of creatures by CreatureID + std::map totalPowerByCreatureID; + + // Calculate hero power and total power by CreatureID + for (auto slot : hero->Slots()) + { + int creatureID = slot.second->getCreatureID(); + float slotPower = slot.second->getPower(); + + // Add the power of this slot to the heroPower + heroPower += slotPower; + + // Accumulate the total power for the specific CreatureID + if (totalPowerByCreatureID.find(creatureID) == totalPowerByCreatureID.end()) + { + // First time encountering this CreatureID, retrieve total creatures' power + totalPowerByCreatureID[creatureID] = ai->armyManager->getTotalCreaturesAvailable(creatureID).power; + } + } + + // Calculate total power based on unique CreatureIDs + for (const auto& entry : totalPowerByCreatureID) + { + totalPower += entry.second; + } + + // Compute the power ratio if total power is greater than zero + if (totalPower > 0) + { + evaluationContext.powerRatio = heroPower / totalPower; + } + } + if (target) { evaluationContext.goldReward += evaluationContext.evaluator.getGoldReward(target, hero); @@ -1190,7 +1230,10 @@ public: auto potentialUpgradeValue = evaluationContext.evaluator.getUpgradeArmyReward(buildThis.town, bi); evaluationContext.addNonCriticalStrategicalValue(potentialUpgradeValue / 10000.0f / (float)bi.prerequisitesCount); - evaluationContext.armyReward += potentialUpgradeValue / (float)bi.prerequisitesCount; + if(bi.id.IsDwelling()) + evaluationContext.armyReward += bi.armyStrength - evaluationContext.evaluator.ai->armyManager->evaluateStackPower(bi.baseCreatureID.toCreature(), bi.creatureGrows); + else //This is for prerequisite-buildings + evaluationContext.armyReward += evaluationContext.evaluator.ai->armyManager->evaluateStackPower(bi.baseCreatureID.toCreature(), bi.creatureGrows); } } else if(bi.id == BuildingID::CITADEL || bi.id == BuildingID::CASTLE) @@ -1333,15 +1376,17 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) else { float score = 0; - const bool amIInDanger = ai->cb->getTownsInfo().empty() || (evaluationContext.isDefend && evaluationContext.threatTurns == 0); - const float maxWillingToLose = amIInDanger ? 1 : ai->settings->getMaxArmyLossTarget(); + const bool amIInDanger = ai->cb->getTownsInfo().empty(); + const float maxWillingToLose = amIInDanger ? 1 : ai->settings->getMaxArmyLossTarget() * evaluationContext.powerRatio > 0 ? evaluationContext.powerRatio : 1.0; + float dangerThreshold = 1; + dangerThreshold *= evaluationContext.powerRatio > 0 ? evaluationContext.powerRatio : 1.0; bool arriveNextWeek = false; if (ai->cb->getDate(Date::DAY_OF_WEEK) + evaluationContext.turn > 7 && priorityTier < PriorityTier::FAR_KILL) arriveNextWeek = true; #if NKAI_TRACE_LEVEL >= 2 - logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, army-involvement: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, explorePriority: %d isDefend: %d isEnemy: %d arriveNextWeek: %d", + logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, army-involvement: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, explorePriority: %d isDefend: %d isEnemy: %d arriveNextWeek: %d powerRatio: %f", priorityTier, task->toString(), evaluationContext.armyLossPersentage, @@ -1365,7 +1410,8 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) evaluationContext.explorePriority, evaluationContext.isDefend, evaluationContext.isEnemy, - arriveNextWeek); + arriveNextWeek, + evaluationContext.powerRatio); #endif switch (priorityTier) @@ -1378,7 +1424,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) return 0; if(evaluationContext.conquestValue > 0) score = evaluationContext.armyInvolvement; - if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty())) + if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > dangerThreshold && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty())) return 0; if (maxWillingToLose - evaluationContext.armyLossPersentage < 0) return 0; @@ -1388,7 +1434,11 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } case PriorityTier::INSTADEFEND: //Defend immediately threatened towns { - if (evaluationContext.isDefend && evaluationContext.threatTurns == 0 && evaluationContext.turn == 0) + if (evaluationContext.closestWayRatio < 1.0) + return 0; + if (evaluationContext.isEnemy && evaluationContext.turn > 0) + return 0; + if (evaluationContext.isDefend && evaluationContext.threatTurns <= evaluationContext.turn) score = evaluationContext.armyInvolvement; if (evaluationContext.isEnemy && maxWillingToLose - evaluationContext.armyLossPersentage < 0) return 0; @@ -1404,7 +1454,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) return 0; if (evaluationContext.conquestValue > 0) score = evaluationContext.armyInvolvement; - if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty())) + if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > dangerThreshold && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty())) return 0; if (maxWillingToLose - evaluationContext.armyLossPersentage < 0) return 0; @@ -1417,7 +1467,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) { if (!evaluationContext.isArmyUpgrade) return 0; - if (evaluationContext.enemyHeroDangerRatio > 1) + if (evaluationContext.enemyHeroDangerRatio > dangerThreshold) return 0; if (maxWillingToLose - evaluationContext.armyLossPersentage < 0) return 0; @@ -1430,7 +1480,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } case PriorityTier::HIGH_PRIO_EXPLORE: { - if (evaluationContext.enemyHeroDangerRatio > 1) + if (evaluationContext.enemyHeroDangerRatio > dangerThreshold) return 0; if (evaluationContext.explorePriority != 1) return 0; @@ -1447,17 +1497,17 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) //FALL_THROUGH case PriorityTier::FAR_HUNTER_GATHER: { - if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend) + if (evaluationContext.enemyHeroDangerRatio > dangerThreshold && !evaluationContext.isDefend) return 0; if (evaluationContext.buildingCost.marketValue() > 0) return 0; - if (evaluationContext.isDefend && (evaluationContext.enemyHeroDangerRatio < 1 || evaluationContext.threatTurns > 0 || evaluationContext.turn > 0)) + if (evaluationContext.isDefend && (evaluationContext.enemyHeroDangerRatio < dangerThreshold || evaluationContext.threatTurns > 0 || evaluationContext.turn > 0)) return 0; if (evaluationContext.explorePriority == 3) return 0; if (evaluationContext.isArmyUpgrade) return 0; - if ((evaluationContext.enemyHeroDangerRatio > 0 && arriveNextWeek) || evaluationContext.enemyHeroDangerRatio > 1) + if ((evaluationContext.enemyHeroDangerRatio > 0 && arriveNextWeek) || evaluationContext.enemyHeroDangerRatio > dangerThreshold) return 0; if (maxWillingToLose - evaluationContext.armyLossPersentage < 0) return 0; @@ -1480,7 +1530,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } case PriorityTier::LOW_PRIO_EXPLORE: { - if (evaluationContext.enemyHeroDangerRatio > 1) + if (evaluationContext.enemyHeroDangerRatio > dangerThreshold) return 0; if (evaluationContext.explorePriority != 3) return 0; @@ -1495,7 +1545,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) } case PriorityTier::DEFEND: //Defend whatever if nothing else is to do { - if (evaluationContext.enemyHeroDangerRatio > 1 && evaluationContext.isExchange) + if (evaluationContext.enemyHeroDangerRatio > dangerThreshold && evaluationContext.isExchange) return 0; if (evaluationContext.isDefend || evaluationContext.isArmyUpgrade) score = evaluationContext.armyInvolvement; @@ -1536,9 +1586,15 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier) TResources needed = evaluationContext.buildingCost - resourcesAvailable; needed.positive(); int turnsTo = needed.maxPurchasableCount(income); + bool haveEverythingButGold = true; + for (int i = 0; i < GameConstants::RESOURCE_QUANTITY; i++) + { + if (i != GameResID::GOLD && resourcesAvailable[i] < evaluationContext.buildingCost[i]) + haveEverythingButGold = false; + } if (turnsTo == INT_MAX) return 0; - else + if (!haveEverythingButGold) score /= turnsTo; } } diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index a403ee6bd..326d54c22 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -84,6 +84,7 @@ struct DLL_EXPORT EvaluationContext bool isHero; bool isEnemy; int explorePriority; + float powerRatio; EvaluationContext(const Nullkiller * ai);