PriorityEvaluator.cpp 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076
  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/mapObjectConstructors/AObjectTypeHandler.h"
  14. #include "../../../lib/mapObjectConstructors/CObjectClassesHandler.h"
  15. #include "../../../lib/mapObjectConstructors/CBankInstanceConstructor.h"
  16. #include "../../../lib/mapObjects/MapObjects.h"
  17. #include "../../../lib/CCreatureHandler.h"
  18. #include "../../../lib/VCMI_Lib.h"
  19. #include "../../../lib/StartInfo.h"
  20. #include "../../../CCallback.h"
  21. #include "../../../lib/filesystem/Filesystem.h"
  22. #include "../Goals/ExecuteHeroChain.h"
  23. #include "../Goals/BuildThis.h"
  24. #include "../Goals/ExchangeSwapTownHeroes.h"
  25. #include "../Goals/DismissHero.h"
  26. #include "../Markers/UnlockCluster.h"
  27. #include "../Markers/HeroExchange.h"
  28. #include "../Markers/ArmyUpgrade.h"
  29. #include "../Markers/DefendTown.h"
  30. namespace NKAI
  31. {
  32. #define MIN_AI_STRENGHT (0.5f) //lower when combat AI gets smarter
  33. #define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
  34. const float MIN_CRITICAL_VALUE = 2.0f;
  35. EvaluationContext::EvaluationContext(const Nullkiller * ai)
  36. : movementCost(0.0),
  37. manaCost(0),
  38. danger(0),
  39. closestWayRatio(1),
  40. movementCostByRole(),
  41. skillReward(0),
  42. goldReward(0),
  43. goldCost(0),
  44. armyReward(0),
  45. armyLossPersentage(0),
  46. heroRole(HeroRole::SCOUT),
  47. turn(0),
  48. strategicalValue(0),
  49. evaluator(ai),
  50. enemyHeroDangerRatio(0),
  51. armyGrowth(0)
  52. {
  53. }
  54. void EvaluationContext::addNonCriticalStrategicalValue(float value)
  55. {
  56. vstd::amax(strategicalValue, std::min(value, MIN_CRITICAL_VALUE));
  57. }
  58. PriorityEvaluator::~PriorityEvaluator()
  59. {
  60. delete engine;
  61. }
  62. void PriorityEvaluator::initVisitTile()
  63. {
  64. auto file = CResourceHandler::get()->load(ResourceID("config/ai/object-priorities.txt"))->readAll();
  65. std::string str = std::string((char *)file.first.get(), file.second);
  66. engine = fl::FllImporter().fromString(str);
  67. armyLossPersentageVariable = engine->getInputVariable("armyLoss");
  68. armyGrowthVariable = engine->getInputVariable("armyGrowth");
  69. heroRoleVariable = engine->getInputVariable("heroRole");
  70. dangerVariable = engine->getInputVariable("danger");
  71. turnVariable = engine->getInputVariable("turn");
  72. mainTurnDistanceVariable = engine->getInputVariable("mainTurnDistance");
  73. scoutTurnDistanceVariable = engine->getInputVariable("scoutTurnDistance");
  74. goldRewardVariable = engine->getInputVariable("goldReward");
  75. armyRewardVariable = engine->getInputVariable("armyReward");
  76. skillRewardVariable = engine->getInputVariable("skillReward");
  77. rewardTypeVariable = engine->getInputVariable("rewardType");
  78. closestHeroRatioVariable = engine->getInputVariable("closestHeroRatio");
  79. strategicalValueVariable = engine->getInputVariable("strategicalValue");
  80. goldPreasureVariable = engine->getInputVariable("goldPreasure");
  81. goldCostVariable = engine->getInputVariable("goldCost");
  82. fearVariable = engine->getInputVariable("fear");
  83. value = engine->getOutputVariable("Value");
  84. }
  85. bool isAnotherAi(const CGObjectInstance * obj, const CPlayerSpecificInfoCallback & cb)
  86. {
  87. return obj->getOwner().isValidPlayer()
  88. && cb.getStartInfo()->getIthPlayersSettings(obj->getOwner()).isControlledByAI();
  89. }
  90. int32_t estimateTownIncome(CCallback * cb, const CGObjectInstance * target, const CGHeroInstance * hero)
  91. {
  92. auto relations = cb->getPlayerRelations(hero->tempOwner, target->tempOwner);
  93. if(relations != PlayerRelations::ENEMIES)
  94. return 0; // if we already own it, no additional reward will be received by just visiting it
  95. auto booster = isAnotherAi(target, *cb) ? 1 : 2;
  96. auto town = cb->getTown(target->id);
  97. auto fortLevel = town->fortLevel();
  98. if(town->hasCapitol()) return booster * 2000;
  99. // probably well developed town will have city hall
  100. if(fortLevel == CGTownInstance::CASTLE) return booster * 750;
  101. return booster * (town->hasFort() && town->tempOwner != PlayerColor::NEUTRAL ? booster * 500 : 250);
  102. }
  103. TResources getCreatureBankResources(const CGObjectInstance * target, const CGHeroInstance * hero)
  104. {
  105. //Fixme: unused variable hero
  106. auto objectInfo = VLC->objtypeh->getHandlerFor(target->ID, target->subID)->getObjectInfo(target->appearance);
  107. CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
  108. auto resources = bankInfo->getPossibleResourcesReward();
  109. TResources result = TResources();
  110. int sum = 0;
  111. for(auto & reward : resources)
  112. {
  113. result += reward.data * reward.chance;
  114. sum += reward.chance;
  115. }
  116. return sum > 1 ? result / sum : result;
  117. }
  118. uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero)
  119. {
  120. auto objectInfo = VLC->objtypeh->getHandlerFor(target->ID, target->subID)->getObjectInfo(target->appearance);
  121. CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
  122. auto creatures = bankInfo->getPossibleCreaturesReward();
  123. uint64_t result = 0;
  124. const auto& slots = hero->Slots();
  125. ui64 weakestStackPower = 0;
  126. if (slots.size() >= GameConstants::ARMY_SIZE)
  127. {
  128. //No free slot, we might discard our weakest stack
  129. weakestStackPower = std::numeric_limits<ui64>().max();
  130. for (const auto & stack : slots)
  131. {
  132. vstd::amin(weakestStackPower, stack.second->getPower());
  133. }
  134. }
  135. for (auto c : creatures)
  136. {
  137. //Only if hero has slot for this creature in the army
  138. auto ccre = dynamic_cast<const CCreature*>(c.data.type);
  139. if (hero->getSlotFor(ccre).validSlot())
  140. {
  141. result += (c.data.type->getAIValue() * c.data.count) * c.chance;
  142. }
  143. else
  144. {
  145. //we will need to discard the weakest stack
  146. result += (c.data.type->getAIValue() * c.data.count - weakestStackPower) * c.chance;
  147. }
  148. }
  149. result /= 100; //divide by total chance
  150. return result;
  151. }
  152. uint64_t getDwellingArmyValue(CCallback * cb, const CGObjectInstance * target, bool checkGold)
  153. {
  154. auto dwelling = dynamic_cast<const CGDwelling *>(target);
  155. uint64_t score = 0;
  156. for(auto & creLevel : dwelling->creatures)
  157. {
  158. if(creLevel.first && creLevel.second.size())
  159. {
  160. auto creature = creLevel.second.back().toCreature();
  161. auto creaturesAreFree = creature->getLevel() == 1;
  162. if(!creaturesAreFree && checkGold && !cb->getResourceAmount().canAfford(creature->getFullRecruitCost() * creLevel.first))
  163. continue;
  164. score += creature->getAIValue() * creLevel.first;
  165. }
  166. }
  167. return score;
  168. }
  169. uint64_t getDwellingArmyGrowth(CCallback * cb, const CGObjectInstance * target, PlayerColor myColor)
  170. {
  171. auto dwelling = dynamic_cast<const CGDwelling *>(target);
  172. uint64_t score = 0;
  173. if(dwelling->getOwner() == myColor)
  174. return 0;
  175. for(auto & creLevel : dwelling->creatures)
  176. {
  177. if(creLevel.second.size())
  178. {
  179. auto creature = creLevel.second.back().toCreature();
  180. score += creature->getAIValue() * creature->getGrowth();
  181. }
  182. }
  183. return score;
  184. }
  185. int getDwellingArmyCost(const CGObjectInstance * target)
  186. {
  187. auto dwelling = dynamic_cast<const CGDwelling *>(target);
  188. int cost = 0;
  189. for(auto & creLevel : dwelling->creatures)
  190. {
  191. if(creLevel.first && creLevel.second.size())
  192. {
  193. auto creature = creLevel.second.back().toCreature();
  194. auto creaturesAreFree = creature->getLevel() == 1;
  195. if(!creaturesAreFree)
  196. cost += creature->getRecruitCost(EGameResID::GOLD) * creLevel.first;
  197. }
  198. }
  199. return cost;
  200. }
  201. uint64_t evaluateArtifactArmyValue(CArtifactInstance * art)
  202. {
  203. if(art->artType->getId() == ArtifactID::SPELL_SCROLL)
  204. return 1500;
  205. auto statsValue =
  206. 10 * art->valOfBonuses(BonusType::MOVEMENT, 1)
  207. + 1200 * art->valOfBonuses(BonusType::STACKS_SPEED)
  208. + 700 * art->valOfBonuses(BonusType::MORALE)
  209. + 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, PrimarySkill::ATTACK)
  210. + 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, PrimarySkill::DEFENSE)
  211. + 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, PrimarySkill::KNOWLEDGE)
  212. + 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, PrimarySkill::SPELL_POWER)
  213. + 500 * art->valOfBonuses(BonusType::LUCK);
  214. auto classValue = 0;
  215. switch(art->artType->aClass)
  216. {
  217. case CArtifact::EartClass::ART_MINOR:
  218. classValue = 1000;
  219. break;
  220. case CArtifact::EartClass::ART_MAJOR:
  221. classValue = 3000;
  222. break;
  223. case CArtifact::EartClass::ART_RELIC:
  224. case CArtifact::EartClass::ART_SPECIAL:
  225. classValue = 8000;
  226. break;
  227. }
  228. return statsValue > classValue ? statsValue : classValue;
  229. }
  230. uint64_t RewardEvaluator::getArmyReward(
  231. const CGObjectInstance * target,
  232. const CGHeroInstance * hero,
  233. const CCreatureSet * army,
  234. bool checkGold) const
  235. {
  236. const float enemyArmyEliminationRewardRatio = 0.5f;
  237. if(!target)
  238. return 0;
  239. switch(target->ID)
  240. {
  241. case Obj::HILL_FORT:
  242. return ai->armyManager->calculateCreaturesUpgrade(army, target, ai->cb->getResourceAmount()).upgradeValue;
  243. case Obj::CREATURE_BANK:
  244. return getCreatureBankArmyReward(target, hero);
  245. case Obj::CREATURE_GENERATOR1:
  246. case Obj::CREATURE_GENERATOR2:
  247. case Obj::CREATURE_GENERATOR3:
  248. case Obj::CREATURE_GENERATOR4:
  249. return getDwellingArmyValue(ai->cb.get(), target, checkGold);
  250. case Obj::CRYPT:
  251. case Obj::SHIPWRECK:
  252. case Obj::SHIPWRECK_SURVIVOR:
  253. case Obj::WARRIORS_TOMB:
  254. return 1000;
  255. case Obj::ARTIFACT:
  256. return evaluateArtifactArmyValue(dynamic_cast<const CGArtifact *>(target)->storedArtifact);
  257. case Obj::DRAGON_UTOPIA:
  258. return 10000;
  259. case Obj::HERO:
  260. return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
  261. ? enemyArmyEliminationRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->getArmyStrength()
  262. : 0;
  263. case Obj::PANDORAS_BOX:
  264. return 5000;
  265. default:
  266. return 0;
  267. }
  268. }
  269. uint64_t RewardEvaluator::getArmyGrowth(
  270. const CGObjectInstance * target,
  271. const CGHeroInstance * hero,
  272. const CCreatureSet * army) const
  273. {
  274. if(!target)
  275. return 0;
  276. switch(target->ID)
  277. {
  278. case Obj::TOWN:
  279. {
  280. auto town = dynamic_cast<const CGTownInstance *>(target);
  281. auto fortLevel = town->fortLevel();
  282. auto neutral = !town->getOwner().isValidPlayer();
  283. auto booster = isAnotherAi(town, *ai->cb) || neutral ? 1 : 2;
  284. if(fortLevel < CGTownInstance::CITADEL)
  285. return town->hasFort() ? booster * 500 : 0;
  286. else
  287. return booster * (fortLevel == CGTownInstance::CASTLE ? 5000 : 2000);
  288. }
  289. case Obj::CREATURE_GENERATOR1:
  290. case Obj::CREATURE_GENERATOR2:
  291. case Obj::CREATURE_GENERATOR3:
  292. case Obj::CREATURE_GENERATOR4:
  293. return getDwellingArmyGrowth(ai->cb.get(), target, hero->getOwner());
  294. case Obj::ARTIFACT:
  295. // 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.
  296. return 0;
  297. default:
  298. return 0;
  299. }
  300. }
  301. int RewardEvaluator::getGoldCost(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army) const
  302. {
  303. if(!target)
  304. return 0;
  305. if(auto * m = dynamic_cast<const IMarket *>(target))
  306. {
  307. if(m->allowsTrade(EMarketMode::RESOURCE_SKILL))
  308. return 2000;
  309. }
  310. switch(target->ID)
  311. {
  312. case Obj::HILL_FORT:
  313. return ai->armyManager->calculateCreaturesUpgrade(army, target, ai->cb->getResourceAmount()).upgradeCost[EGameResID::GOLD];
  314. case Obj::SCHOOL_OF_MAGIC:
  315. case Obj::SCHOOL_OF_WAR:
  316. return 1000;
  317. case Obj::CREATURE_GENERATOR1:
  318. case Obj::CREATURE_GENERATOR2:
  319. case Obj::CREATURE_GENERATOR3:
  320. case Obj::CREATURE_GENERATOR4:
  321. return getDwellingArmyCost(target);
  322. default:
  323. return 0;
  324. }
  325. }
  326. float RewardEvaluator::getEnemyHeroStrategicalValue(const CGHeroInstance * enemy) const
  327. {
  328. auto objectsUnderTreat = ai->dangerHitMap->getOneTurnAccessibleObjects(enemy);
  329. float objectValue = 0;
  330. for(auto obj : objectsUnderTreat)
  331. {
  332. vstd::amax(objectValue, getStrategicalValue(obj));
  333. }
  334. /*
  335. 1. If an enemy hero can attack nearby object, it's not useful to capture the object on our own.
  336. Killing the hero is almost as important (0.9) as capturing the object itself.
  337. 2. The formula quickly approaches 1.0 as hero level increases,
  338. but higher level always means higher value and the minimal value for level 1 hero is 0.5
  339. */
  340. return std::min(1.5f, objectValue * 0.9f + (1.5f - (1.5f / (1 + enemy->level))));
  341. }
  342. float RewardEvaluator::getResourceRequirementStrength(int resType) const
  343. {
  344. TResources requiredResources = ai->buildAnalyzer->getResourcesRequiredNow();
  345. TResources dailyIncome = ai->buildAnalyzer->getDailyIncome();
  346. if(requiredResources[resType] == 0)
  347. return 0;
  348. if(dailyIncome[resType] == 0)
  349. return 1.0f;
  350. float ratio = (float)requiredResources[resType] / dailyIncome[resType] / 2;
  351. return std::min(ratio, 1.0f);
  352. }
  353. float RewardEvaluator::getTotalResourceRequirementStrength(int resType) const
  354. {
  355. TResources requiredResources = ai->buildAnalyzer->getTotalResourcesRequired();
  356. TResources dailyIncome = ai->buildAnalyzer->getDailyIncome();
  357. if(requiredResources[resType] == 0)
  358. return 0;
  359. float ratio = dailyIncome[resType] == 0
  360. ? (float)requiredResources[resType] / 50.0f
  361. : (float)requiredResources[resType] / dailyIncome[resType] / 50.0f;
  362. return std::min(ratio, 1.0f);
  363. }
  364. uint64_t RewardEvaluator::townArmyGrowth(const CGTownInstance * town) const
  365. {
  366. uint64_t result = 0;
  367. for(auto creatureInfo : town->creatures)
  368. {
  369. if(creatureInfo.second.empty())
  370. continue;
  371. auto creature = creatureInfo.second.back().toCreature();
  372. result += creature->getAIValue() * town->getGrowthInfo(creature->getLevel() - 1).totalGrowth();
  373. }
  374. return result;
  375. }
  376. float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) const
  377. {
  378. if(!target)
  379. return 0;
  380. switch(target->ID)
  381. {
  382. case Obj::MINE:
  383. return target->subID == GameResID(EGameResID::GOLD)
  384. ? 0.5f
  385. : 0.4f * getTotalResourceRequirementStrength(target->subID) + 0.1f * getResourceRequirementStrength(target->subID);
  386. case Obj::RESOURCE:
  387. return target->subID == GameResID(EGameResID::GOLD)
  388. ? 0
  389. : 0.2f * getTotalResourceRequirementStrength(target->subID) + 0.4f * getResourceRequirementStrength(target->subID);
  390. case Obj::CREATURE_BANK:
  391. {
  392. auto resourceReward = getCreatureBankResources(target, nullptr);
  393. float sum = 0.0f;
  394. for (TResources::nziterator it (resourceReward); it.valid(); it++)
  395. {
  396. //Evaluate resources used for construction. Gold is evaluated separately.
  397. if (it->resType != EGameResID::GOLD)
  398. {
  399. sum += 0.1f * getResourceRequirementStrength(it->resType);
  400. }
  401. }
  402. return sum;
  403. }
  404. case Obj::TOWN:
  405. {
  406. if(ai->buildAnalyzer->getDevelopmentInfo().empty())
  407. return 10.0f;
  408. auto town = dynamic_cast<const CGTownInstance *>(target);
  409. if(town->getOwner() == ai->playerID)
  410. {
  411. auto armyIncome = townArmyGrowth(town);
  412. auto dailyIncome = town->dailyIncome()[EGameResID::GOLD];
  413. return std::min(1.0f, std::sqrt(armyIncome / 40000.0f)) + std::min(0.3f, dailyIncome / 10000.0f);
  414. }
  415. auto fortLevel = town->fortLevel();
  416. auto booster = isAnotherAi(town, *ai->cb) ? 0.3f : 0.7f;
  417. if(town->hasCapitol()) return booster;
  418. if(fortLevel < CGTownInstance::CITADEL)
  419. return booster * (town->hasFort() ? 0.6 : 0.4);
  420. else
  421. return booster * (fortLevel == CGTownInstance::CASTLE ? 0.9 : 0.8);
  422. }
  423. case Obj::HERO:
  424. return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
  425. ? getEnemyHeroStrategicalValue(dynamic_cast<const CGHeroInstance *>(target))
  426. : 0;
  427. default:
  428. return 0;
  429. }
  430. }
  431. float RewardEvaluator::evaluateWitchHutSkillScore(const CGWitchHut * hut, const CGHeroInstance * hero, HeroRole role) const
  432. {
  433. if(!hut->wasVisited(hero->tempOwner))
  434. return role == HeroRole::SCOUT ? 2 : 0;
  435. auto skill = SecondarySkill(hut->ability);
  436. if(hero->getSecSkillLevel(skill) != SecSkillLevel::NONE
  437. || hero->secSkills.size() >= GameConstants::SKILL_PER_HERO)
  438. return 0;
  439. auto score = ai->heroManager->evaluateSecSkill(skill, hero);
  440. return score >= 2 ? (role == HeroRole::MAIN ? 10 : 4) : score;
  441. }
  442. float RewardEvaluator::getSkillReward(const CGObjectInstance * target, const CGHeroInstance * hero, HeroRole role) const
  443. {
  444. const float enemyHeroEliminationSkillRewardRatio = 0.5f;
  445. if(!target)
  446. return 0;
  447. switch(target->ID)
  448. {
  449. case Obj::STAR_AXIS:
  450. case Obj::SCHOLAR:
  451. case Obj::SCHOOL_OF_MAGIC:
  452. case Obj::SCHOOL_OF_WAR:
  453. case Obj::GARDEN_OF_REVELATION:
  454. case Obj::MARLETTO_TOWER:
  455. case Obj::MERCENARY_CAMP:
  456. case Obj::SHRINE_OF_MAGIC_GESTURE:
  457. case Obj::SHRINE_OF_MAGIC_INCANTATION:
  458. case Obj::TREE_OF_KNOWLEDGE:
  459. return 1;
  460. case Obj::LEARNING_STONE:
  461. return 1.0f / std::sqrt(hero->level);
  462. case Obj::ARENA:
  463. case Obj::SHRINE_OF_MAGIC_THOUGHT:
  464. return 2;
  465. case Obj::LIBRARY_OF_ENLIGHTENMENT:
  466. return 8;
  467. case Obj::WITCH_HUT:
  468. return evaluateWitchHutSkillScore(dynamic_cast<const CGWitchHut *>(target), hero, role);
  469. case Obj::PANDORAS_BOX:
  470. //Can contains experience, spells, or skills (only on custom maps)
  471. return 2.5f;
  472. case Obj::HERO:
  473. return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
  474. ? enemyHeroEliminationSkillRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->level
  475. : 0;
  476. default:
  477. return 0;
  478. }
  479. }
  480. const HitMapInfo & RewardEvaluator::getEnemyHeroDanger(const int3 & tile, uint8_t turn) const
  481. {
  482. auto & treatNode = ai->dangerHitMap->getTileTreat(tile);
  483. if(treatNode.maximumDanger.danger == 0)
  484. return HitMapInfo::NoTreat;
  485. if(treatNode.maximumDanger.turn <= turn)
  486. return treatNode.maximumDanger;
  487. return treatNode.fastestDanger.turn <= turn ? treatNode.fastestDanger : HitMapInfo::NoTreat;
  488. }
  489. int32_t getArmyCost(const CArmedInstance * army)
  490. {
  491. int32_t value = 0;
  492. for(auto stack : army->Slots())
  493. {
  494. value += stack.second->getCreatureID().toCreature()->getRecruitCost(EGameResID::GOLD) * stack.second->count;
  495. }
  496. return value;
  497. }
  498. int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CGHeroInstance * hero) const
  499. {
  500. if(!target)
  501. return 0;
  502. const int dailyIncomeMultiplier = 5;
  503. const float enemyArmyEliminationGoldRewardRatio = 0.2f;
  504. const int32_t heroEliminationBonus = GameConstants::HERO_GOLD_COST / 2;
  505. auto isGold = target->subID == GameResID(EGameResID::GOLD); // TODO: other resorces could be sold but need to evaluate market power
  506. switch(target->ID)
  507. {
  508. case Obj::RESOURCE:
  509. return isGold ? 600 : 100;
  510. case Obj::TREASURE_CHEST:
  511. return 1500;
  512. case Obj::WATER_WHEEL:
  513. return 1000;
  514. case Obj::TOWN:
  515. return dailyIncomeMultiplier * estimateTownIncome(ai->cb.get(), target, hero);
  516. case Obj::MINE:
  517. case Obj::ABANDONED_MINE:
  518. return dailyIncomeMultiplier * (isGold ? 1000 : 75);
  519. case Obj::MYSTICAL_GARDEN:
  520. case Obj::WINDMILL:
  521. return 100;
  522. case Obj::CAMPFIRE:
  523. return 800;
  524. case Obj::WAGON:
  525. return 100;
  526. case Obj::CREATURE_BANK:
  527. return getCreatureBankResources(target, hero)[EGameResID::GOLD];
  528. case Obj::CRYPT:
  529. case Obj::DERELICT_SHIP:
  530. return 3000;
  531. case Obj::DRAGON_UTOPIA:
  532. return 10000;
  533. case Obj::SEA_CHEST:
  534. return 1500;
  535. case Obj::PANDORAS_BOX:
  536. return 2500;
  537. case Obj::PRISON:
  538. //Objectively saves us 2500 to hire hero
  539. return GameConstants::HERO_GOLD_COST;
  540. case Obj::HERO:
  541. return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
  542. ? heroEliminationBonus + enemyArmyEliminationGoldRewardRatio * getArmyCost(dynamic_cast<const CGHeroInstance *>(target))
  543. : 0;
  544. default:
  545. return 0;
  546. }
  547. }
  548. class HeroExchangeEvaluator : public IEvaluationContextBuilder
  549. {
  550. public:
  551. virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  552. {
  553. if(task->goalType != Goals::HERO_EXCHANGE)
  554. return;
  555. Goals::HeroExchange & heroExchange = dynamic_cast<Goals::HeroExchange &>(*task);
  556. uint64_t armyStrength = heroExchange.getReinforcementArmyStrength();
  557. evaluationContext.addNonCriticalStrategicalValue(2.0f * armyStrength / (float)heroExchange.hero.get()->getArmyStrength());
  558. evaluationContext.heroRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(heroExchange.hero.get());
  559. }
  560. };
  561. class ArmyUpgradeEvaluator : public IEvaluationContextBuilder
  562. {
  563. public:
  564. virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  565. {
  566. if(task->goalType != Goals::ARMY_UPGRADE)
  567. return;
  568. Goals::ArmyUpgrade & armyUpgrade = dynamic_cast<Goals::ArmyUpgrade &>(*task);
  569. uint64_t upgradeValue = armyUpgrade.getUpgradeValue();
  570. evaluationContext.armyReward += upgradeValue;
  571. evaluationContext.addNonCriticalStrategicalValue(upgradeValue / (float)armyUpgrade.hero->getArmyStrength());
  572. }
  573. };
  574. void addTileDanger(EvaluationContext & evaluationContext, const int3 & tile, uint8_t turn, uint64_t ourStrength)
  575. {
  576. HitMapInfo enemyDanger = evaluationContext.evaluator.getEnemyHeroDanger(tile, turn);
  577. if(enemyDanger.danger)
  578. {
  579. auto dangerRatio = enemyDanger.danger / (double)ourStrength;
  580. auto enemyHero = evaluationContext.evaluator.ai->cb->getObj(enemyDanger.hero.hid, false);
  581. bool isAI = enemyHero && isAnotherAi(enemyHero, *evaluationContext.evaluator.ai->cb);
  582. if(isAI)
  583. {
  584. dangerRatio *= 1.5; // lets make AI bit more afraid of other AI.
  585. }
  586. vstd::amax(evaluationContext.enemyHeroDangerRatio, dangerRatio);
  587. }
  588. }
  589. class DefendTownEvaluator : public IEvaluationContextBuilder
  590. {
  591. public:
  592. virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  593. {
  594. if(task->goalType != Goals::DEFEND_TOWN)
  595. return;
  596. Goals::DefendTown & defendTown = dynamic_cast<Goals::DefendTown &>(*task);
  597. const CGTownInstance * town = defendTown.town;
  598. auto & treat = defendTown.getTreat();
  599. auto strategicalValue = evaluationContext.evaluator.getStrategicalValue(town);
  600. float multiplier = 1;
  601. if(treat.turn < defendTown.getTurn())
  602. multiplier /= 1 + (defendTown.getTurn() - treat.turn);
  603. multiplier /= 1.0f + treat.turn / 5.0f;
  604. if(defendTown.getTurn() > 0 && defendTown.isContrAttack())
  605. {
  606. auto ourSpeed = defendTown.hero->movementPointsLimit(true);
  607. auto enemySpeed = treat.hero->movementPointsLimit(true);
  608. if(enemySpeed > ourSpeed) multiplier *= 0.7f;
  609. }
  610. auto dailyIncome = town->dailyIncome()[EGameResID::GOLD];
  611. auto armyGrowth = evaluationContext.evaluator.townArmyGrowth(town);
  612. evaluationContext.armyGrowth += armyGrowth * multiplier;
  613. evaluationContext.goldReward += dailyIncome * 5 * multiplier;
  614. if(evaluationContext.evaluator.ai->buildAnalyzer->getDevelopmentInfo().size() == 1)
  615. vstd::amax(evaluationContext.strategicalValue, 2.5f * multiplier * strategicalValue);
  616. else
  617. evaluationContext.addNonCriticalStrategicalValue(1.7f * multiplier * strategicalValue);
  618. vstd::amax(evaluationContext.danger, defendTown.getTreat().danger);
  619. addTileDanger(evaluationContext, town->visitablePos(), defendTown.getTurn(), defendTown.getDefenceStrength());
  620. }
  621. };
  622. class ExecuteHeroChainEvaluationContextBuilder : public IEvaluationContextBuilder
  623. {
  624. private:
  625. const Nullkiller * ai;
  626. public:
  627. ExecuteHeroChainEvaluationContextBuilder(const Nullkiller * ai) : ai(ai) {}
  628. virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  629. {
  630. if(task->goalType != Goals::EXECUTE_HERO_CHAIN)
  631. return;
  632. Goals::ExecuteHeroChain & chain = dynamic_cast<Goals::ExecuteHeroChain &>(*task);
  633. const AIPath & path = chain.getPath();
  634. vstd::amax(evaluationContext.danger, path.getTotalDanger());
  635. evaluationContext.movementCost += path.movementCost();
  636. evaluationContext.closestWayRatio = chain.closestWayRatio;
  637. std::map<const CGHeroInstance *, float> costsPerHero;
  638. for(auto & node : path.nodes)
  639. {
  640. vstd::amax(costsPerHero[node.targetHero], node.cost);
  641. }
  642. for(auto pair : costsPerHero)
  643. {
  644. auto role = evaluationContext.evaluator.ai->heroManager->getHeroRole(pair.first);
  645. evaluationContext.movementCostByRole[role] += pair.second;
  646. }
  647. auto heroPtr = task->hero;
  648. auto hero = heroPtr.get(ai->cb.get());
  649. bool checkGold = evaluationContext.danger == 0;
  650. auto army = path.heroArmy;
  651. const CGObjectInstance * target = ai->cb->getObj((ObjectInstanceID)task->objid, false);
  652. auto heroRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(heroPtr);
  653. if(heroRole == HeroRole::MAIN)
  654. evaluationContext.heroRole = heroRole;
  655. if (target && ai->cb->getPlayerRelations(target->tempOwner, hero->tempOwner) == PlayerRelations::ENEMIES)
  656. {
  657. evaluationContext.goldReward += evaluationContext.evaluator.getGoldReward(target, hero);
  658. evaluationContext.armyReward += evaluationContext.evaluator.getArmyReward(target, hero, army, checkGold);
  659. evaluationContext.armyGrowth += evaluationContext.evaluator.getArmyGrowth(target, hero, army);
  660. evaluationContext.skillReward += evaluationContext.evaluator.getSkillReward(target, hero, heroRole);
  661. evaluationContext.addNonCriticalStrategicalValue(evaluationContext.evaluator.getStrategicalValue(target));
  662. evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army);
  663. }
  664. vstd::amax(evaluationContext.armyLossPersentage, path.getTotalArmyLoss() / (double)path.getHeroStrength());
  665. addTileDanger(evaluationContext, path.targetTile(), path.turn(), path.getHeroStrength());
  666. vstd::amax(evaluationContext.turn, path.turn());
  667. }
  668. };
  669. class ClusterEvaluationContextBuilder : public IEvaluationContextBuilder
  670. {
  671. public:
  672. ClusterEvaluationContextBuilder(const Nullkiller * ai) {}
  673. virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  674. {
  675. if(task->goalType != Goals::UNLOCK_CLUSTER)
  676. return;
  677. Goals::UnlockCluster & clusterGoal = dynamic_cast<Goals::UnlockCluster &>(*task);
  678. std::shared_ptr<ObjectCluster> cluster = clusterGoal.getCluster();
  679. auto hero = clusterGoal.hero.get();
  680. auto role = evaluationContext.evaluator.ai->heroManager->getHeroRole(clusterGoal.hero);
  681. std::vector<std::pair<const CGObjectInstance *, ClusterObjectInfo>> objects(cluster->objects.begin(), cluster->objects.end());
  682. std::sort(objects.begin(), objects.end(), [](std::pair<const CGObjectInstance *, ClusterObjectInfo> o1, std::pair<const CGObjectInstance *, ClusterObjectInfo> o2) -> bool
  683. {
  684. return o1.second.priority > o2.second.priority;
  685. });
  686. int boost = 1;
  687. for(auto objInfo : objects)
  688. {
  689. auto target = objInfo.first;
  690. bool checkGold = objInfo.second.danger == 0;
  691. auto army = hero;
  692. evaluationContext.goldReward += evaluationContext.evaluator.getGoldReward(target, hero) / boost;
  693. evaluationContext.armyReward += evaluationContext.evaluator.getArmyReward(target, hero, army, checkGold) / boost;
  694. evaluationContext.skillReward += evaluationContext.evaluator.getSkillReward(target, hero, role) / boost;
  695. evaluationContext.addNonCriticalStrategicalValue(evaluationContext.evaluator.getStrategicalValue(target) / boost);
  696. evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army) / boost;
  697. evaluationContext.movementCostByRole[role] += objInfo.second.movementCost / boost;
  698. evaluationContext.movementCost += objInfo.second.movementCost / boost;
  699. vstd::amax(evaluationContext.turn, objInfo.second.turn / boost);
  700. boost <<= 1;
  701. if(boost > 8)
  702. break;
  703. }
  704. }
  705. };
  706. class ExchangeSwapTownHeroesContextBuilder : public IEvaluationContextBuilder
  707. {
  708. public:
  709. virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  710. {
  711. if(task->goalType != Goals::EXCHANGE_SWAP_TOWN_HEROES)
  712. return;
  713. Goals::ExchangeSwapTownHeroes & swapCommand = dynamic_cast<Goals::ExchangeSwapTownHeroes &>(*task);
  714. const CGHeroInstance * garrisonHero = swapCommand.getGarrisonHero();
  715. if(garrisonHero && swapCommand.getLockingReason() == HeroLockedReason::DEFENCE)
  716. {
  717. auto defenderRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(garrisonHero);
  718. auto mpLeft = garrisonHero->movementPointsRemaining() / (float)garrisonHero->movementPointsLimit(true);
  719. evaluationContext.movementCost += mpLeft;
  720. evaluationContext.movementCostByRole[defenderRole] += mpLeft;
  721. evaluationContext.heroRole = defenderRole;
  722. }
  723. }
  724. };
  725. class DismissHeroContextBuilder : public IEvaluationContextBuilder
  726. {
  727. private:
  728. const Nullkiller * ai;
  729. public:
  730. DismissHeroContextBuilder(const Nullkiller * ai) : ai(ai) {}
  731. virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  732. {
  733. if(task->goalType != Goals::DISMISS_HERO)
  734. return;
  735. Goals::DismissHero & dismissCommand = dynamic_cast<Goals::DismissHero &>(*task);
  736. const CGHeroInstance * dismissedHero = dismissCommand.getHero().get();
  737. auto role = ai->heroManager->getHeroRole(dismissedHero);
  738. auto mpLeft = dismissedHero->movementPointsRemaining();
  739. evaluationContext.movementCost += mpLeft;
  740. evaluationContext.movementCostByRole[role] += mpLeft;
  741. evaluationContext.goldCost += GameConstants::HERO_GOLD_COST + getArmyCost(dismissedHero);
  742. }
  743. };
  744. class BuildThisEvaluationContextBuilder : public IEvaluationContextBuilder
  745. {
  746. public:
  747. virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  748. {
  749. if(task->goalType != Goals::BUILD_STRUCTURE)
  750. return;
  751. Goals::BuildThis & buildThis = dynamic_cast<Goals::BuildThis &>(*task);
  752. auto & bi = buildThis.buildingInfo;
  753. evaluationContext.goldReward += 7 * bi.dailyIncome[EGameResID::GOLD] / 2; // 7 day income but half we already have
  754. evaluationContext.heroRole = HeroRole::MAIN;
  755. evaluationContext.movementCostByRole[evaluationContext.heroRole] += bi.prerequisitesCount;
  756. evaluationContext.goldCost += bi.buildCostWithPrerequisits[EGameResID::GOLD];
  757. evaluationContext.closestWayRatio = 1;
  758. if(bi.creatureID != CreatureID::NONE)
  759. {
  760. evaluationContext.addNonCriticalStrategicalValue(buildThis.townInfo.armyStrength / 50000.0);
  761. if(bi.baseCreatureID == bi.creatureID)
  762. {
  763. evaluationContext.addNonCriticalStrategicalValue((0.5f + 0.1f * bi.creatureLevel) / (float)bi.prerequisitesCount);
  764. evaluationContext.armyReward += bi.armyStrength;
  765. }
  766. else
  767. {
  768. auto potentialUpgradeValue = evaluationContext.evaluator.getUpgradeArmyReward(buildThis.town, bi);
  769. evaluationContext.addNonCriticalStrategicalValue(potentialUpgradeValue / 10000.0f / (float)bi.prerequisitesCount);
  770. evaluationContext.armyReward += potentialUpgradeValue / (float)bi.prerequisitesCount;
  771. }
  772. }
  773. else if(bi.id == BuildingID::CITADEL || bi.id == BuildingID::CASTLE)
  774. {
  775. evaluationContext.addNonCriticalStrategicalValue(buildThis.town->creatures.size() * 0.2f);
  776. evaluationContext.armyReward += buildThis.townInfo.armyStrength / 2;
  777. }
  778. else if(bi.id >= BuildingID::MAGES_GUILD_1 && bi.id <= BuildingID::MAGES_GUILD_5)
  779. {
  780. evaluationContext.skillReward += 2 * (bi.id - BuildingID::MAGES_GUILD_1);
  781. }
  782. if(evaluationContext.goldReward)
  783. {
  784. auto goldPreasure = evaluationContext.evaluator.ai->buildAnalyzer->getGoldPreasure();
  785. evaluationContext.addNonCriticalStrategicalValue(evaluationContext.goldReward * goldPreasure / 3500.0f / bi.prerequisitesCount);
  786. }
  787. if(bi.notEnoughRes && bi.prerequisitesCount == 1)
  788. {
  789. evaluationContext.strategicalValue /= 2;
  790. }
  791. }
  792. };
  793. uint64_t RewardEvaluator::getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const
  794. {
  795. if(ai->buildAnalyzer->hasAnyBuilding(town->subID, bi.id))
  796. return 0;
  797. auto creaturesToUpgrade = ai->armyManager->getTotalCreaturesAvailable(bi.baseCreatureID);
  798. auto upgradedPower = ai->armyManager->evaluateStackPower(bi.creatureID.toCreature(), creaturesToUpgrade.count);
  799. return upgradedPower - creaturesToUpgrade.power;
  800. }
  801. PriorityEvaluator::PriorityEvaluator(const Nullkiller * ai)
  802. :ai(ai)
  803. {
  804. initVisitTile();
  805. evaluationContextBuilders.push_back(std::make_shared<ExecuteHeroChainEvaluationContextBuilder>(ai));
  806. evaluationContextBuilders.push_back(std::make_shared<BuildThisEvaluationContextBuilder>());
  807. evaluationContextBuilders.push_back(std::make_shared<ClusterEvaluationContextBuilder>(ai));
  808. evaluationContextBuilders.push_back(std::make_shared<HeroExchangeEvaluator>());
  809. evaluationContextBuilders.push_back(std::make_shared<ArmyUpgradeEvaluator>());
  810. evaluationContextBuilders.push_back(std::make_shared<DefendTownEvaluator>());
  811. evaluationContextBuilders.push_back(std::make_shared<ExchangeSwapTownHeroesContextBuilder>());
  812. evaluationContextBuilders.push_back(std::make_shared<DismissHeroContextBuilder>(ai));
  813. }
  814. EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal) const
  815. {
  816. Goals::TGoalVec parts;
  817. EvaluationContext context(ai);
  818. if(goal->goalType == Goals::COMPOSITION)
  819. {
  820. parts = goal->decompose();
  821. }
  822. else
  823. {
  824. parts.push_back(goal);
  825. }
  826. for(auto subgoal : parts)
  827. {
  828. context.goldCost += subgoal->goldCost;
  829. for(auto builder : evaluationContextBuilders)
  830. {
  831. builder->buildEvaluationContext(context, subgoal);
  832. }
  833. }
  834. return context;
  835. }
  836. float PriorityEvaluator::evaluate(Goals::TSubgoal task)
  837. {
  838. auto evaluationContext = buildEvaluationContext(task);
  839. int rewardType = (evaluationContext.goldReward > 0 ? 1 : 0)
  840. + (evaluationContext.armyReward > 0 ? 1 : 0)
  841. + (evaluationContext.skillReward > 0 ? 1 : 0)
  842. + (evaluationContext.strategicalValue > 0 ? 1 : 0);
  843. auto goldRewardPerTurn = evaluationContext.goldReward / std::log2f(evaluationContext.movementCost * 10);
  844. double result = 0;
  845. try
  846. {
  847. armyLossPersentageVariable->setValue(evaluationContext.armyLossPersentage);
  848. heroRoleVariable->setValue(evaluationContext.heroRole);
  849. mainTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::MAIN]);
  850. scoutTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::SCOUT]);
  851. goldRewardVariable->setValue(goldRewardPerTurn);
  852. armyRewardVariable->setValue(evaluationContext.armyReward);
  853. armyGrowthVariable->setValue(evaluationContext.armyGrowth);
  854. skillRewardVariable->setValue(evaluationContext.skillReward);
  855. dangerVariable->setValue(evaluationContext.danger);
  856. rewardTypeVariable->setValue(rewardType);
  857. closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio);
  858. strategicalValueVariable->setValue(evaluationContext.strategicalValue);
  859. goldPreasureVariable->setValue(ai->buildAnalyzer->getGoldPreasure());
  860. goldCostVariable->setValue(evaluationContext.goldCost / ((float)ai->getFreeResources()[EGameResID::GOLD] + (float)ai->buildAnalyzer->getDailyIncome()[EGameResID::GOLD] + 1.0f));
  861. turnVariable->setValue(evaluationContext.turn);
  862. fearVariable->setValue(evaluationContext.enemyHeroDangerRatio);
  863. engine->process();
  864. result = value->getValue();
  865. }
  866. catch(fl::Exception & fe)
  867. {
  868. logAi->error("evaluate VisitTile: %s", fe.getWhat());
  869. }
  870. #if NKAI_TRACE_LEVEL >= 2
  871. logAi->trace("Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %d, cost: %d, army gain: %d, danger: %d, role: %s, strategical value: %f, cwr: %f, fear: %f, result %f",
  872. task->toString(),
  873. evaluationContext.armyLossPersentage,
  874. (int)evaluationContext.turn,
  875. evaluationContext.movementCostByRole[HeroRole::MAIN],
  876. evaluationContext.movementCostByRole[HeroRole::SCOUT],
  877. evaluationContext.goldReward,
  878. evaluationContext.goldCost,
  879. evaluationContext.armyReward,
  880. evaluationContext.danger,
  881. evaluationContext.heroRole == HeroRole::MAIN ? "main" : "scout",
  882. evaluationContext.strategicalValue,
  883. evaluationContext.closestWayRatio,
  884. evaluationContext.enemyHeroDangerRatio,
  885. result);
  886. #endif
  887. return result;
  888. }
  889. }