PriorityEvaluator.cpp 58 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703
  1. /*
  2. * PriorityEvaluator.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include <limits>
  12. #include "Nullkiller.h"
  13. #include "../../../lib/entities/artifact/CArtifact.h"
  14. #include "../../../lib/entities/ResourceTypeHandler.h"
  15. #include "../../../lib/mapObjectConstructors/AObjectTypeHandler.h"
  16. #include "../../../lib/mapObjectConstructors/CObjectClassesHandler.h"
  17. #include "../../../lib/mapObjects/CGResource.h"
  18. #include "../../../lib/mapping/TerrainTile.h"
  19. #include "../../../lib/RoadHandler.h"
  20. #include "../../../lib/CCreatureHandler.h"
  21. #include "../../../lib/GameLibrary.h"
  22. #include "../../../lib/StartInfo.h"
  23. #include "../../../lib/GameSettings.h"
  24. #include "../../../lib/filesystem/Filesystem.h"
  25. #include "../Goals/ExecuteHeroChain.h"
  26. #include "../Goals/BuildThis.h"
  27. #include "../Goals/StayAtTown.h"
  28. #include "../Goals/ExchangeSwapTownHeroes.h"
  29. #include "../Goals/DismissHero.h"
  30. #include "../Markers/UnlockCluster.h"
  31. #include "../Markers/HeroExchange.h"
  32. #include "../Markers/ArmyUpgrade.h"
  33. #include "../Markers/DefendTown.h"
  34. namespace NK2AI
  35. {
  36. constexpr float MAX_CRITICAL_VALUE = 2.0f;
  37. EvaluationContext::EvaluationContext(const Nullkiller* aiNk)
  38. : movementCost(0.0),
  39. manaCost(0),
  40. danger(0),
  41. closestWayRatio(1),
  42. movementCostByRole(),
  43. skillReward(0),
  44. goldReward(0),
  45. goldCost(0),
  46. armyReward(0),
  47. armyLossRatio(0),
  48. heroRole(HeroRole::SCOUT),
  49. turn(0),
  50. strategicalValue(0),
  51. conquestValue(0),
  52. evaluator(aiNk),
  53. enemyHeroDangerRatio(0),
  54. threat(0),
  55. armyGrowth(0),
  56. armyInvolvement(0),
  57. defenseValue(0),
  58. isDefend(false),
  59. threatTurns(INT_MAX),
  60. involvesSailing(false),
  61. isTradeBuilding(false),
  62. isExchange(false),
  63. isArmyUpgrade(false),
  64. isHero(false),
  65. isEnemy(false),
  66. explorePriority(0),
  67. powerRatio(0)
  68. {
  69. }
  70. void EvaluationContext::addNonCriticalStrategicalValue(float value)
  71. {
  72. vstd::amax(strategicalValue, std::min(value, MAX_CRITICAL_VALUE));
  73. }
  74. PriorityEvaluator::~PriorityEvaluator()
  75. {
  76. delete engine;
  77. }
  78. void PriorityEvaluator::initVisitTile()
  79. {
  80. auto file = CResourceHandler::get()->load(ResourcePath("config/ai/nk2ai/object-priorities.txt"))->readAll();
  81. std::string str = std::string((char *)file.first.get(), file.second);
  82. engine = fl::FllImporter().fromString(str);
  83. armyLossRatioVariable = engine->getInputVariable("armyLoss");
  84. armyGrowthVariable = engine->getInputVariable("armyGrowth");
  85. heroRoleVariable = engine->getInputVariable("heroRole");
  86. dangerVariable = engine->getInputVariable("danger");
  87. turnVariable = engine->getInputVariable("turn");
  88. mainTurnDistanceVariable = engine->getInputVariable("mainTurnDistance");
  89. scoutTurnDistanceVariable = engine->getInputVariable("scoutTurnDistance");
  90. goldRewardVsMovementVariable = engine->getInputVariable("goldReward");
  91. armyRewardVariable = engine->getInputVariable("armyReward");
  92. skillRewardVariable = engine->getInputVariable("skillReward");
  93. rewardTypeVariable = engine->getInputVariable("rewardType");
  94. closestHeroRatioVariable = engine->getInputVariable("closestHeroRatio");
  95. strategicalValueVariable = engine->getInputVariable("strategicalValue");
  96. goldPressureVariable = engine->getInputVariable("goldPressure");
  97. goldCostVariable = engine->getInputVariable("goldCost");
  98. fearVariable = engine->getInputVariable("fear");
  99. value = engine->getOutputVariable("Value");
  100. }
  101. bool isAnotherAi(const CGObjectInstance * obj, const CPlayerSpecificInfoCallback & cb)
  102. {
  103. return obj->getOwner().isValidPlayer()
  104. && cb.getStartInfo()->getIthPlayersSettings(obj->getOwner()).isControlledByAI();
  105. }
  106. int32_t estimateTownIncome(CCallback * cb, const CGObjectInstance * target, const CGHeroInstance * hero)
  107. {
  108. auto relations = cb->getPlayerRelations(hero->tempOwner, target->tempOwner);
  109. if(relations != PlayerRelations::ENEMIES)
  110. return 0; // if we already own it, no additional reward will be received by just visiting it
  111. auto booster = isAnotherAi(target, *cb) ? 1 : 2;
  112. auto town = cb->getTown(target->id);
  113. auto fortLevel = town->fortLevel();
  114. if(town->hasCapitol())
  115. return booster * 2000;
  116. // probably well developed town will have city hall
  117. if(fortLevel == CGTownInstance::CASTLE) return booster * 750;
  118. return booster * (town->hasFort() && town->tempOwner != PlayerColor::NEUTRAL ? booster * 500 : 250);
  119. }
  120. int32_t getResourcesGoldReward(const TResources & res)
  121. {
  122. int32_t result = 0;
  123. for(auto r : LIBRARY->resourceTypeHandler->getAllObjects())
  124. {
  125. if(res[r] > 0)
  126. result += r == EGameResID::GOLD ? res[r] : res[r] * 100;
  127. }
  128. return result;
  129. }
  130. uint64_t getDwellingArmyValue(CCallback * cb, const CGObjectInstance * target, bool checkGold)
  131. {
  132. auto dwelling = dynamic_cast<const CGDwelling *>(target);
  133. uint64_t score = 0;
  134. for(auto & creLevel : dwelling->creatures)
  135. {
  136. if(creLevel.first && creLevel.second.size())
  137. {
  138. auto creature = creLevel.second.back().toCreature();
  139. auto creaturesAreFree = creature->getLevel() == 1;
  140. if(!creaturesAreFree && checkGold && !cb->getResourceAmount().canAfford(creature->getFullRecruitCost() * creLevel.first))
  141. continue;
  142. score += creature->getAIValue() * creLevel.first;
  143. }
  144. }
  145. return score;
  146. }
  147. uint64_t getDwellingArmyGrowth(CCallback * cb, const CGObjectInstance * target, PlayerColor myColor)
  148. {
  149. auto dwelling = dynamic_cast<const CGDwelling *>(target);
  150. uint64_t score = 0;
  151. if(dwelling->getOwner() == myColor)
  152. return 0;
  153. for(auto & creLevel : dwelling->creatures)
  154. {
  155. if(creLevel.second.size())
  156. {
  157. auto creature = creLevel.second.back().toCreature();
  158. score += creature->getAIValue() * creature->getGrowth();
  159. }
  160. }
  161. return score;
  162. }
  163. int getDwellingArmyCost(const CGObjectInstance * target)
  164. {
  165. auto dwelling = dynamic_cast<const CGDwelling *>(target);
  166. int cost = 0;
  167. for(auto & creLevel : dwelling->creatures)
  168. {
  169. if(creLevel.first && creLevel.second.size())
  170. {
  171. auto creature = creLevel.second.back().toCreature();
  172. auto creaturesAreFree = creature->getLevel() == 1;
  173. if(!creaturesAreFree)
  174. cost += creature->getFullRecruitCost().marketValue() * creLevel.first;
  175. }
  176. }
  177. return cost;
  178. }
  179. static uint64_t evaluateSpellScrollArmyValue(const SpellID &)
  180. {
  181. return 1500;
  182. }
  183. static uint64_t evaluateArtifactArmyValue(const CArtifact * art)
  184. {
  185. if(art->getId() == ArtifactID::SPELL_SCROLL)
  186. return 1500;
  187. return getPotentialArtifactScore(art);
  188. }
  189. uint64_t RewardEvaluator::getArmyReward(
  190. const CGObjectInstance * target,
  191. const CGHeroInstance * hero,
  192. const CCreatureSet * army,
  193. bool checkGold) const
  194. {
  195. const float enemyArmyEliminationRewardRatio = 0.5f;
  196. auto relations = aiNk->cc->getPlayerRelations(target->tempOwner, aiNk->playerID);
  197. if(!target)
  198. return 0;
  199. switch(target->ID)
  200. {
  201. case Obj::HILL_FORT:
  202. return aiNk->armyManager->calculateCreaturesUpgrade(army, target, aiNk->cc->getResourceAmount()).upgradeValue;
  203. case Obj::CREATURE_GENERATOR1:
  204. case Obj::CREATURE_GENERATOR2:
  205. case Obj::CREATURE_GENERATOR3:
  206. case Obj::CREATURE_GENERATOR4:
  207. return getDwellingArmyValue(aiNk->cc.get(), target, checkGold);
  208. case Obj::SPELL_SCROLL:
  209. return evaluateSpellScrollArmyValue(dynamic_cast<const CGArtifact *>(target)->getArtifactInstance()->getScrollSpellID());
  210. case Obj::ARTIFACT:
  211. return evaluateArtifactArmyValue(dynamic_cast<const CGArtifact *>(target)->getArtifactInstance()->getType());
  212. case Obj::HERO:
  213. return relations == PlayerRelations::ENEMIES
  214. ? enemyArmyEliminationRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->getArmyStrength()
  215. : 0;
  216. case Obj::PANDORAS_BOX:
  217. return 5000;
  218. case Obj::MAGIC_WELL:
  219. case Obj::MAGIC_SPRING:
  220. return getManaRecoveryArmyReward(hero);
  221. default:
  222. break;
  223. }
  224. auto rewardable = dynamic_cast<const Rewardable::Interface *>(target);
  225. if(rewardable)
  226. {
  227. auto totalValue = 0;
  228. for(int index : rewardable->getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT))
  229. {
  230. auto & info = rewardable->configuration.info[index];
  231. auto rewardValue = 0;
  232. for(auto artID : info.reward.grantedArtifacts)
  233. rewardValue += evaluateArtifactArmyValue(artID.toArtifact());
  234. for(auto scroll : info.reward.grantedScrolls)
  235. rewardValue += evaluateSpellScrollArmyValue(scroll);
  236. for(const auto & stackInfo : info.reward.creatures)
  237. rewardValue += stackInfo.getType()->getAIValue() * stackInfo.getCount();
  238. const auto combined_size = std::min(static_cast<size_t>(1),
  239. info.reward.grantedArtifacts.size() + info.reward.creatures.size() +
  240. info.reward.grantedScrolls.size());
  241. totalValue += rewardValue > 0 ? rewardValue / combined_size : 0;
  242. }
  243. return totalValue;
  244. }
  245. return 0;
  246. }
  247. uint64_t RewardEvaluator::getArmyGrowth(
  248. const CGObjectInstance * target,
  249. const CGHeroInstance * hero,
  250. const CCreatureSet * army) const
  251. {
  252. if(!target)
  253. return 0;
  254. auto relations = aiNk->cc->getPlayerRelations(target->tempOwner, hero->tempOwner);
  255. if(relations != PlayerRelations::ENEMIES)
  256. return 0;
  257. switch(target->ID)
  258. {
  259. case Obj::TOWN:
  260. {
  261. auto town = dynamic_cast<const CGTownInstance *>(target);
  262. auto fortLevel = town->fortLevel();
  263. auto neutral = !town->getOwner().isValidPlayer();
  264. auto booster = isAnotherAi(town, *aiNk->cc) || neutral ? 1 : 2;
  265. if(fortLevel < CGTownInstance::CITADEL)
  266. return town->hasFort() ? booster * 500 : 0;
  267. else
  268. return booster * (fortLevel == CGTownInstance::CASTLE ? 5000 : 2000);
  269. }
  270. case Obj::CREATURE_GENERATOR1:
  271. case Obj::CREATURE_GENERATOR2:
  272. case Obj::CREATURE_GENERATOR3:
  273. case Obj::CREATURE_GENERATOR4:
  274. return getDwellingArmyGrowth(aiNk->cc.get(), target, hero->getOwner());
  275. case Obj::ARTIFACT:
  276. // it is not supported now because hero will not sit in town on 7th day but later parts of legion may be counted as army growth as well.
  277. return 0;
  278. default:
  279. return 0;
  280. }
  281. }
  282. int RewardEvaluator::getGoldCost(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army) const
  283. {
  284. if(!target)
  285. return 0;
  286. if(auto * m = dynamic_cast<const IMarket *>(target))
  287. {
  288. if(m->allowsTrade(EMarketMode::RESOURCE_SKILL))
  289. return 2000;
  290. }
  291. switch(target->ID)
  292. {
  293. case Obj::HILL_FORT:
  294. return aiNk->armyManager->calculateCreaturesUpgrade(army, target, aiNk->cc->getResourceAmount()).upgradeCost[EGameResID::GOLD];
  295. case Obj::SCHOOL_OF_MAGIC:
  296. case Obj::SCHOOL_OF_WAR:
  297. return 1000;
  298. case Obj::CREATURE_GENERATOR1:
  299. case Obj::CREATURE_GENERATOR2:
  300. case Obj::CREATURE_GENERATOR3:
  301. case Obj::CREATURE_GENERATOR4:
  302. return getDwellingArmyCost(target);
  303. default:
  304. return 0;
  305. }
  306. }
  307. float RewardEvaluator::getEnemyHeroStrategicalValue(const CGHeroInstance * enemy) const
  308. {
  309. auto objectsUnderThreat = aiNk->dangerHitMap->getOneTurnAccessibleObjects(enemy);
  310. float objectValue = 0;
  311. for(auto obj : objectsUnderThreat)
  312. {
  313. vstd::amax(objectValue, getStrategicalValue(obj));
  314. }
  315. /*
  316. 1. If an enemy hero can attack nearby object, it's not useful to capture the object on our own.
  317. Killing the hero is almost as important (0.9) as capturing the object itself.
  318. 2. The formula quickly approaches 1.0 as hero level increases,
  319. but higher level always means higher value and the minimal value for level 1 hero is 0.5
  320. */
  321. return std::min(1.5f, objectValue * 0.9f + (1.5f - (1.5f / (1 + enemy->level))));
  322. }
  323. /// @return between 0-1.0f
  324. float RewardEvaluator::getNowResourceRequirementStrength(GameResID resType) const
  325. {
  326. TResources requiredResources = aiNk->buildAnalyzer->getMissingResourcesNow();
  327. TResources dailyIncome = aiNk->buildAnalyzer->getDailyIncome();
  328. if(requiredResources[resType] == 0)
  329. return 0;
  330. if(dailyIncome[resType] == 0)
  331. return 1.0f;
  332. return 0.8f;
  333. }
  334. /// @return between 0-1.0f
  335. float RewardEvaluator::getTotalResourceRequirementStrength(GameResID resType) const
  336. {
  337. TResources requiredResources = aiNk->buildAnalyzer->getMissingResourcesInTotal();
  338. TResources dailyIncome = aiNk->buildAnalyzer->getDailyIncome();
  339. if(requiredResources[resType] == 0)
  340. return 0;
  341. if(dailyIncome[resType] == 0)
  342. return 1.0f;
  343. return 0.8f;
  344. }
  345. uint64_t RewardEvaluator::townArmyGrowth(const CGTownInstance * town) const
  346. {
  347. uint64_t result = 0;
  348. for(auto creatureInfo : town->creatures)
  349. {
  350. if(creatureInfo.second.empty())
  351. continue;
  352. auto creature = creatureInfo.second.back().toCreature();
  353. result += creature->getAIValue() * town->getGrowthInfo(creature->getLevel() - 1).totalGrowth();
  354. }
  355. return result;
  356. }
  357. float RewardEvaluator::getManaRecoveryArmyReward(const CGHeroInstance * hero) const
  358. {
  359. return aiNk->heroManager->getMagicStrength(hero) * 10000 * (1.0f - std::sqrt(static_cast<float>(hero->mana) / hero->manaLimit()));
  360. }
  361. /// @return between 0-1.0f
  362. float RewardEvaluator::getCombinedResourceRequirementStrength(const TResources & res) const
  363. {
  364. float sum = 0.0f;
  365. for(TResources::nziterator it(res); it.valid(); it++)
  366. {
  367. auto calculation = 0.5f * getNowResourceRequirementStrength(it->resType)
  368. + 0.5f * getTotalResourceRequirementStrength(it->resType);
  369. // Even not required resources should be valuable because they shouldn't be left for the enemies to collect
  370. sum += std::min(MINIMUM_STRATEGICAL_VALUE_NON_TOWN, calculation);
  371. }
  372. return sum;
  373. }
  374. float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target, const CGHeroInstance * hero) const
  375. {
  376. if(!target)
  377. return 0;
  378. switch(target->ID)
  379. {
  380. case Obj::MINE:
  381. {
  382. auto mine = dynamic_cast<const CGMine *>(target);
  383. TResources res;
  384. res[mine->producedResource] = mine->producedQuantity;
  385. // Mines should have higher priority than resources
  386. return 1.0f + getCombinedResourceRequirementStrength(res);
  387. }
  388. case Obj::RESOURCE:
  389. {
  390. auto resource = dynamic_cast<const CGResource *>(target);
  391. TResources res;
  392. res[resource->resourceID()] = resource->getAmount();
  393. return getCombinedResourceRequirementStrength(res);
  394. }
  395. case Obj::TOWN:
  396. {
  397. if(aiNk->buildAnalyzer->getDevelopmentInfo().empty())
  398. return 10.0f;
  399. auto town = dynamic_cast<const CGTownInstance *>(target);
  400. if(town->getOwner() == aiNk->playerID)
  401. {
  402. auto armyIncome = townArmyGrowth(town);
  403. auto dailyIncome = town->dailyIncome()[EGameResID::GOLD];
  404. return std::min(1.0f, std::sqrt(armyIncome / 40000.0f)) + std::min(0.3f, dailyIncome / 10000.0f);
  405. }
  406. auto fortLevel = town->fortLevel();
  407. auto booster = isAnotherAi(town, *aiNk->cc) ? 0.4f : 1.0f;
  408. if(town->hasCapitol())
  409. return booster * 1.5;
  410. if(fortLevel < CGTownInstance::CITADEL)
  411. return booster * (town->hasFort() ? 1.0 : 0.8);
  412. return booster * (fortLevel == CGTownInstance::CASTLE ? 1.4 : 1.2);
  413. }
  414. case Obj::HERO:
  415. return aiNk->cc->getPlayerRelations(target->tempOwner, aiNk->playerID) == PlayerRelations::ENEMIES
  416. ? getEnemyHeroStrategicalValue(dynamic_cast<const CGHeroInstance *>(target))
  417. : 0;
  418. case Obj::KEYMASTER:
  419. return 0.6f;
  420. default:
  421. break;
  422. }
  423. auto rewardable = dynamic_cast<const Rewardable::Interface *>(target);
  424. if(rewardable && hero)
  425. {
  426. auto resourceReward = 0.0f;
  427. for(int index : rewardable->getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT))
  428. {
  429. resourceReward += getCombinedResourceRequirementStrength(rewardable->configuration.info[index].reward.resources);
  430. }
  431. return resourceReward;
  432. }
  433. return 0;
  434. }
  435. float RewardEvaluator::getConquestValue(const CGObjectInstance* target) const
  436. {
  437. if (!target)
  438. return 0;
  439. if (target->getOwner() == aiNk->playerID)
  440. return 0;
  441. switch (target->ID)
  442. {
  443. case Obj::TOWN:
  444. {
  445. if (aiNk->buildAnalyzer->getDevelopmentInfo().empty())
  446. return 10.0f;
  447. auto town = dynamic_cast<const CGTownInstance*>(target);
  448. if (town->getOwner() == aiNk->playerID)
  449. {
  450. auto armyIncome = townArmyGrowth(town);
  451. auto dailyIncome = town->dailyIncome()[EGameResID::GOLD];
  452. return std::min(1.0f, std::sqrt(armyIncome / 40000.0f)) + std::min(0.3f, dailyIncome / 10000.0f);
  453. }
  454. auto fortLevel = town->fortLevel();
  455. auto booster = 1.0f;
  456. if (town->hasCapitol())
  457. return booster * 1.5;
  458. if (fortLevel < CGTownInstance::CITADEL)
  459. return booster * (town->hasFort() ? 1.0 : 0.8);
  460. else
  461. return booster * (fortLevel == CGTownInstance::CASTLE ? 1.4 : 1.2);
  462. }
  463. case Obj::HERO:
  464. return aiNk->cc->getPlayerRelations(target->tempOwner, aiNk->playerID) == PlayerRelations::ENEMIES
  465. ? getEnemyHeroStrategicalValue(dynamic_cast<const CGHeroInstance*>(target))
  466. : 0;
  467. default:
  468. return 0;
  469. }
  470. }
  471. float RewardEvaluator::evaluateWitchHutSkillScore(const CGObjectInstance * hut, const CGHeroInstance * hero, HeroRole role) const
  472. {
  473. auto rewardable = dynamic_cast<const CRewardableObject *>(hut);
  474. assert(rewardable);
  475. auto skill = SecondarySkill(*rewardable->configuration.getVariable("secondarySkill", "gainedSkill"));
  476. if(!hut->wasVisited(hero->tempOwner))
  477. return role == HeroRole::SCOUT ? 2 : 0;
  478. if(hero->getSecSkillLevel(skill) != MasteryLevel::NONE
  479. || static_cast<int>(hero->secSkills.size()) >= aiNk->cc->getSettings().getInteger(EGameSettings::HEROES_SKILL_PER_HERO))
  480. return 0;
  481. auto score = aiNk->heroManager->evaluateSecSkill(skill, hero);
  482. return score >= 2 ? (role == HeroRole::MAIN ? 10 : 4) : score;
  483. }
  484. float RewardEvaluator::getSkillReward(const CGObjectInstance * target, const CGHeroInstance * hero, HeroRole role) const
  485. {
  486. const float enemyHeroEliminationSkillRewardRatio = 0.5f;
  487. if(!target)
  488. return 0;
  489. switch(target->ID)
  490. {
  491. case Obj::STAR_AXIS:
  492. case Obj::SCHOLAR:
  493. case Obj::SCHOOL_OF_MAGIC:
  494. case Obj::SCHOOL_OF_WAR:
  495. case Obj::GARDEN_OF_REVELATION:
  496. case Obj::MARLETTO_TOWER:
  497. case Obj::MERCENARY_CAMP:
  498. case Obj::TREE_OF_KNOWLEDGE:
  499. return 1;
  500. case Obj::LEARNING_STONE:
  501. return 1.0f / std::sqrt(hero->level);
  502. case Obj::ARENA:
  503. return 2;
  504. case Obj::SHRINE_OF_MAGIC_INCANTATION:
  505. return 0.25f;
  506. case Obj::SHRINE_OF_MAGIC_GESTURE:
  507. return 1.0f;
  508. case Obj::SHRINE_OF_MAGIC_THOUGHT:
  509. return 2.0f;
  510. case Obj::LIBRARY_OF_ENLIGHTENMENT:
  511. return 8;
  512. case Obj::WITCH_HUT:
  513. return evaluateWitchHutSkillScore(target, hero, role);
  514. case Obj::PANDORAS_BOX:
  515. //Can contains experience, spells, or skills (only on custom maps)
  516. return 2.5f;
  517. case Obj::HERO:
  518. return aiNk->cc->getPlayerRelations(target->tempOwner, aiNk->playerID) == PlayerRelations::ENEMIES
  519. ? enemyHeroEliminationSkillRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->level
  520. : 0;
  521. default:
  522. break;
  523. }
  524. auto rewardable = dynamic_cast<const Rewardable::Interface *>(target);
  525. if(rewardable)
  526. {
  527. auto totalValue = 0.0f;
  528. for(int index : rewardable->getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT))
  529. {
  530. auto & info = rewardable->configuration.info[index];
  531. auto rewardValue = 0.0f;
  532. if(!info.reward.spells.empty())
  533. {
  534. for(auto spellID : info.reward.spells)
  535. {
  536. const spells::Spell * spell = LIBRARY->spells()->getById(spellID);
  537. if(hero->canLearnSpell(spell) && !hero->spellbookContainsSpell(spellID))
  538. {
  539. rewardValue += std::sqrt(spell->getLevel()) / 4.0f;
  540. }
  541. }
  542. totalValue += rewardValue / info.reward.spells.size();
  543. }
  544. if(!info.reward.primary.empty())
  545. {
  546. for(auto value : info.reward.primary)
  547. {
  548. totalValue += value;
  549. }
  550. }
  551. }
  552. return totalValue;
  553. }
  554. return 0;
  555. }
  556. const HitMapInfo & RewardEvaluator::getEnemyHeroDanger(const int3 & tile, uint8_t turn) const
  557. {
  558. auto & threatNode = aiNk->dangerHitMap->getTileThreat(tile);
  559. if(threatNode.maximumDanger.danger == 0)
  560. return HitMapInfo::NoThreat;
  561. if(threatNode.maximumDanger.turn <= turn)
  562. return threatNode.maximumDanger;
  563. return threatNode.fastestDanger.turn <= turn ? threatNode.fastestDanger : HitMapInfo::NoThreat;
  564. }
  565. int32_t getArmyCost(const CArmedInstance * army)
  566. {
  567. int32_t value = 0;
  568. for(const auto & stack : army->Slots())
  569. {
  570. value += stack.second->getCreatureID().toCreature()->getFullRecruitCost().marketValue() * stack.second->getCount();
  571. }
  572. return value;
  573. }
  574. int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CGHeroInstance * hero) const
  575. {
  576. if(!target)
  577. return 0;
  578. auto relations = aiNk->cc->getPlayerRelations(target->tempOwner, hero->tempOwner);
  579. const int dailyIncomeMultiplier = 5;
  580. const float enemyArmyEliminationGoldRewardRatio = 0.2f;
  581. const int32_t heroEliminationBonus = GameConstants::HERO_GOLD_COST / 2;
  582. switch(target->ID)
  583. {
  584. case Obj::RESOURCE:
  585. {
  586. auto * res = dynamic_cast<const CGResource*>(target);
  587. return res && res->resourceID() == GameResID::GOLD ? 600 : 100;
  588. }
  589. case Obj::TREASURE_CHEST:
  590. return 1500;
  591. case Obj::WATER_WHEEL:
  592. return 1000;
  593. case Obj::TOWN:
  594. return dailyIncomeMultiplier * estimateTownIncome(aiNk->cc.get(), target, hero);
  595. case Obj::MINE:
  596. case Obj::ABANDONED_MINE:
  597. {
  598. auto * mine = dynamic_cast<const CGMine*>(target);
  599. return dailyIncomeMultiplier * (mine->producedResource == GameResID::GOLD ? 1000 : 75);
  600. }
  601. case Obj::PANDORAS_BOX:
  602. return 2500;
  603. case Obj::PRISON:
  604. //Objectively saves us 2500 to hire hero
  605. return GameConstants::HERO_GOLD_COST;
  606. case Obj::HERO:
  607. return relations == PlayerRelations::ENEMIES
  608. ? heroEliminationBonus + enemyArmyEliminationGoldRewardRatio * getArmyCost(dynamic_cast<const CGHeroInstance *>(target))
  609. : 0;
  610. default:
  611. break;
  612. }
  613. auto rewardable = dynamic_cast<const Rewardable::Interface *>(target);
  614. if(rewardable)
  615. {
  616. auto goldReward = 0;
  617. for(int index : rewardable->getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT))
  618. {
  619. auto & info = rewardable->configuration.info[index];
  620. goldReward += getResourcesGoldReward(info.reward.resources);
  621. }
  622. return goldReward;
  623. }
  624. return 0;
  625. }
  626. class HeroExchangeEvaluator : public IEvaluationContextBuilder
  627. {
  628. public:
  629. void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  630. {
  631. if(task->goalType != Goals::HERO_EXCHANGE)
  632. return;
  633. Goals::HeroExchange & heroExchange = dynamic_cast<Goals::HeroExchange &>(*task);
  634. uint64_t armyStrength = heroExchange.getReinforcementArmyStrength(evaluationContext.evaluator.aiNk);
  635. evaluationContext.addNonCriticalStrategicalValue(2.0f * armyStrength / (float)heroExchange.hero->getArmyStrength());
  636. evaluationContext.conquestValue += 2.0f * armyStrength / (float)heroExchange.hero->getArmyStrength();
  637. evaluationContext.heroRole = evaluationContext.evaluator.aiNk->heroManager->getHeroRoleOrDefaultInefficient(heroExchange.hero);
  638. evaluationContext.isExchange = true;
  639. }
  640. };
  641. class ArmyUpgradeEvaluator : public IEvaluationContextBuilder
  642. {
  643. public:
  644. void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  645. {
  646. if(task->goalType != Goals::ARMY_UPGRADE)
  647. return;
  648. Goals::ArmyUpgrade & armyUpgrade = dynamic_cast<Goals::ArmyUpgrade &>(*task);
  649. uint64_t upgradeValue = armyUpgrade.getUpgradeValue();
  650. evaluationContext.armyReward += upgradeValue;
  651. evaluationContext.addNonCriticalStrategicalValue(upgradeValue / (float)armyUpgrade.hero->getArmyStrength());
  652. evaluationContext.isArmyUpgrade = true;
  653. }
  654. };
  655. class ExplorePointEvaluator : public IEvaluationContextBuilder
  656. {
  657. public:
  658. void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  659. {
  660. if(task->goalType != Goals::EXPLORATION_POINT)
  661. return;
  662. int tilesDiscovered = task->value;
  663. evaluationContext.addNonCriticalStrategicalValue(0.03f * tilesDiscovered);
  664. for (auto obj : evaluationContext.evaluator.aiNk->cc->getVisitableObjs(task->tile))
  665. {
  666. switch (obj->ID.num)
  667. {
  668. case Obj::MONOLITH_ONE_WAY_ENTRANCE:
  669. case Obj::MONOLITH_TWO_WAY:
  670. case Obj::SUBTERRANEAN_GATE:
  671. evaluationContext.explorePriority = 1;
  672. break;
  673. case Obj::REDWOOD_OBSERVATORY:
  674. case Obj::PILLAR_OF_FIRE:
  675. evaluationContext.explorePriority = 2;
  676. break;
  677. }
  678. }
  679. if(evaluationContext.evaluator.aiNk->cc->getTile(task->tile)->roadType != RoadId::NO_ROAD)
  680. evaluationContext.explorePriority = 1;
  681. if (evaluationContext.explorePriority == 0)
  682. evaluationContext.explorePriority = 3;
  683. }
  684. };
  685. class StayAtTownManaRecoveryEvaluator : public IEvaluationContextBuilder
  686. {
  687. public:
  688. void buildEvaluationContext(EvaluationContext& evaluationContext, Goals::TSubgoal task) const override
  689. {
  690. if (task->goalType != Goals::STAY_AT_TOWN)
  691. return;
  692. Goals::StayAtTown& stayAtTown = dynamic_cast<Goals::StayAtTown&>(*task);
  693. if (stayAtTown.getHero() != nullptr && stayAtTown.getHero()->movementPointsRemaining() < 100)
  694. {
  695. return;
  696. }
  697. if(stayAtTown.town->mageGuildLevel() > 0)
  698. evaluationContext.armyReward += evaluationContext.evaluator.getManaRecoveryArmyReward(stayAtTown.getHero());
  699. if (vstd::isAlmostZero(evaluationContext.armyReward))
  700. evaluationContext.isDefend = true;
  701. else
  702. {
  703. evaluationContext.movementCost += stayAtTown.getMovementWasted();
  704. evaluationContext.movementCostByRole[evaluationContext.heroRole] += stayAtTown.getMovementWasted();
  705. }
  706. }
  707. };
  708. void addTileDanger(EvaluationContext & evaluationContext, const int3 & tile, uint8_t turn, uint64_t ourStrength)
  709. {
  710. HitMapInfo enemyDanger = evaluationContext.evaluator.getEnemyHeroDanger(tile, turn);
  711. if(enemyDanger.danger)
  712. {
  713. auto dangerRatio = enemyDanger.danger / (double)ourStrength;
  714. vstd::amax(evaluationContext.enemyHeroDangerRatio, dangerRatio);
  715. vstd::amax(evaluationContext.threat, enemyDanger.threat);
  716. }
  717. }
  718. class DefendTownEvaluator : public IEvaluationContextBuilder
  719. {
  720. public:
  721. void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  722. {
  723. if(task->goalType != Goals::DEFEND_TOWN)
  724. return;
  725. Goals::DefendTown & defendTown = dynamic_cast<Goals::DefendTown &>(*task);
  726. const CGTownInstance * town = defendTown.town;
  727. auto & threat = defendTown.getThreat();
  728. auto strategicalValue = evaluationContext.evaluator.getStrategicalValue(town);
  729. float multiplier = 1;
  730. if(threat.turn < defendTown.getTurn())
  731. multiplier /= 1 + (defendTown.getTurn() - threat.turn);
  732. multiplier /= 1.0f + threat.turn / 5.0f;
  733. if(defendTown.getTurn() > 0 && defendTown.isCounterAttack())
  734. {
  735. auto ourSpeed = defendTown.hero->movementPointsLimit(true);
  736. auto enemySpeed = threat.heroPtr.get()->movementPointsLimit(true);
  737. if(enemySpeed > ourSpeed) multiplier *= 0.7f;
  738. }
  739. auto dailyIncome = town->dailyIncome()[EGameResID::GOLD];
  740. auto armyGrowth = evaluationContext.evaluator.townArmyGrowth(town);
  741. evaluationContext.armyGrowth += armyGrowth * multiplier;
  742. evaluationContext.goldReward += dailyIncome * 5 * multiplier;
  743. if(evaluationContext.evaluator.aiNk->buildAnalyzer->getDevelopmentInfo().size() == 1)
  744. vstd::amax(evaluationContext.strategicalValue, 2.5f * multiplier * strategicalValue);
  745. else
  746. evaluationContext.addNonCriticalStrategicalValue(1.7f * multiplier * strategicalValue);
  747. evaluationContext.defenseValue = town->fortLevel();
  748. evaluationContext.isDefend = true;
  749. evaluationContext.threatTurns = threat.turn;
  750. vstd::amax(evaluationContext.danger, defendTown.getThreat().danger);
  751. addTileDanger(evaluationContext, town->visitablePos(), defendTown.getTurn(), defendTown.getDefenceStrength());
  752. }
  753. };
  754. class ExecuteHeroChainEvaluationContextBuilder : public IEvaluationContextBuilder
  755. {
  756. private:
  757. const Nullkiller * aiNk;
  758. public:
  759. ExecuteHeroChainEvaluationContextBuilder(const Nullkiller * aiNk) : aiNk(aiNk) {}
  760. void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  761. {
  762. if(task->goalType != Goals::EXECUTE_HERO_CHAIN)
  763. return;
  764. Goals::ExecuteHeroChain & chain = dynamic_cast<Goals::ExecuteHeroChain &>(*task);
  765. const AIPath & path = chain.getPath();
  766. if (vstd::isAlmostZero(path.movementCost()))
  767. return;
  768. vstd::amax(evaluationContext.danger, path.getTotalDanger());
  769. evaluationContext.movementCost += path.movementCost();
  770. evaluationContext.closestWayRatio = chain.closestWayRatio;
  771. std::map<const CGHeroInstance *, float> costsPerHero;
  772. for(auto & node : path.nodes)
  773. {
  774. vstd::amax(costsPerHero[node.targetHero], node.cost);
  775. if (node.layer == EPathfindingLayer::SAIL)
  776. evaluationContext.involvesSailing = true;
  777. }
  778. float highestCostForSingleHero = 0;
  779. for(auto pair : costsPerHero)
  780. {
  781. auto role = evaluationContext.evaluator.aiNk->heroManager->getHeroRoleOrDefaultInefficient(pair.first);
  782. evaluationContext.movementCostByRole[role] += pair.second;
  783. if (pair.second > highestCostForSingleHero)
  784. highestCostForSingleHero = pair.second;
  785. }
  786. if (highestCostForSingleHero > 1 && costsPerHero.size() > 1)
  787. {
  788. //Chains that involve more than 1 hero doing something for more than a turn are too expensive in my book. They often involved heroes doing nothing just standing there waiting to fulfill their part of the chain.
  789. return;
  790. }
  791. evaluationContext.movementCost *= costsPerHero.size(); //further deincentivise chaining as it often involves bringing back the army afterwards
  792. auto hero = task->hero;
  793. bool checkGold = evaluationContext.danger == 0;
  794. auto army = path.heroArmy;
  795. const CGObjectInstance * target = aiNk->cc->getObj((ObjectInstanceID)task->objid, false);
  796. auto heroRole = evaluationContext.evaluator.aiNk->heroManager->getHeroRoleOrDefaultInefficient(hero);
  797. if(heroRole == HeroRole::MAIN)
  798. evaluationContext.heroRole = heroRole;
  799. // Assuming Slots() returns a collection of slots with slot.second->getCreatureID() and slot.second->getPower()
  800. float heroPower = 0;
  801. float totalPower = 0;
  802. // Map to store the aggregated power of creatures by CreatureID
  803. std::map<CreatureID, float> totalPowerByCreatureID;
  804. // Calculate hero power and total power by CreatureID
  805. for (const auto & slot : hero->Slots())
  806. {
  807. CreatureID creatureID = slot.second->getCreatureID();
  808. float slotPower = slot.second->getPower();
  809. // Add the power of this slot to the heroPower
  810. heroPower += slotPower;
  811. // Accumulate the total power for the specific CreatureID
  812. if (totalPowerByCreatureID.find(creatureID) == totalPowerByCreatureID.end())
  813. {
  814. // First time encountering this CreatureID, retrieve total creatures' power
  815. totalPowerByCreatureID[creatureID] = aiNk->armyManager->getTotalCreaturesAvailable(creatureID).power;
  816. }
  817. }
  818. // Calculate total power based on unique CreatureIDs
  819. for (const auto& entry : totalPowerByCreatureID)
  820. {
  821. totalPower += entry.second;
  822. }
  823. // Compute the power ratio if total power is greater than zero
  824. if (totalPower > 0)
  825. {
  826. evaluationContext.powerRatio = heroPower / totalPower;
  827. }
  828. if (target)
  829. {
  830. evaluationContext.goldReward += evaluationContext.evaluator.getGoldReward(target, hero);
  831. evaluationContext.armyReward += evaluationContext.evaluator.getArmyReward(target, hero, army, checkGold);
  832. evaluationContext.armyGrowth += evaluationContext.evaluator.getArmyGrowth(target, hero, army);
  833. evaluationContext.skillReward += evaluationContext.evaluator.getSkillReward(target, hero, heroRole);
  834. evaluationContext.addNonCriticalStrategicalValue(evaluationContext.evaluator.getStrategicalValue(target));
  835. evaluationContext.conquestValue += evaluationContext.evaluator.getConquestValue(target);
  836. if (target->ID == Obj::HERO)
  837. evaluationContext.isHero = true;
  838. if (target->getOwner().isValidPlayer() && aiNk->cc->getPlayerRelations(aiNk->playerID, target->getOwner()) == PlayerRelations::ENEMIES)
  839. evaluationContext.isEnemy = true;
  840. if (target->ID == Obj::TOWN)
  841. evaluationContext.defenseValue = dynamic_cast<const CGTownInstance*>(target)->fortLevel();
  842. evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army);
  843. if(evaluationContext.danger > 0)
  844. evaluationContext.skillReward += (float)evaluationContext.danger / (float)hero->getArmyStrength();
  845. }
  846. evaluationContext.armyInvolvement += army->getArmyCost();
  847. vstd::amax(evaluationContext.armyLossRatio, (float)path.getTotalArmyLoss() / (float)army->getArmyStrength());
  848. addTileDanger(evaluationContext, path.targetTile(), path.turn(), path.getHeroStrength());
  849. vstd::amax(evaluationContext.turn, path.turn());
  850. }
  851. };
  852. class ClusterEvaluationContextBuilder : public IEvaluationContextBuilder
  853. {
  854. public:
  855. ClusterEvaluationContextBuilder(const Nullkiller * aiNk) {}
  856. void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  857. {
  858. if(task->goalType != Goals::UNLOCK_CLUSTER)
  859. return;
  860. Goals::UnlockCluster & clusterGoal = dynamic_cast<Goals::UnlockCluster &>(*task);
  861. std::shared_ptr<ObjectCluster> cluster = clusterGoal.getCluster();
  862. auto hero = clusterGoal.hero;
  863. auto role = evaluationContext.evaluator.aiNk->heroManager->getHeroRoleOrDefaultInefficient(hero);
  864. std::vector<std::pair<ObjectInstanceID, ClusterObjectInfo>> objects;
  865. objects.reserve(cluster->objects.size());
  866. for (const auto& obj : cluster->objects)
  867. objects.emplace_back(obj.first, obj.second);
  868. std::sort(objects.begin(), objects.end(), [](std::pair<ObjectInstanceID, ClusterObjectInfo> o1, std::pair<ObjectInstanceID, ClusterObjectInfo> o2) -> bool
  869. {
  870. return o1.second.priority > o2.second.priority;
  871. });
  872. int boost = 1;
  873. for(auto & objInfo : objects)
  874. {
  875. auto target = evaluationContext.evaluator.aiNk->cc->getObj(objInfo.first);
  876. bool checkGold = objInfo.second.danger == 0;
  877. auto army = hero;
  878. evaluationContext.goldReward += evaluationContext.evaluator.getGoldReward(target, hero) / boost;
  879. evaluationContext.armyReward += evaluationContext.evaluator.getArmyReward(target, hero, army, checkGold) / boost;
  880. evaluationContext.skillReward += evaluationContext.evaluator.getSkillReward(target, hero, role) / boost;
  881. evaluationContext.addNonCriticalStrategicalValue(evaluationContext.evaluator.getStrategicalValue(target) / boost);
  882. evaluationContext.conquestValue += evaluationContext.evaluator.getConquestValue(target);
  883. evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army) / boost;
  884. evaluationContext.movementCostByRole[role] += objInfo.second.movementCost / boost;
  885. evaluationContext.movementCost += objInfo.second.movementCost / boost;
  886. vstd::amax(evaluationContext.turn, objInfo.second.turn / boost);
  887. boost <<= 1;
  888. if(boost > 8)
  889. break;
  890. }
  891. }
  892. };
  893. class ExchangeSwapTownHeroesContextBuilder : public IEvaluationContextBuilder
  894. {
  895. public:
  896. void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  897. {
  898. if(task->goalType != Goals::EXCHANGE_SWAP_TOWN_HEROES)
  899. return;
  900. Goals::ExchangeSwapTownHeroes & swapCommand = dynamic_cast<Goals::ExchangeSwapTownHeroes &>(*task);
  901. const CGHeroInstance * garrisonHero = swapCommand.getGarrisonHero();
  902. logAi->trace("buildEvaluationContext ExchangeSwapTownHeroesContextBuilder %s affected objects: %d", swapCommand.toString(), swapCommand.getAffectedObjects().size());
  903. for (auto obj : swapCommand.getAffectedObjects())
  904. {
  905. logAi->trace("affected object: %s", evaluationContext.evaluator.aiNk->cc->getObj(obj)->getObjectName());
  906. }
  907. if (garrisonHero)
  908. logAi->debug("with %s and %d", garrisonHero->getNameTranslated(), int(swapCommand.getLockingReason()));
  909. if(garrisonHero && swapCommand.getLockingReason() == HeroLockedReason::DEFENCE)
  910. {
  911. auto defenderRole = evaluationContext.evaluator.aiNk->heroManager->getHeroRoleOrDefaultInefficient(garrisonHero);
  912. auto mpLeft = garrisonHero->movementPointsRemaining() / (float)garrisonHero->movementPointsLimit(true);
  913. evaluationContext.movementCost += mpLeft;
  914. evaluationContext.movementCostByRole[defenderRole] += mpLeft;
  915. evaluationContext.heroRole = defenderRole;
  916. evaluationContext.isDefend = true;
  917. evaluationContext.armyInvolvement = garrisonHero->getArmyStrength();
  918. logAi->debug("evaluationContext.isDefend: %d", evaluationContext.isDefend);
  919. }
  920. }
  921. };
  922. class DismissHeroContextBuilder : public IEvaluationContextBuilder
  923. {
  924. private:
  925. const Nullkiller * aiNk;
  926. public:
  927. DismissHeroContextBuilder(const Nullkiller * aiNk) : aiNk(aiNk) {}
  928. void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  929. {
  930. if(task->goalType != Goals::DISMISS_HERO)
  931. return;
  932. Goals::DismissHero & dismissCommand = dynamic_cast<Goals::DismissHero &>(*task);
  933. const CGHeroInstance * dismissedHero = dismissCommand.getHero();
  934. auto role = aiNk->heroManager->getHeroRoleOrDefaultInefficient(dismissedHero);
  935. auto mpLeft = dismissedHero->movementPointsRemaining();
  936. evaluationContext.movementCost += mpLeft;
  937. evaluationContext.movementCostByRole[role] += mpLeft;
  938. evaluationContext.goldCost += GameConstants::HERO_GOLD_COST + getArmyCost(dismissedHero);
  939. }
  940. };
  941. class BuildThisEvaluationContextBuilder : public IEvaluationContextBuilder
  942. {
  943. public:
  944. void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  945. {
  946. if(task->goalType != Goals::BUILD_STRUCTURE)
  947. return;
  948. Goals::BuildThis & buildThis = dynamic_cast<Goals::BuildThis &>(*task);
  949. auto & bi = buildThis.buildingInfo;
  950. evaluationContext.goldReward += 7 * bi.dailyIncome.marketValue() / 2; // 7 day income but half we already have
  951. evaluationContext.heroRole = HeroRole::MAIN;
  952. evaluationContext.movementCostByRole[evaluationContext.heroRole] += bi.prerequisitesCount;
  953. int32_t cost = bi.buildCost[EGameResID::GOLD];
  954. evaluationContext.goldCost += cost;
  955. evaluationContext.closestWayRatio = 1;
  956. evaluationContext.buildingCost += bi.buildCostWithPrerequisites;
  957. bool alreadyOwn = false;
  958. int highestMageGuildPossible = BuildingID::MAGES_GUILD_3;
  959. for (auto town : evaluationContext.evaluator.aiNk->cc->getTownsInfo())
  960. {
  961. if (town->hasBuilt(bi.id))
  962. alreadyOwn = true;
  963. if (evaluationContext.evaluator.aiNk->cc->canBuildStructure(town, BuildingID::MAGES_GUILD_5) != EBuildingState::FORBIDDEN)
  964. highestMageGuildPossible = BuildingID::MAGES_GUILD_5;
  965. else if (evaluationContext.evaluator.aiNk->cc->canBuildStructure(town, BuildingID::MAGES_GUILD_4) != EBuildingState::FORBIDDEN)
  966. highestMageGuildPossible = BuildingID::MAGES_GUILD_4;
  967. }
  968. if (bi.id == BuildingID::MARKETPLACE || bi.dailyIncome[EGameResID::WOOD] > 0)
  969. evaluationContext.isTradeBuilding = true;
  970. #if NK2AI_TRACE_LEVEL >= 1
  971. logAi->trace("Building costs for %s : %s MarketValue: %d",bi.toString(), evaluationContext.buildingCost.toString(), evaluationContext.buildingCost.marketValue());
  972. #endif
  973. if(bi.creatureID != CreatureID::NONE)
  974. {
  975. evaluationContext.addNonCriticalStrategicalValue(buildThis.townInfo.armyStrength / 50000.0);
  976. if(bi.baseCreatureID == bi.creatureID)
  977. {
  978. evaluationContext.addNonCriticalStrategicalValue((0.5f + 0.1f * bi.creatureLevel) / (float)bi.prerequisitesCount);
  979. evaluationContext.armyReward += bi.armyStrength * 1.5;
  980. }
  981. else
  982. {
  983. auto potentialUpgradeValue = evaluationContext.evaluator.getUpgradeArmyReward(buildThis.town, bi);
  984. evaluationContext.addNonCriticalStrategicalValue(potentialUpgradeValue / 10000.0f / (float)bi.prerequisitesCount);
  985. if(bi.id.isDwelling())
  986. evaluationContext.armyReward += bi.armyStrength - evaluationContext.evaluator.aiNk->armyManager->evaluateStackPower(bi.baseCreatureID.toCreature(), bi.creatureGrowth);
  987. else //This is for prerequisite-buildings
  988. evaluationContext.armyReward += evaluationContext.evaluator.aiNk->armyManager->evaluateStackPower(bi.baseCreatureID.toCreature(), bi.creatureGrowth);
  989. if(alreadyOwn)
  990. evaluationContext.armyReward /= bi.buildCostWithPrerequisites.marketValue();
  991. }
  992. }
  993. else if(bi.id == BuildingID::CITADEL || bi.id == BuildingID::CASTLE)
  994. {
  995. evaluationContext.addNonCriticalStrategicalValue(buildThis.town->creatures.size() * 0.2f);
  996. evaluationContext.armyReward += buildThis.townInfo.armyStrength / 2;
  997. }
  998. else if(bi.id >= BuildingID::MAGES_GUILD_1 && bi.id <= BuildingID::MAGES_GUILD_5)
  999. {
  1000. evaluationContext.skillReward += 2 * bi.id.getMagesGuildLevel();
  1001. if (!alreadyOwn && evaluationContext.evaluator.aiNk->cc->canBuildStructure(buildThis.town, highestMageGuildPossible) != EBuildingState::FORBIDDEN)
  1002. {
  1003. for (auto hero : evaluationContext.evaluator.aiNk->cc->getHeroesInfo())
  1004. {
  1005. if(hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + hero->getPrimSkillLevel(PrimarySkill::KNOWLEDGE) > hero->getPrimSkillLevel(PrimarySkill::ATTACK) + hero->getPrimSkillLevel(PrimarySkill::DEFENSE)
  1006. && hero->manaLimit() > 30)
  1007. evaluationContext.armyReward += hero->getArmyCost();
  1008. }
  1009. }
  1010. }
  1011. int sameTownBonus = 0;
  1012. for (auto town : evaluationContext.evaluator.aiNk->cc->getTownsInfo())
  1013. {
  1014. if (buildThis.town->getFaction() == town->getFaction())
  1015. sameTownBonus += town->getTownLevel();
  1016. }
  1017. evaluationContext.armyReward *= sameTownBonus;
  1018. if(evaluationContext.goldReward)
  1019. {
  1020. auto goldPressure = evaluationContext.evaluator.aiNk->buildAnalyzer->getGoldPressure();
  1021. evaluationContext.addNonCriticalStrategicalValue(evaluationContext.goldReward * goldPressure / 3500.0f / bi.prerequisitesCount);
  1022. }
  1023. if(bi.isMissingResources && bi.prerequisitesCount == 1)
  1024. {
  1025. evaluationContext.strategicalValue /= 3;
  1026. evaluationContext.movementCostByRole[evaluationContext.heroRole] += 5;
  1027. evaluationContext.turn += 5;
  1028. }
  1029. }
  1030. };
  1031. uint64_t RewardEvaluator::getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const
  1032. {
  1033. if(aiNk->buildAnalyzer->isBuilt(town->getFactionID(), bi.id))
  1034. return 0;
  1035. auto creaturesToUpgrade = aiNk->armyManager->getTotalCreaturesAvailable(bi.baseCreatureID);
  1036. auto upgradedPower = aiNk->armyManager->evaluateStackPower(bi.creatureID.toCreature(), creaturesToUpgrade.count);
  1037. return upgradedPower - creaturesToUpgrade.power;
  1038. }
  1039. PriorityEvaluator::PriorityEvaluator(const Nullkiller * aiNk)
  1040. :aiNk(aiNk)
  1041. {
  1042. initVisitTile();
  1043. evaluationContextBuilders.push_back(std::make_shared<ExecuteHeroChainEvaluationContextBuilder>(aiNk));
  1044. evaluationContextBuilders.push_back(std::make_shared<BuildThisEvaluationContextBuilder>());
  1045. evaluationContextBuilders.push_back(std::make_shared<ClusterEvaluationContextBuilder>(aiNk));
  1046. evaluationContextBuilders.push_back(std::make_shared<HeroExchangeEvaluator>());
  1047. evaluationContextBuilders.push_back(std::make_shared<ArmyUpgradeEvaluator>());
  1048. evaluationContextBuilders.push_back(std::make_shared<DefendTownEvaluator>());
  1049. evaluationContextBuilders.push_back(std::make_shared<ExchangeSwapTownHeroesContextBuilder>());
  1050. evaluationContextBuilders.push_back(std::make_shared<DismissHeroContextBuilder>(aiNk));
  1051. evaluationContextBuilders.push_back(std::make_shared<StayAtTownManaRecoveryEvaluator>());
  1052. evaluationContextBuilders.push_back(std::make_shared<ExplorePointEvaluator>());
  1053. }
  1054. EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal) const
  1055. {
  1056. Goals::TGoalVec parts;
  1057. EvaluationContext context(aiNk);
  1058. if(goal->goalType == Goals::COMPOSITION)
  1059. {
  1060. parts = goal->decompose(aiNk);
  1061. }
  1062. else
  1063. {
  1064. parts.push_back(goal);
  1065. }
  1066. for(auto subgoal : parts)
  1067. {
  1068. context.goldCost += subgoal->goldCost;
  1069. context.buildingCost += subgoal->buildingCost;
  1070. for(auto builder : evaluationContextBuilders)
  1071. {
  1072. builder->buildEvaluationContext(context, subgoal);
  1073. }
  1074. }
  1075. return context;
  1076. }
  1077. float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
  1078. {
  1079. auto evaluationContext = buildEvaluationContext(task);
  1080. int rewardType = (evaluationContext.goldReward > 0 ? 1 : 0)
  1081. + (evaluationContext.armyReward > 0 ? 1 : 0)
  1082. + (evaluationContext.skillReward > 0 ? 1 : 0)
  1083. + (evaluationContext.strategicalValue > 0 ? 1 : 0);
  1084. float goldRewardVsMovement = evaluationContext.goldReward / std::log2f(2 + evaluationContext.movementCost * 10);
  1085. double result = 0;
  1086. if (aiNk->settings->isUseFuzzy())
  1087. {
  1088. float fuzzyResult = 0;
  1089. try
  1090. {
  1091. armyLossRatioVariable->setValue(evaluationContext.armyLossRatio);
  1092. heroRoleVariable->setValue(evaluationContext.heroRole);
  1093. mainTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::MAIN]);
  1094. scoutTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::SCOUT]);
  1095. goldRewardVsMovementVariable->setValue(goldRewardVsMovement);
  1096. armyRewardVariable->setValue(evaluationContext.armyReward);
  1097. armyGrowthVariable->setValue(evaluationContext.armyGrowth);
  1098. skillRewardVariable->setValue(evaluationContext.skillReward);
  1099. dangerVariable->setValue(evaluationContext.danger);
  1100. rewardTypeVariable->setValue(rewardType);
  1101. closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio);
  1102. strategicalValueVariable->setValue(evaluationContext.strategicalValue);
  1103. goldPressureVariable->setValue(aiNk->buildAnalyzer->getGoldPressure());
  1104. goldCostVariable->setValue(evaluationContext.goldCost / ((float)aiNk->getFreeResources()[EGameResID::GOLD] + (float)aiNk->buildAnalyzer->getDailyIncome()[EGameResID::GOLD] + 1.0f));
  1105. turnVariable->setValue(evaluationContext.turn);
  1106. fearVariable->setValue(evaluationContext.enemyHeroDangerRatio);
  1107. engine->process();
  1108. fuzzyResult = value->getValue();
  1109. }
  1110. catch (fl::Exception& fe)
  1111. {
  1112. logAi->error("evaluate VisitTile: %s", fe.getWhat());
  1113. }
  1114. result = fuzzyResult;
  1115. }
  1116. else
  1117. {
  1118. float score = 0;
  1119. bool currentPositionThreatened = false;
  1120. if (task->hero)
  1121. {
  1122. auto currentTileThreat = aiNk->dangerHitMap->getTileThreat(task->hero->visitablePos());
  1123. if (currentTileThreat.fastestDanger.turn < 1 && currentTileThreat.fastestDanger.danger > task->hero->getTotalStrength())
  1124. currentPositionThreatened = true;
  1125. }
  1126. if (priorityTier == PriorityTier::FAR_HUNTER_GATHER && currentPositionThreatened == true)
  1127. {
  1128. #if NK2AI_TRACE_LEVEL >= 2
  1129. logAi->trace("Skip FAR_HUNTER_GATHER because hero would be threatened.");
  1130. #endif
  1131. return 0;
  1132. }
  1133. const bool amIInDanger = aiNk->cc->getTownsInfo().empty();
  1134. // TODO: Mircea: Shouldn't it default to 0 instead of 1.0 in the end?
  1135. const float maxWillingToLose = amIInDanger ? 1 : aiNk->settings->getMaxArmyLossTarget() * evaluationContext.powerRatio > 0 ? aiNk->settings->getMaxArmyLossTarget() * evaluationContext.powerRatio : 1.0;
  1136. float dangerThreshold = 1;
  1137. dangerThreshold *= evaluationContext.powerRatio > 0 ? evaluationContext.powerRatio : 1.0;
  1138. bool arriveNextWeek = false;
  1139. if (aiNk->cc->getDate(Date::DAY_OF_WEEK) + evaluationContext.turn > 7 && priorityTier < PriorityTier::FAR_KILL)
  1140. arriveNextWeek = true;
  1141. #if NK2AI_TRACE_LEVEL >= 2
  1142. logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, maxWillingToLose: %f, turn: %d, turns main: %f, scout: %f, armyInvolvement: %f, goldRewardVsMovement: %f, cost: %d, armyReward: %f, armyGrowth: %f skillReward: %f danger: %d, threatTurns: %d, threat: %d, heroRole: %s, strategicalValue: %f, conquestValue: %f closestWayRatio: %f, enemyHeroDangerRatio: %f, dangerThreshold: %f explorePriority: %d isDefend: %d isEnemy: %d arriveNextWeek: %d powerRatio: %f",
  1143. priorityTier,
  1144. task->toString(),
  1145. evaluationContext.armyLossRatio,
  1146. maxWillingToLose,
  1147. (int)evaluationContext.turn,
  1148. evaluationContext.movementCostByRole[HeroRole::MAIN],
  1149. evaluationContext.movementCostByRole[HeroRole::SCOUT],
  1150. evaluationContext.armyInvolvement,
  1151. goldRewardVsMovement,
  1152. evaluationContext.goldCost,
  1153. evaluationContext.armyReward,
  1154. evaluationContext.armyGrowth,
  1155. evaluationContext.skillReward,
  1156. evaluationContext.danger,
  1157. evaluationContext.threatTurns,
  1158. evaluationContext.threat,
  1159. evaluationContext.heroRole == HeroRole::MAIN ? "main" : "scout",
  1160. evaluationContext.strategicalValue,
  1161. evaluationContext.conquestValue,
  1162. evaluationContext.closestWayRatio,
  1163. evaluationContext.enemyHeroDangerRatio,
  1164. dangerThreshold,
  1165. evaluationContext.explorePriority,
  1166. evaluationContext.isDefend,
  1167. evaluationContext.isEnemy,
  1168. arriveNextWeek,
  1169. evaluationContext.powerRatio);
  1170. #endif
  1171. switch (priorityTier)
  1172. {
  1173. case PriorityTier::INSTAKILL: //Take towns / kill heroes in immediate reach
  1174. {
  1175. if (evaluationContext.turn > 0 || evaluationContext.isExchange)
  1176. return 0;
  1177. if (evaluationContext.movementCost >= 1)
  1178. return 0;
  1179. if (evaluationContext.defenseValue < 2 && evaluationContext.enemyHeroDangerRatio > dangerThreshold)
  1180. return 0;
  1181. if(evaluationContext.conquestValue > 0)
  1182. score = evaluationContext.armyInvolvement;
  1183. if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > dangerThreshold && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !aiNk->cc->getTownsInfo().empty()))
  1184. return 0;
  1185. if (maxWillingToLose - evaluationContext.armyLossRatio < 0)
  1186. return 0;
  1187. if (evaluationContext.movementCost > 0)
  1188. score /= evaluationContext.movementCost;
  1189. break;
  1190. }
  1191. case PriorityTier::INSTADEFEND: //Defend immediately threatened towns
  1192. {
  1193. //No point defending if we don't have defensive-structures
  1194. if (evaluationContext.defenseValue < 2)
  1195. return 0;
  1196. if (maxWillingToLose - evaluationContext.armyLossRatio < 0)
  1197. return 0;
  1198. if (evaluationContext.closestWayRatio < 1.0)
  1199. return 0;
  1200. if (evaluationContext.isEnemy && evaluationContext.turn > 0)
  1201. return 0;
  1202. if (evaluationContext.isDefend && evaluationContext.threatTurns <= evaluationContext.turn)
  1203. {
  1204. const float OPTIMAL_PERCENTAGE = 0.75f; // We want army to be 75% of the threat
  1205. float optimalStrength = evaluationContext.threat * OPTIMAL_PERCENTAGE;
  1206. // Calculate how far the army is from optimal strength
  1207. float deviation = std::abs(evaluationContext.armyInvolvement - optimalStrength);
  1208. // Convert deviation to a percentage of the threat to normalize it
  1209. float deviationPercentage = deviation / evaluationContext.threat;
  1210. // Calculate score: 1.0 is perfect, decreasing as deviation increases
  1211. score = 1.0f / (1.0f + deviationPercentage);
  1212. // Apply turn penalty to still prefer earlier moves when scores are close
  1213. score = score / (evaluationContext.turn + 1);
  1214. }
  1215. break;
  1216. }
  1217. case PriorityTier::KILL: //Take towns / kill heroes that are further away
  1218. //FALL_THROUGH
  1219. case PriorityTier::FAR_KILL:
  1220. {
  1221. if (evaluationContext.defenseValue < 2 && evaluationContext.enemyHeroDangerRatio > dangerThreshold)
  1222. return 0;
  1223. if (evaluationContext.turn > 0 && evaluationContext.isHero)
  1224. return 0;
  1225. if (arriveNextWeek && evaluationContext.isEnemy)
  1226. return 0;
  1227. if (evaluationContext.conquestValue > 0)
  1228. score = evaluationContext.armyInvolvement;
  1229. if (vstd::isAlmostZero(score) || (evaluationContext.enemyHeroDangerRatio > dangerThreshold && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !aiNk->cc->getTownsInfo().empty()))
  1230. return 0;
  1231. if (maxWillingToLose - evaluationContext.armyLossRatio < 0)
  1232. return 0;
  1233. score *= evaluationContext.closestWayRatio;
  1234. if (evaluationContext.movementCost > 0)
  1235. score /= evaluationContext.movementCost;
  1236. break;
  1237. }
  1238. case PriorityTier::HIGH_PRIO_EXPLORE:
  1239. {
  1240. if (evaluationContext.enemyHeroDangerRatio > dangerThreshold)
  1241. return 0;
  1242. if (evaluationContext.explorePriority != 1)
  1243. return 0;
  1244. if (maxWillingToLose - evaluationContext.armyLossRatio < 0)
  1245. return 0;
  1246. if (vstd::isAlmostZero(evaluationContext.armyLossRatio) && evaluationContext.closestWayRatio < 1.0)
  1247. return 0;
  1248. score = 1000;
  1249. if (evaluationContext.movementCost > 0)
  1250. score /= evaluationContext.movementCost;
  1251. break;
  1252. }
  1253. case PriorityTier::HUNTER_GATHER: //Collect guarded stuff
  1254. //FALL_THROUGH
  1255. case PriorityTier::FAR_HUNTER_GATHER:
  1256. // TODO: Mircea: Should not go to something that gives army if no slots available in the hero, but probably not in the evaluator, but in the finder
  1257. // task.get()->hero->getSlotFor(creature, 7) == false (not sure I get to know which creature is there in Orc Tower building)
  1258. // /// so I can't know for sure if it fits my stacks or not, but at least we can avoid going there with all 7 stacks occupied by other units
  1259. // task.get()->hero->getFreeSlots(7) == 7
  1260. // getDuplicatingSlots(task.get()->hero) == false
  1261. {
  1262. if (evaluationContext.enemyHeroDangerRatio > dangerThreshold && !evaluationContext.isDefend && priorityTier != PriorityTier::FAR_HUNTER_GATHER)
  1263. {
  1264. // logAi->trace("case PriorityTier::FAR_HUNTER_GATHER if 1");
  1265. return 0;
  1266. }
  1267. if (evaluationContext.buildingCost.marketValue() > 0)
  1268. {
  1269. // logAi->trace("case PriorityTier::FAR_HUNTER_GATHER if 2");
  1270. return 0;
  1271. }
  1272. if (priorityTier != PriorityTier::FAR_HUNTER_GATHER && evaluationContext.isDefend && (evaluationContext.enemyHeroDangerRatio > dangerThreshold || evaluationContext.threatTurns > 0 || evaluationContext.turn > 0))
  1273. {
  1274. // logAi->trace("case PriorityTier::FAR_HUNTER_GATHER if 3");
  1275. return 0;
  1276. }
  1277. if (evaluationContext.explorePriority == 3)
  1278. {
  1279. // logAi->trace("case PriorityTier::FAR_HUNTER_GATHER if 4");
  1280. return 0;
  1281. }
  1282. if (priorityTier != PriorityTier::FAR_HUNTER_GATHER && ((evaluationContext.enemyHeroDangerRatio > 0 && arriveNextWeek) || evaluationContext.enemyHeroDangerRatio > dangerThreshold))
  1283. {
  1284. // logAi->trace("case PriorityTier::FAR_HUNTER_GATHER if 5");
  1285. return 0;
  1286. }
  1287. if (maxWillingToLose - evaluationContext.armyLossRatio < 0)
  1288. {
  1289. // logAi->trace("case PriorityTier::FAR_HUNTER_GATHER if 6");
  1290. return 0;
  1291. }
  1292. if (vstd::isAlmostZero(evaluationContext.armyLossRatio) && evaluationContext.closestWayRatio < 1.0)
  1293. {
  1294. // logAi->trace("case PriorityTier::FAR_HUNTER_GATHER if 7");
  1295. return 0;
  1296. }
  1297. score += evaluationContext.strategicalValue * 1000;
  1298. score += evaluationContext.goldReward;
  1299. score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossRatio) * 0.05;
  1300. score += evaluationContext.armyReward;
  1301. score += evaluationContext.armyGrowth;
  1302. score -= evaluationContext.goldCost / 2; // don't include the full cost of School of Magic or others because those locations are beneficial
  1303. score -= evaluationContext.armyInvolvement * evaluationContext.armyLossRatio * 0.1;
  1304. #if NK2AI_TRACE_LEVEL >= 1
  1305. logAi->trace("case PriorityTier::FAR_HUNTER_GATHER score %f, strategicalValue %f, goldReward %f, skillRewardMultiplied %f, armyReward %f, armyGrowth %f, goldCost -%f, armyInvolvementMultiplied -%f, "
  1306. "armyLossPersentage %f, movementCost %f, enemyHeroDangerRatio %f",
  1307. score, evaluationContext.strategicalValue, evaluationContext.goldReward, evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossRatio) * 0.05,
  1308. evaluationContext.armyReward, evaluationContext.armyGrowth, evaluationContext.goldCost, evaluationContext.armyInvolvement * evaluationContext.armyLossRatio,
  1309. evaluationContext.armyLossRatio, evaluationContext.movementCost, evaluationContext.enemyHeroDangerRatio);
  1310. #endif
  1311. if (score > 0)
  1312. {
  1313. // score = 1000;
  1314. if (evaluationContext.movementCost > 0)
  1315. {
  1316. // logAi->trace("case PriorityTier::FAR_HUNTER_GATHER if 8");
  1317. score -= evaluationContext.movementCost / 20 * score; // we expect movement won't be over 20 turns
  1318. }
  1319. // TODO: Mircea: This doesn't make sense at it always seems to be 0. To test
  1320. if(evaluationContext.enemyHeroDangerRatio > 0)
  1321. {
  1322. // logAi->trace("case PriorityTier::FAR_HUNTER_GATHER if 9");
  1323. score *= 1 - evaluationContext.enemyHeroDangerRatio;
  1324. }
  1325. if(evaluationContext.armyLossRatio > 0)
  1326. {
  1327. // logAi->trace("case PriorityTier::FAR_HUNTER_GATHER if 10");
  1328. score *= 1 - evaluationContext.armyLossRatio;
  1329. }
  1330. }
  1331. // logAi->trace("case PriorityTier::FAR_HUNTER_GATHER score final %f", score);
  1332. break;
  1333. }
  1334. case PriorityTier::LOW_PRIO_EXPLORE:
  1335. {
  1336. if (evaluationContext.enemyHeroDangerRatio > dangerThreshold)
  1337. return 0;
  1338. if (evaluationContext.explorePriority != 3)
  1339. return 0;
  1340. if (maxWillingToLose - evaluationContext.armyLossRatio < 0)
  1341. return 0;
  1342. if (evaluationContext.closestWayRatio < 1.0)
  1343. return 0;
  1344. score = 1000;
  1345. if (evaluationContext.movementCost > 0)
  1346. score /= evaluationContext.movementCost;
  1347. break;
  1348. }
  1349. case PriorityTier::DEFEND: //Defend whatever if nothing else is to do
  1350. {
  1351. if (evaluationContext.enemyHeroDangerRatio > dangerThreshold)
  1352. return 0;
  1353. if (evaluationContext.isDefend || evaluationContext.isArmyUpgrade)
  1354. score = evaluationContext.armyInvolvement;
  1355. score /= (evaluationContext.turn + 1);
  1356. break;
  1357. }
  1358. case PriorityTier::BUILDINGS: //For buildings and buying army
  1359. {
  1360. if (maxWillingToLose - evaluationContext.armyLossRatio < 0)
  1361. return 0;
  1362. //If we already have locked resources, we don't look at other buildings
  1363. if (aiNk->getLockedResources().marketValue() > 0)
  1364. return 0;
  1365. score += evaluationContext.conquestValue * 1000;
  1366. score += evaluationContext.strategicalValue * 1000;
  1367. score += evaluationContext.goldReward;
  1368. score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossRatio) * 0.05;
  1369. score += evaluationContext.armyReward;
  1370. score += evaluationContext.armyGrowth;
  1371. if (evaluationContext.buildingCost.marketValue() > 0)
  1372. {
  1373. if (!evaluationContext.isTradeBuilding && aiNk->getFreeResources()[EGameResID::WOOD] - evaluationContext.buildingCost[EGameResID::WOOD] < 5 && aiNk->buildAnalyzer->getDailyIncome()[EGameResID::WOOD] < 1)
  1374. {
  1375. logAi->trace("Should make sure to build market-place instead of %s", task->toString());
  1376. for (auto town : aiNk->cc->getTownsInfo())
  1377. {
  1378. if (!town->hasBuiltResourceMarketplace())
  1379. return 0;
  1380. }
  1381. }
  1382. score += 1000;
  1383. auto resourcesAvailable = evaluationContext.evaluator.aiNk->getFreeResources();
  1384. auto income = aiNk->buildAnalyzer->getDailyIncome();
  1385. if(aiNk->buildAnalyzer->isGoldPressureOverMax())
  1386. score /= evaluationContext.buildingCost.marketValue();
  1387. if (!resourcesAvailable.canAfford(evaluationContext.buildingCost))
  1388. {
  1389. TResources needed = evaluationContext.buildingCost - resourcesAvailable;
  1390. needed.positive();
  1391. int turnsTo = needed.maxPurchasableCount(income);
  1392. bool haveEverythingButGold = true;
  1393. for (GameResID & i : LIBRARY->resourceTypeHandler->getAllObjects())
  1394. {
  1395. if (i != GameResID::GOLD && resourcesAvailable[i] < evaluationContext.buildingCost[i])
  1396. haveEverythingButGold = false;
  1397. }
  1398. if (turnsTo == INT_MAX)
  1399. return 0;
  1400. if (!haveEverythingButGold)
  1401. score /= turnsTo;
  1402. }
  1403. }
  1404. else
  1405. {
  1406. if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend && vstd::isAlmostZero(evaluationContext.conquestValue))
  1407. return 0;
  1408. }
  1409. break;
  1410. }
  1411. }
  1412. result = score;
  1413. //TODO: Figure out the root cause for why evaluationContext.closestWayRatio has become -nan(ind).
  1414. if (std::isnan(result))
  1415. return 0;
  1416. }
  1417. #if NK2AI_TRACE_LEVEL >= 2
  1418. logAi->trace("priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, armyInvolvement: %f, goldRewardVsMovement: %f, cost: %d, armyReward: %f, armyGrowth: %f skillReward: %f danger: %d, threatTurns: %d, threat: %d, heroRole: %s, strategicalValue: %f, conquestValue: %f closestWayRatio: %f, enemyHeroDangerRatio: %f, result %f",
  1419. priorityTier,
  1420. task->toString(),
  1421. evaluationContext.armyLossRatio,
  1422. (int)evaluationContext.turn,
  1423. evaluationContext.movementCostByRole[HeroRole::MAIN],
  1424. evaluationContext.movementCostByRole[HeroRole::SCOUT],
  1425. evaluationContext.armyInvolvement,
  1426. goldRewardVsMovement,
  1427. evaluationContext.goldCost,
  1428. evaluationContext.armyReward,
  1429. evaluationContext.armyGrowth,
  1430. evaluationContext.skillReward,
  1431. evaluationContext.danger,
  1432. evaluationContext.threatTurns,
  1433. evaluationContext.threat,
  1434. evaluationContext.heroRole == HeroRole::MAIN ? "main" : "scout",
  1435. evaluationContext.strategicalValue,
  1436. evaluationContext.conquestValue,
  1437. evaluationContext.closestWayRatio,
  1438. evaluationContext.enemyHeroDangerRatio,
  1439. result);
  1440. #endif
  1441. return result;
  1442. }
  1443. }