PriorityEvaluator.cpp 50 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580
  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/StayAtTown.h"
  25. #include "../Goals/ExchangeSwapTownHeroes.h"
  26. #include "../Goals/DismissHero.h"
  27. #include "../Markers/UnlockCluster.h"
  28. #include "../Markers/HeroExchange.h"
  29. #include "../Markers/ArmyUpgrade.h"
  30. #include "../Markers/DefendTown.h"
  31. namespace NKAI
  32. {
  33. #define MIN_AI_STRENGTH (0.5f) //lower when combat AI gets smarter
  34. #define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
  35. const float MIN_CRITICAL_VALUE = 2.0f;
  36. EvaluationContext::EvaluationContext(const Nullkiller* ai)
  37. : movementCost(0.0),
  38. manaCost(0),
  39. danger(0),
  40. closestWayRatio(1),
  41. movementCostByRole(),
  42. skillReward(0),
  43. goldReward(0),
  44. goldCost(0),
  45. armyReward(0),
  46. armyLossPersentage(0),
  47. heroRole(HeroRole::SCOUT),
  48. turn(0),
  49. strategicalValue(0),
  50. conquestValue(0),
  51. evaluator(ai),
  52. enemyHeroDangerRatio(0),
  53. threat(0),
  54. armyGrowth(0),
  55. armyInvolvement(0),
  56. defenseValue(0),
  57. isDefend(false),
  58. threatTurns(INT_MAX),
  59. involvesSailing(false),
  60. isTradeBuilding(false),
  61. isChain(false),
  62. isEnemy(false),
  63. isExchange(false)
  64. {
  65. }
  66. void EvaluationContext::addNonCriticalStrategicalValue(float value)
  67. {
  68. vstd::amax(strategicalValue, std::min(value, MIN_CRITICAL_VALUE));
  69. }
  70. PriorityEvaluator::~PriorityEvaluator()
  71. {
  72. delete engine;
  73. }
  74. void PriorityEvaluator::initVisitTile()
  75. {
  76. auto file = CResourceHandler::get()->load(ResourcePath("config/ai/nkai/object-priorities.txt"))->readAll();
  77. std::string str = std::string((char *)file.first.get(), file.second);
  78. engine = fl::FllImporter().fromString(str);
  79. armyLossPersentageVariable = engine->getInputVariable("armyLoss");
  80. armyGrowthVariable = engine->getInputVariable("armyGrowth");
  81. heroRoleVariable = engine->getInputVariable("heroRole");
  82. dangerVariable = engine->getInputVariable("danger");
  83. turnVariable = engine->getInputVariable("turn");
  84. mainTurnDistanceVariable = engine->getInputVariable("mainTurnDistance");
  85. scoutTurnDistanceVariable = engine->getInputVariable("scoutTurnDistance");
  86. goldRewardVariable = engine->getInputVariable("goldReward");
  87. armyRewardVariable = engine->getInputVariable("armyReward");
  88. skillRewardVariable = engine->getInputVariable("skillReward");
  89. rewardTypeVariable = engine->getInputVariable("rewardType");
  90. closestHeroRatioVariable = engine->getInputVariable("closestHeroRatio");
  91. strategicalValueVariable = engine->getInputVariable("strategicalValue");
  92. goldPressureVariable = engine->getInputVariable("goldPressure");
  93. goldCostVariable = engine->getInputVariable("goldCost");
  94. fearVariable = engine->getInputVariable("fear");
  95. value = engine->getOutputVariable("Value");
  96. }
  97. bool isAnotherAi(const CGObjectInstance * obj, const CPlayerSpecificInfoCallback & cb)
  98. {
  99. return obj->getOwner().isValidPlayer()
  100. && cb.getStartInfo()->getIthPlayersSettings(obj->getOwner()).isControlledByAI();
  101. }
  102. int32_t estimateTownIncome(CCallback * cb, const CGObjectInstance * target, const CGHeroInstance * hero)
  103. {
  104. auto relations = cb->getPlayerRelations(hero->tempOwner, target->tempOwner);
  105. if(relations != PlayerRelations::ENEMIES)
  106. return 0; // if we already own it, no additional reward will be received by just visiting it
  107. auto booster = isAnotherAi(target, *cb) ? 1 : 2;
  108. auto town = cb->getTown(target->id);
  109. auto fortLevel = town->fortLevel();
  110. if(town->hasCapitol())
  111. return booster * 2000;
  112. // probably well developed town will have city hall
  113. if(fortLevel == CGTownInstance::CASTLE) return booster * 750;
  114. return booster * (town->hasFort() && town->tempOwner != PlayerColor::NEUTRAL ? booster * 500 : 250);
  115. }
  116. TResources getCreatureBankResources(const CGObjectInstance * target, const CGHeroInstance * hero)
  117. {
  118. //Fixme: unused variable hero
  119. auto objectInfo = target->getObjectHandler()->getObjectInfo(target->appearance);
  120. CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
  121. auto resources = bankInfo->getPossibleResourcesReward();
  122. TResources result = TResources();
  123. int sum = 0;
  124. for(auto & reward : resources)
  125. {
  126. result += reward.data * reward.chance;
  127. sum += reward.chance;
  128. }
  129. return sum > 1 ? result / sum : result;
  130. }
  131. int32_t getResourcesGoldReward(const TResources & res)
  132. {
  133. int32_t result = 0;
  134. for(auto r : GameResID::ALL_RESOURCES())
  135. {
  136. if(res[r] > 0)
  137. result += r == EGameResID::GOLD ? res[r] : res[r] * 100;
  138. }
  139. return result;
  140. }
  141. uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero)
  142. {
  143. auto objectInfo = target->getObjectHandler()->getObjectInfo(target->appearance);
  144. CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
  145. auto creatures = bankInfo->getPossibleCreaturesReward(target->cb);
  146. uint64_t result = 0;
  147. const auto& slots = hero->Slots();
  148. ui64 weakestStackPower = 0;
  149. int duplicatingSlots = getDuplicatingSlots(hero);
  150. if (slots.size() >= GameConstants::ARMY_SIZE)
  151. {
  152. //No free slot, we might discard our weakest stack
  153. weakestStackPower = std::numeric_limits<ui64>().max();
  154. for (const auto & stack : slots)
  155. {
  156. vstd::amin(weakestStackPower, stack.second->getPower());
  157. }
  158. }
  159. for (auto c : creatures)
  160. {
  161. //Only if hero has slot for this creature in the army
  162. auto ccre = dynamic_cast<const CCreature*>(c.data.type);
  163. if (hero->getSlotFor(ccre).validSlot() || duplicatingSlots > 0)
  164. {
  165. result += (c.data.type->getAIValue() * c.data.count) * c.chance;
  166. }
  167. /*else
  168. {
  169. //we will need to discard the weakest stack
  170. result += (c.data.type->getAIValue() * c.data.count - weakestStackPower) * c.chance;
  171. }*/
  172. }
  173. result /= 100; //divide by total chance
  174. return result;
  175. }
  176. uint64_t getDwellingArmyValue(CCallback * cb, const CGObjectInstance * target, bool checkGold)
  177. {
  178. auto dwelling = dynamic_cast<const CGDwelling *>(target);
  179. uint64_t score = 0;
  180. for(auto & creLevel : dwelling->creatures)
  181. {
  182. if(creLevel.first && creLevel.second.size())
  183. {
  184. auto creature = creLevel.second.back().toCreature();
  185. auto creaturesAreFree = creature->getLevel() == 1;
  186. if(!creaturesAreFree && checkGold && !cb->getResourceAmount().canAfford(creature->getFullRecruitCost() * creLevel.first))
  187. continue;
  188. score += creature->getAIValue() * creLevel.first;
  189. }
  190. }
  191. return score;
  192. }
  193. uint64_t getDwellingArmyGrowth(CCallback * cb, const CGObjectInstance * target, PlayerColor myColor)
  194. {
  195. auto dwelling = dynamic_cast<const CGDwelling *>(target);
  196. uint64_t score = 0;
  197. if(dwelling->getOwner() == myColor)
  198. return 0;
  199. for(auto & creLevel : dwelling->creatures)
  200. {
  201. if(creLevel.second.size())
  202. {
  203. auto creature = creLevel.second.back().toCreature();
  204. score += creature->getAIValue() * creature->getGrowth();
  205. }
  206. }
  207. return score;
  208. }
  209. int getDwellingArmyCost(const CGObjectInstance * target)
  210. {
  211. auto dwelling = dynamic_cast<const CGDwelling *>(target);
  212. int cost = 0;
  213. for(auto & creLevel : dwelling->creatures)
  214. {
  215. if(creLevel.first && creLevel.second.size())
  216. {
  217. auto creature = creLevel.second.back().toCreature();
  218. auto creaturesAreFree = creature->getLevel() == 1;
  219. if(!creaturesAreFree)
  220. cost += creature->getFullRecruitCost().marketValue() * creLevel.first;
  221. }
  222. }
  223. return cost;
  224. }
  225. static uint64_t evaluateArtifactArmyValue(const CArtifact * art)
  226. {
  227. if(art->getId() == ArtifactID::SPELL_SCROLL)
  228. return 1500;
  229. auto statsValue =
  230. 10 * art->valOfBonuses(BonusType::MOVEMENT, BonusCustomSubtype::heroMovementLand)
  231. + 1200 * art->valOfBonuses(BonusType::STACKS_SPEED)
  232. + 700 * art->valOfBonuses(BonusType::MORALE)
  233. + 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::ATTACK))
  234. + 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::DEFENSE))
  235. + 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::KNOWLEDGE))
  236. + 700 * art->valOfBonuses(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::SPELL_POWER))
  237. + 500 * art->valOfBonuses(BonusType::LUCK);
  238. auto classValue = 0;
  239. switch(art->aClass)
  240. {
  241. case CArtifact::EartClass::ART_MINOR:
  242. classValue = 1000;
  243. break;
  244. case CArtifact::EartClass::ART_MAJOR:
  245. classValue = 3000;
  246. break;
  247. case CArtifact::EartClass::ART_RELIC:
  248. case CArtifact::EartClass::ART_SPECIAL:
  249. classValue = 8000;
  250. break;
  251. }
  252. return statsValue > classValue ? statsValue : classValue;
  253. }
  254. uint64_t RewardEvaluator::getArmyReward(
  255. const CGObjectInstance * target,
  256. const CGHeroInstance * hero,
  257. const CCreatureSet * army,
  258. bool checkGold) const
  259. {
  260. const float enemyArmyEliminationRewardRatio = 0.5f;
  261. auto relations = ai->cb->getPlayerRelations(target->tempOwner, ai->playerID);
  262. if(!target)
  263. return 0;
  264. switch(target->ID)
  265. {
  266. case Obj::HILL_FORT:
  267. return ai->armyManager->calculateCreaturesUpgrade(army, target, ai->cb->getResourceAmount()).upgradeValue;
  268. case Obj::CREATURE_BANK:
  269. return getCreatureBankArmyReward(target, hero);
  270. case Obj::CREATURE_GENERATOR1:
  271. case Obj::CREATURE_GENERATOR2:
  272. case Obj::CREATURE_GENERATOR3:
  273. case Obj::CREATURE_GENERATOR4:
  274. return getDwellingArmyValue(ai->cb.get(), target, checkGold);
  275. case Obj::CRYPT:
  276. case Obj::SHIPWRECK:
  277. case Obj::SHIPWRECK_SURVIVOR:
  278. case Obj::WARRIORS_TOMB:
  279. return 1000;
  280. case Obj::ARTIFACT:
  281. return evaluateArtifactArmyValue(dynamic_cast<const CGArtifact *>(target)->storedArtifact->artType);
  282. case Obj::DRAGON_UTOPIA:
  283. return 10000;
  284. case Obj::HERO:
  285. return relations == PlayerRelations::ENEMIES
  286. ? enemyArmyEliminationRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->getArmyStrength()
  287. : 0;
  288. case Obj::PANDORAS_BOX:
  289. return 5000;
  290. case Obj::MAGIC_WELL:
  291. case Obj::MAGIC_SPRING:
  292. return getManaRecoveryArmyReward(hero);
  293. default:
  294. break;
  295. }
  296. auto rewardable = dynamic_cast<const Rewardable::Interface *>(target);
  297. if(rewardable)
  298. {
  299. auto totalValue = 0;
  300. for(int index : rewardable->getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT))
  301. {
  302. auto & info = rewardable->configuration.info[index];
  303. auto rewardValue = 0;
  304. if(!info.reward.artifacts.empty())
  305. {
  306. for(auto artID : info.reward.artifacts)
  307. {
  308. const auto * art = dynamic_cast<const CArtifact *>(VLC->artifacts()->getById(artID));
  309. rewardValue += evaluateArtifactArmyValue(art);
  310. }
  311. }
  312. if(!info.reward.creatures.empty())
  313. {
  314. for(const auto & stackInfo : info.reward.creatures)
  315. {
  316. rewardValue += stackInfo.getType()->getAIValue() * stackInfo.getCount();
  317. }
  318. }
  319. totalValue += rewardValue > 0 ? rewardValue / (info.reward.artifacts.size() + info.reward.creatures.size()) : 0;
  320. }
  321. return totalValue;
  322. }
  323. return 0;
  324. }
  325. uint64_t RewardEvaluator::getArmyGrowth(
  326. const CGObjectInstance * target,
  327. const CGHeroInstance * hero,
  328. const CCreatureSet * army) const
  329. {
  330. if(!target)
  331. return 0;
  332. auto relations = ai->cb->getPlayerRelations(target->tempOwner, hero->tempOwner);
  333. if(relations != PlayerRelations::ENEMIES)
  334. return 0;
  335. switch(target->ID)
  336. {
  337. case Obj::TOWN:
  338. {
  339. auto town = dynamic_cast<const CGTownInstance *>(target);
  340. auto fortLevel = town->fortLevel();
  341. auto neutral = !town->getOwner().isValidPlayer();
  342. auto booster = isAnotherAi(town, *ai->cb) || neutral ? 1 : 2;
  343. if(fortLevel < CGTownInstance::CITADEL)
  344. return town->hasFort() ? booster * 500 : 0;
  345. else
  346. return booster * (fortLevel == CGTownInstance::CASTLE ? 5000 : 2000);
  347. }
  348. case Obj::CREATURE_GENERATOR1:
  349. case Obj::CREATURE_GENERATOR2:
  350. case Obj::CREATURE_GENERATOR3:
  351. case Obj::CREATURE_GENERATOR4:
  352. return getDwellingArmyGrowth(ai->cb.get(), target, hero->getOwner());
  353. case Obj::ARTIFACT:
  354. // 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.
  355. return 0;
  356. default:
  357. return 0;
  358. }
  359. }
  360. int RewardEvaluator::getGoldCost(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army) const
  361. {
  362. if(!target)
  363. return 0;
  364. if(auto * m = dynamic_cast<const IMarket *>(target))
  365. {
  366. if(m->allowsTrade(EMarketMode::RESOURCE_SKILL))
  367. return 2000;
  368. }
  369. switch(target->ID)
  370. {
  371. case Obj::HILL_FORT:
  372. return ai->armyManager->calculateCreaturesUpgrade(army, target, ai->cb->getResourceAmount()).upgradeCost[EGameResID::GOLD];
  373. case Obj::SCHOOL_OF_MAGIC:
  374. case Obj::SCHOOL_OF_WAR:
  375. return 1000;
  376. case Obj::CREATURE_GENERATOR1:
  377. case Obj::CREATURE_GENERATOR2:
  378. case Obj::CREATURE_GENERATOR3:
  379. case Obj::CREATURE_GENERATOR4:
  380. return getDwellingArmyCost(target);
  381. default:
  382. return 0;
  383. }
  384. }
  385. float RewardEvaluator::getEnemyHeroStrategicalValue(const CGHeroInstance * enemy) const
  386. {
  387. auto objectsUnderTreat = ai->dangerHitMap->getOneTurnAccessibleObjects(enemy);
  388. float objectValue = 0;
  389. for(auto obj : objectsUnderTreat)
  390. {
  391. vstd::amax(objectValue, getStrategicalValue(obj));
  392. }
  393. /*
  394. 1. If an enemy hero can attack nearby object, it's not useful to capture the object on our own.
  395. Killing the hero is almost as important (0.9) as capturing the object itself.
  396. 2. The formula quickly approaches 1.0 as hero level increases,
  397. but higher level always means higher value and the minimal value for level 1 hero is 0.5
  398. */
  399. return std::min(1.5f, objectValue * 0.9f + (1.5f - (1.5f / (1 + enemy->level))));
  400. }
  401. float RewardEvaluator::getResourceRequirementStrength(int resType) const
  402. {
  403. TResources requiredResources = ai->buildAnalyzer->getResourcesRequiredNow();
  404. TResources dailyIncome = ai->buildAnalyzer->getDailyIncome();
  405. if(requiredResources[resType] == 0)
  406. return 0;
  407. if(dailyIncome[resType] == 0)
  408. return 1.0f;
  409. float ratio = (float)requiredResources[resType] / dailyIncome[resType] / 2;
  410. return std::min(ratio, 1.0f);
  411. }
  412. float RewardEvaluator::getTotalResourceRequirementStrength(int resType) const
  413. {
  414. TResources requiredResources = ai->buildAnalyzer->getTotalResourcesRequired();
  415. TResources dailyIncome = ai->buildAnalyzer->getDailyIncome();
  416. if(requiredResources[resType] == 0)
  417. return 0;
  418. float ratio = dailyIncome[resType] == 0
  419. ? (float)requiredResources[resType] / 10.0f
  420. : (float)requiredResources[resType] / dailyIncome[resType] / 20.0f;
  421. return std::min(ratio, 2.0f);
  422. }
  423. uint64_t RewardEvaluator::townArmyGrowth(const CGTownInstance * town) const
  424. {
  425. uint64_t result = 0;
  426. for(auto creatureInfo : town->creatures)
  427. {
  428. if(creatureInfo.second.empty())
  429. continue;
  430. auto creature = creatureInfo.second.back().toCreature();
  431. result += creature->getAIValue() * town->getGrowthInfo(creature->getLevel() - 1).totalGrowth();
  432. }
  433. return result;
  434. }
  435. uint64_t RewardEvaluator::getManaRecoveryArmyReward(const CGHeroInstance * hero) const
  436. {
  437. return ai->heroManager->getMagicStrength(hero) * 10000 * (1.0f - std::sqrt(static_cast<float>(hero->mana) / hero->manaLimit()));
  438. }
  439. float RewardEvaluator::getResourceRequirementStrength(const TResources & res) const
  440. {
  441. float sum = 0.0f;
  442. for(TResources::nziterator it(res); it.valid(); it++)
  443. {
  444. //Evaluate resources used for construction. Gold is evaluated separately.
  445. if(it->resType != EGameResID::GOLD)
  446. {
  447. sum += 0.1f * it->resVal * getResourceRequirementStrength(it->resType)
  448. + 0.05f * it->resVal * getTotalResourceRequirementStrength(it->resType);
  449. }
  450. }
  451. return sum;
  452. }
  453. float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target, const CGHeroInstance * hero) const
  454. {
  455. if(!target)
  456. return 0;
  457. switch(target->ID)
  458. {
  459. case Obj::MINE:
  460. {
  461. auto mine = dynamic_cast<const CGMine *>(target);
  462. return mine->producedResource == EGameResID::GOLD
  463. ? 0.5f
  464. : 0.4f * getTotalResourceRequirementStrength(mine->producedResource) + 0.1f * getResourceRequirementStrength(mine->producedResource);
  465. }
  466. case Obj::RESOURCE:
  467. {
  468. auto resource = dynamic_cast<const CGResource *>(target);
  469. TResources res;
  470. res[resource->resourceID()] = resource->amount;
  471. return getResourceRequirementStrength(res);
  472. }
  473. case Obj::CREATURE_BANK:
  474. {
  475. auto resourceReward = getCreatureBankResources(target, nullptr);
  476. return getResourceRequirementStrength(resourceReward);
  477. }
  478. case Obj::TOWN:
  479. {
  480. if(ai->buildAnalyzer->getDevelopmentInfo().empty())
  481. return 10.0f;
  482. auto town = dynamic_cast<const CGTownInstance *>(target);
  483. if(town->getOwner() == ai->playerID)
  484. {
  485. auto armyIncome = townArmyGrowth(town);
  486. auto dailyIncome = town->dailyIncome()[EGameResID::GOLD];
  487. return std::min(1.0f, std::sqrt(armyIncome / 40000.0f)) + std::min(0.3f, dailyIncome / 10000.0f);
  488. }
  489. auto fortLevel = town->fortLevel();
  490. auto booster = isAnotherAi(town, *ai->cb) ? 0.4f : 1.0f;
  491. if(town->hasCapitol())
  492. return booster * 1.5;
  493. if(fortLevel < CGTownInstance::CITADEL)
  494. return booster * (town->hasFort() ? 1.0 : 0.8);
  495. else
  496. return booster * (fortLevel == CGTownInstance::CASTLE ? 1.4 : 1.2);
  497. }
  498. case Obj::HERO:
  499. return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
  500. ? getEnemyHeroStrategicalValue(dynamic_cast<const CGHeroInstance *>(target))
  501. : 0;
  502. case Obj::KEYMASTER:
  503. return 0.6f;
  504. default:
  505. break;
  506. }
  507. auto rewardable = dynamic_cast<const Rewardable::Interface *>(target);
  508. if(rewardable && hero)
  509. {
  510. auto resourceReward = 0.0f;
  511. for(int index : rewardable->getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT))
  512. {
  513. resourceReward += getResourceRequirementStrength(rewardable->configuration.info[index].reward.resources);
  514. }
  515. return resourceReward;
  516. }
  517. return 0;
  518. }
  519. float RewardEvaluator::getConquestValue(const CGObjectInstance* target) const
  520. {
  521. if (!target)
  522. return 0;
  523. if (target->getOwner() == ai->playerID)
  524. return 0;
  525. switch (target->ID)
  526. {
  527. case Obj::TOWN:
  528. {
  529. if (ai->buildAnalyzer->getDevelopmentInfo().empty())
  530. return 10.0f;
  531. auto town = dynamic_cast<const CGTownInstance*>(target);
  532. if (town->getOwner() == ai->playerID)
  533. {
  534. auto armyIncome = townArmyGrowth(town);
  535. auto dailyIncome = town->dailyIncome()[EGameResID::GOLD];
  536. return std::min(1.0f, std::sqrt(armyIncome / 40000.0f)) + std::min(0.3f, dailyIncome / 10000.0f);
  537. }
  538. auto fortLevel = town->fortLevel();
  539. auto booster = 1.0f;
  540. if (town->hasCapitol())
  541. return booster * 1.5;
  542. if (fortLevel < CGTownInstance::CITADEL)
  543. return booster * (town->hasFort() ? 1.0 : 0.8);
  544. else
  545. return booster * (fortLevel == CGTownInstance::CASTLE ? 1.4 : 1.2);
  546. }
  547. case Obj::HERO:
  548. return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
  549. ? getEnemyHeroStrategicalValue(dynamic_cast<const CGHeroInstance*>(target))
  550. : 0;
  551. case Obj::KEYMASTER:
  552. return 0.6f;
  553. default:
  554. return 0;
  555. }
  556. }
  557. float RewardEvaluator::evaluateWitchHutSkillScore(const CGObjectInstance * hut, const CGHeroInstance * hero, HeroRole role) const
  558. {
  559. auto rewardable = dynamic_cast<const CRewardableObject *>(hut);
  560. assert(rewardable);
  561. auto skill = SecondarySkill(*rewardable->configuration.getVariable("secondarySkill", "gainedSkill"));
  562. if(!hut->wasVisited(hero->tempOwner))
  563. return role == HeroRole::SCOUT ? 2 : 0;
  564. if(hero->getSecSkillLevel(skill) != MasteryLevel::NONE
  565. || hero->secSkills.size() >= GameConstants::SKILL_PER_HERO)
  566. return 0;
  567. auto score = ai->heroManager->evaluateSecSkill(skill, hero);
  568. return score >= 2 ? (role == HeroRole::MAIN ? 10 : 4) : score;
  569. }
  570. float RewardEvaluator::getSkillReward(const CGObjectInstance * target, const CGHeroInstance * hero, HeroRole role) const
  571. {
  572. const float enemyHeroEliminationSkillRewardRatio = 0.5f;
  573. if(!target)
  574. return 0;
  575. switch(target->ID)
  576. {
  577. case Obj::STAR_AXIS:
  578. case Obj::SCHOLAR:
  579. case Obj::SCHOOL_OF_MAGIC:
  580. case Obj::SCHOOL_OF_WAR:
  581. case Obj::GARDEN_OF_REVELATION:
  582. case Obj::MARLETTO_TOWER:
  583. case Obj::MERCENARY_CAMP:
  584. case Obj::TREE_OF_KNOWLEDGE:
  585. return 1;
  586. case Obj::LEARNING_STONE:
  587. return 1.0f / std::sqrt(hero->level);
  588. case Obj::ARENA:
  589. return 2;
  590. case Obj::SHRINE_OF_MAGIC_INCANTATION:
  591. return 0.25f;
  592. case Obj::SHRINE_OF_MAGIC_GESTURE:
  593. return 1.0f;
  594. case Obj::SHRINE_OF_MAGIC_THOUGHT:
  595. return 2.0f;
  596. case Obj::LIBRARY_OF_ENLIGHTENMENT:
  597. return 8;
  598. case Obj::WITCH_HUT:
  599. return evaluateWitchHutSkillScore(target, hero, role);
  600. case Obj::PANDORAS_BOX:
  601. //Can contains experience, spells, or skills (only on custom maps)
  602. return 2.5f;
  603. case Obj::PYRAMID:
  604. return 6.0f;
  605. case Obj::HERO:
  606. return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
  607. ? enemyHeroEliminationSkillRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->level
  608. : 0;
  609. default:
  610. break;
  611. }
  612. auto rewardable = dynamic_cast<const Rewardable::Interface *>(target);
  613. if(rewardable)
  614. {
  615. auto totalValue = 0.0f;
  616. for(int index : rewardable->getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT))
  617. {
  618. auto & info = rewardable->configuration.info[index];
  619. auto rewardValue = 0.0f;
  620. if(!info.reward.spells.empty())
  621. {
  622. for(auto spellID : info.reward.spells)
  623. {
  624. const spells::Spell * spell = VLC->spells()->getById(spellID);
  625. if(hero->canLearnSpell(spell) && !hero->spellbookContainsSpell(spellID))
  626. {
  627. rewardValue += std::sqrt(spell->getLevel()) / 4.0f;
  628. }
  629. }
  630. totalValue += rewardValue / info.reward.spells.size();
  631. }
  632. if(!info.reward.primary.empty())
  633. {
  634. for(auto value : info.reward.primary)
  635. {
  636. totalValue += value;
  637. }
  638. }
  639. }
  640. return totalValue;
  641. }
  642. return 0;
  643. }
  644. const HitMapInfo & RewardEvaluator::getEnemyHeroDanger(const int3 & tile, uint8_t turn) const
  645. {
  646. auto & treatNode = ai->dangerHitMap->getTileThreat(tile);
  647. if(treatNode.maximumDanger.danger == 0)
  648. return HitMapInfo::NoThreat;
  649. if(treatNode.maximumDanger.turn <= turn)
  650. return treatNode.maximumDanger;
  651. return treatNode.fastestDanger.turn <= turn ? treatNode.fastestDanger : HitMapInfo::NoThreat;
  652. }
  653. int32_t getArmyCost(const CArmedInstance * army)
  654. {
  655. int32_t value = 0;
  656. for(auto stack : army->Slots())
  657. {
  658. value += stack.second->getCreatureID().toCreature()->getFullRecruitCost().marketValue() * stack.second->count;
  659. }
  660. return value;
  661. }
  662. int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CGHeroInstance * hero) const
  663. {
  664. if(!target)
  665. return 0;
  666. auto relations = ai->cb->getPlayerRelations(target->tempOwner, hero->tempOwner);
  667. const int dailyIncomeMultiplier = 5;
  668. const float enemyArmyEliminationGoldRewardRatio = 0.2f;
  669. const int32_t heroEliminationBonus = GameConstants::HERO_GOLD_COST / 2;
  670. switch(target->ID)
  671. {
  672. case Obj::RESOURCE:
  673. {
  674. auto * res = dynamic_cast<const CGResource*>(target);
  675. return res && res->resourceID() == GameResID::GOLD ? 600 : 100;
  676. }
  677. case Obj::TREASURE_CHEST:
  678. return 1500;
  679. case Obj::WATER_WHEEL:
  680. return 1000;
  681. case Obj::TOWN:
  682. return dailyIncomeMultiplier * estimateTownIncome(ai->cb.get(), target, hero);
  683. case Obj::MINE:
  684. case Obj::ABANDONED_MINE:
  685. {
  686. auto * mine = dynamic_cast<const CGMine*>(target);
  687. return dailyIncomeMultiplier * (mine->producedResource == GameResID::GOLD ? 1000 : 75);
  688. }
  689. case Obj::MYSTICAL_GARDEN:
  690. case Obj::WINDMILL:
  691. return 100;
  692. case Obj::CAMPFIRE:
  693. return 800;
  694. case Obj::WAGON:
  695. return 100;
  696. case Obj::CREATURE_BANK:
  697. return getResourcesGoldReward(getCreatureBankResources(target, hero));
  698. case Obj::CRYPT:
  699. case Obj::DERELICT_SHIP:
  700. return 3000;
  701. case Obj::DRAGON_UTOPIA:
  702. return 10000;
  703. case Obj::SEA_CHEST:
  704. return 1500;
  705. case Obj::PANDORAS_BOX:
  706. return 2500;
  707. case Obj::PRISON:
  708. //Objectively saves us 2500 to hire hero
  709. return GameConstants::HERO_GOLD_COST;
  710. case Obj::HERO:
  711. return relations == PlayerRelations::ENEMIES
  712. ? heroEliminationBonus + enemyArmyEliminationGoldRewardRatio * getArmyCost(dynamic_cast<const CGHeroInstance *>(target))
  713. : 0;
  714. default:
  715. break;
  716. }
  717. auto rewardable = dynamic_cast<const Rewardable::Interface *>(target);
  718. if(rewardable)
  719. {
  720. auto goldReward = 0;
  721. for(int index : rewardable->getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT))
  722. {
  723. auto & info = rewardable->configuration.info[index];
  724. goldReward += getResourcesGoldReward(info.reward.resources);
  725. }
  726. return goldReward;
  727. }
  728. return 0;
  729. }
  730. class HeroExchangeEvaluator : public IEvaluationContextBuilder
  731. {
  732. public:
  733. void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  734. {
  735. if(task->goalType != Goals::HERO_EXCHANGE)
  736. return;
  737. Goals::HeroExchange & heroExchange = dynamic_cast<Goals::HeroExchange &>(*task);
  738. uint64_t armyStrength = heroExchange.getReinforcementArmyStrength(evaluationContext.evaluator.ai);
  739. evaluationContext.addNonCriticalStrategicalValue(2.0f * armyStrength / (float)heroExchange.hero->getArmyStrength());
  740. evaluationContext.conquestValue += 2.0f * armyStrength / (float)heroExchange.hero->getArmyStrength();
  741. evaluationContext.heroRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(heroExchange.hero);
  742. evaluationContext.isExchange = true;
  743. }
  744. };
  745. class ArmyUpgradeEvaluator : public IEvaluationContextBuilder
  746. {
  747. public:
  748. void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  749. {
  750. if(task->goalType != Goals::ARMY_UPGRADE)
  751. return;
  752. Goals::ArmyUpgrade & armyUpgrade = dynamic_cast<Goals::ArmyUpgrade &>(*task);
  753. uint64_t upgradeValue = armyUpgrade.getUpgradeValue();
  754. evaluationContext.armyReward += upgradeValue;
  755. evaluationContext.addNonCriticalStrategicalValue(upgradeValue / (float)armyUpgrade.hero->getArmyStrength());
  756. }
  757. };
  758. class ExplorePointEvaluator : public IEvaluationContextBuilder
  759. {
  760. public:
  761. void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  762. {
  763. if(task->goalType != Goals::EXPLORATION_POINT)
  764. return;
  765. int tilesDiscovered = task->value;
  766. evaluationContext.addNonCriticalStrategicalValue(0.03f * tilesDiscovered);
  767. }
  768. };
  769. class StayAtTownManaRecoveryEvaluator : public IEvaluationContextBuilder
  770. {
  771. public:
  772. void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  773. {
  774. if(task->goalType != Goals::STAY_AT_TOWN)
  775. return;
  776. Goals::StayAtTown & stayAtTown = dynamic_cast<Goals::StayAtTown &>(*task);
  777. evaluationContext.armyReward += evaluationContext.evaluator.getManaRecoveryArmyReward(stayAtTown.getHero());
  778. evaluationContext.movementCostByRole[evaluationContext.heroRole] += stayAtTown.getMovementWasted();
  779. evaluationContext.movementCost += stayAtTown.getMovementWasted();
  780. }
  781. };
  782. void addTileDanger(EvaluationContext & evaluationContext, const int3 & tile, uint8_t turn, uint64_t ourStrength)
  783. {
  784. HitMapInfo enemyDanger = evaluationContext.evaluator.getEnemyHeroDanger(tile, turn);
  785. if(enemyDanger.danger)
  786. {
  787. auto dangerRatio = enemyDanger.danger / (double)ourStrength;
  788. vstd::amax(evaluationContext.enemyHeroDangerRatio, dangerRatio);
  789. vstd::amax(evaluationContext.threat, enemyDanger.threat);
  790. }
  791. }
  792. class DefendTownEvaluator : public IEvaluationContextBuilder
  793. {
  794. public:
  795. void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  796. {
  797. if(task->goalType != Goals::DEFEND_TOWN)
  798. return;
  799. Goals::DefendTown & defendTown = dynamic_cast<Goals::DefendTown &>(*task);
  800. const CGTownInstance * town = defendTown.town;
  801. auto & treat = defendTown.getTreat();
  802. auto strategicalValue = evaluationContext.evaluator.getStrategicalValue(town);
  803. float multiplier = 1;
  804. if(treat.turn < defendTown.getTurn())
  805. multiplier /= 1 + (defendTown.getTurn() - treat.turn);
  806. multiplier /= 1.0f + treat.turn / 5.0f;
  807. if(defendTown.getTurn() > 0 && defendTown.isCounterAttack())
  808. {
  809. auto ourSpeed = defendTown.hero->movementPointsLimit(true);
  810. auto enemySpeed = treat.hero.get(evaluationContext.evaluator.ai->cb.get())->movementPointsLimit(true);
  811. if(enemySpeed > ourSpeed) multiplier *= 0.7f;
  812. }
  813. auto dailyIncome = town->dailyIncome()[EGameResID::GOLD];
  814. auto armyGrowth = evaluationContext.evaluator.townArmyGrowth(town);
  815. evaluationContext.armyGrowth += armyGrowth * multiplier;
  816. evaluationContext.goldReward += dailyIncome * 5 * multiplier;
  817. if(evaluationContext.evaluator.ai->buildAnalyzer->getDevelopmentInfo().size() == 1)
  818. vstd::amax(evaluationContext.strategicalValue, 2.5f * multiplier * strategicalValue);
  819. else
  820. evaluationContext.addNonCriticalStrategicalValue(1.7f * multiplier * strategicalValue);
  821. evaluationContext.defenseValue = town->fortLevel();
  822. evaluationContext.isDefend = true;
  823. evaluationContext.threatTurns = treat.turn;
  824. vstd::amax(evaluationContext.danger, defendTown.getTreat().danger);
  825. addTileDanger(evaluationContext, town->visitablePos(), defendTown.getTurn(), defendTown.getDefenceStrength());
  826. }
  827. };
  828. class ExecuteHeroChainEvaluationContextBuilder : public IEvaluationContextBuilder
  829. {
  830. private:
  831. const Nullkiller * ai;
  832. public:
  833. ExecuteHeroChainEvaluationContextBuilder(const Nullkiller * ai) : ai(ai) {}
  834. void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  835. {
  836. if(task->goalType != Goals::EXECUTE_HERO_CHAIN)
  837. return;
  838. Goals::ExecuteHeroChain & chain = dynamic_cast<Goals::ExecuteHeroChain &>(*task);
  839. const AIPath & path = chain.getPath();
  840. vstd::amax(evaluationContext.danger, path.getTotalDanger());
  841. evaluationContext.movementCost += path.movementCost();
  842. evaluationContext.closestWayRatio = chain.closestWayRatio;
  843. evaluationContext.isChain = true;
  844. std::map<const CGHeroInstance *, float> costsPerHero;
  845. for(auto & node : path.nodes)
  846. {
  847. vstd::amax(costsPerHero[node.targetHero], node.cost);
  848. if (node.layer == EPathfindingLayer::SAIL)
  849. evaluationContext.involvesSailing = true;
  850. }
  851. for(auto pair : costsPerHero)
  852. {
  853. auto role = evaluationContext.evaluator.ai->heroManager->getHeroRole(pair.first);
  854. evaluationContext.movementCostByRole[role] += pair.second;
  855. }
  856. auto hero = task->hero;
  857. bool checkGold = evaluationContext.danger == 0;
  858. auto army = path.heroArmy;
  859. const CGObjectInstance * target = ai->cb->getObj((ObjectInstanceID)task->objid, false);
  860. auto heroRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(hero);
  861. if(heroRole == HeroRole::MAIN)
  862. evaluationContext.heroRole = heroRole;
  863. if (target)
  864. {
  865. evaluationContext.goldReward += evaluationContext.evaluator.getGoldReward(target, hero);
  866. evaluationContext.armyReward += evaluationContext.evaluator.getArmyReward(target, hero, army, checkGold);
  867. evaluationContext.armyGrowth += evaluationContext.evaluator.getArmyGrowth(target, hero, army);
  868. evaluationContext.skillReward += evaluationContext.evaluator.getSkillReward(target, hero, heroRole);
  869. evaluationContext.addNonCriticalStrategicalValue(evaluationContext.evaluator.getStrategicalValue(target));
  870. evaluationContext.conquestValue += evaluationContext.evaluator.getConquestValue(target);
  871. evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army);
  872. evaluationContext.armyInvolvement += army->getArmyCost();
  873. if (target->tempOwner != PlayerColor::NEUTRAL)
  874. evaluationContext.isEnemy = true;
  875. }
  876. vstd::amax(evaluationContext.armyLossPersentage, path.getTotalArmyLoss() / (double)path.getHeroStrength());
  877. addTileDanger(evaluationContext, path.targetTile(), path.turn(), path.getHeroStrength());
  878. vstd::amax(evaluationContext.turn, path.turn());
  879. }
  880. };
  881. class ClusterEvaluationContextBuilder : public IEvaluationContextBuilder
  882. {
  883. public:
  884. ClusterEvaluationContextBuilder(const Nullkiller * ai) {}
  885. void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  886. {
  887. if(task->goalType != Goals::UNLOCK_CLUSTER)
  888. return;
  889. Goals::UnlockCluster & clusterGoal = dynamic_cast<Goals::UnlockCluster &>(*task);
  890. std::shared_ptr<ObjectCluster> cluster = clusterGoal.getCluster();
  891. auto hero = clusterGoal.hero;
  892. auto role = evaluationContext.evaluator.ai->heroManager->getHeroRole(hero);
  893. std::vector<std::pair<ObjectInstanceID, ClusterObjectInfo>> objects(cluster->objects.begin(), cluster->objects.end());
  894. std::sort(objects.begin(), objects.end(), [](std::pair<ObjectInstanceID, ClusterObjectInfo> o1, std::pair<ObjectInstanceID, ClusterObjectInfo> o2) -> bool
  895. {
  896. return o1.second.priority > o2.second.priority;
  897. });
  898. int boost = 1;
  899. for(auto & objInfo : objects)
  900. {
  901. auto target = evaluationContext.evaluator.ai->cb->getObj(objInfo.first);
  902. bool checkGold = objInfo.second.danger == 0;
  903. auto army = hero;
  904. evaluationContext.goldReward += evaluationContext.evaluator.getGoldReward(target, hero) / boost;
  905. evaluationContext.armyReward += evaluationContext.evaluator.getArmyReward(target, hero, army, checkGold) / boost;
  906. evaluationContext.skillReward += evaluationContext.evaluator.getSkillReward(target, hero, role) / boost;
  907. evaluationContext.addNonCriticalStrategicalValue(evaluationContext.evaluator.getStrategicalValue(target) / boost);
  908. evaluationContext.conquestValue += evaluationContext.evaluator.getConquestValue(target);
  909. evaluationContext.goldCost += evaluationContext.evaluator.getGoldCost(target, hero, army) / boost;
  910. evaluationContext.movementCostByRole[role] += objInfo.second.movementCost / boost;
  911. evaluationContext.movementCost += objInfo.second.movementCost / boost;
  912. if (target->tempOwner != PlayerColor::NEUTRAL)
  913. evaluationContext.isEnemy = true;
  914. vstd::amax(evaluationContext.turn, objInfo.second.turn / boost);
  915. boost <<= 1;
  916. if(boost > 8)
  917. break;
  918. }
  919. }
  920. };
  921. class ExchangeSwapTownHeroesContextBuilder : public IEvaluationContextBuilder
  922. {
  923. public:
  924. void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  925. {
  926. if(task->goalType != Goals::EXCHANGE_SWAP_TOWN_HEROES)
  927. return;
  928. Goals::ExchangeSwapTownHeroes & swapCommand = dynamic_cast<Goals::ExchangeSwapTownHeroes &>(*task);
  929. const CGHeroInstance * garrisonHero = swapCommand.getGarrisonHero();
  930. logAi->trace("buildEvaluationContext ExchangeSwapTownHeroesContextBuilder %s affected objects: %d", swapCommand.toString(), swapCommand.getAffectedObjects().size());
  931. for (auto obj : swapCommand.getAffectedObjects())
  932. {
  933. logAi->trace("affected object: %s", evaluationContext.evaluator.ai->cb->getObj(obj)->getObjectName());
  934. }
  935. if (garrisonHero)
  936. logAi->debug("with %s and %d", garrisonHero->getNameTranslated(), int(swapCommand.getLockingReason()));
  937. if(garrisonHero && swapCommand.getLockingReason() == HeroLockedReason::DEFENCE)
  938. {
  939. auto defenderRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(garrisonHero);
  940. auto mpLeft = garrisonHero->movementPointsRemaining() / (float)garrisonHero->movementPointsLimit(true);
  941. evaluationContext.movementCost += mpLeft;
  942. evaluationContext.movementCostByRole[defenderRole] += mpLeft;
  943. evaluationContext.heroRole = defenderRole;
  944. evaluationContext.isDefend = true;
  945. evaluationContext.armyInvolvement = garrisonHero->getArmyStrength();
  946. logAi->debug("evaluationContext.isDefend: %d", evaluationContext.isDefend);
  947. }
  948. }
  949. };
  950. class DismissHeroContextBuilder : public IEvaluationContextBuilder
  951. {
  952. private:
  953. const Nullkiller * ai;
  954. public:
  955. DismissHeroContextBuilder(const Nullkiller * ai) : ai(ai) {}
  956. void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  957. {
  958. if(task->goalType != Goals::DISMISS_HERO)
  959. return;
  960. Goals::DismissHero & dismissCommand = dynamic_cast<Goals::DismissHero &>(*task);
  961. const CGHeroInstance * dismissedHero = dismissCommand.getHero();
  962. auto role = ai->heroManager->getHeroRole(dismissedHero);
  963. auto mpLeft = dismissedHero->movementPointsRemaining();
  964. evaluationContext.movementCost += mpLeft;
  965. evaluationContext.movementCostByRole[role] += mpLeft;
  966. evaluationContext.goldCost += GameConstants::HERO_GOLD_COST + getArmyCost(dismissedHero);
  967. }
  968. };
  969. class BuildThisEvaluationContextBuilder : public IEvaluationContextBuilder
  970. {
  971. public:
  972. void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
  973. {
  974. if(task->goalType != Goals::BUILD_STRUCTURE)
  975. return;
  976. Goals::BuildThis & buildThis = dynamic_cast<Goals::BuildThis &>(*task);
  977. auto & bi = buildThis.buildingInfo;
  978. evaluationContext.goldReward += 7 * bi.dailyIncome[EGameResID::GOLD] / 2; // 7 day income but half we already have
  979. evaluationContext.heroRole = HeroRole::MAIN;
  980. evaluationContext.movementCostByRole[evaluationContext.heroRole] += bi.prerequisitesCount;
  981. int32_t cost = bi.buildCost[EGameResID::GOLD];
  982. evaluationContext.goldCost += cost;
  983. evaluationContext.closestWayRatio = 1;
  984. evaluationContext.buildingCost += bi.buildCostWithPrerequisites;
  985. if (bi.id == BuildingID::MARKETPLACE || bi.dailyIncome[EGameResID::WOOD] > 0)
  986. evaluationContext.isTradeBuilding = true;
  987. logAi->trace("Building costs for %s : %s MarketValue: %d",bi.toString(), evaluationContext.buildingCost.toString(), evaluationContext.buildingCost.marketValue());
  988. if(bi.creatureID != CreatureID::NONE)
  989. {
  990. evaluationContext.addNonCriticalStrategicalValue(buildThis.townInfo.armyStrength / 50000.0);
  991. if(bi.baseCreatureID == bi.creatureID)
  992. {
  993. evaluationContext.addNonCriticalStrategicalValue((0.5f + 0.1f * bi.creatureLevel) / (float)bi.prerequisitesCount);
  994. evaluationContext.armyReward += bi.armyStrength;
  995. }
  996. else
  997. {
  998. auto potentialUpgradeValue = evaluationContext.evaluator.getUpgradeArmyReward(buildThis.town, bi);
  999. evaluationContext.addNonCriticalStrategicalValue(potentialUpgradeValue / 10000.0f / (float)bi.prerequisitesCount);
  1000. evaluationContext.armyReward += potentialUpgradeValue / (float)bi.prerequisitesCount;
  1001. }
  1002. }
  1003. else if(bi.id == BuildingID::CITADEL || bi.id == BuildingID::CASTLE)
  1004. {
  1005. evaluationContext.addNonCriticalStrategicalValue(buildThis.town->creatures.size() * 0.2f);
  1006. evaluationContext.armyReward += buildThis.townInfo.armyStrength / 2;
  1007. }
  1008. else if(bi.id >= BuildingID::MAGES_GUILD_1 && bi.id <= BuildingID::MAGES_GUILD_5)
  1009. {
  1010. evaluationContext.skillReward += 2 * (bi.id - BuildingID::MAGES_GUILD_1);
  1011. for (auto hero : evaluationContext.evaluator.ai->cb->getHeroesInfo())
  1012. {
  1013. evaluationContext.armyInvolvement += hero->getArmyCost();
  1014. }
  1015. }
  1016. int sameTownBonus = 0;
  1017. for (auto town : evaluationContext.evaluator.ai->cb->getTownsInfo())
  1018. {
  1019. if (buildThis.town->getFaction() == town->getFaction())
  1020. sameTownBonus += town->getTownLevel();
  1021. }
  1022. evaluationContext.armyReward *= sameTownBonus;
  1023. if(evaluationContext.goldReward)
  1024. {
  1025. auto goldPressure = evaluationContext.evaluator.ai->buildAnalyzer->getGoldPressure();
  1026. evaluationContext.addNonCriticalStrategicalValue(evaluationContext.goldReward * goldPressure / 3500.0f / bi.prerequisitesCount);
  1027. }
  1028. if(bi.notEnoughRes && bi.prerequisitesCount == 1)
  1029. {
  1030. evaluationContext.strategicalValue /= 3;
  1031. evaluationContext.movementCostByRole[evaluationContext.heroRole] += 5;
  1032. evaluationContext.turn += 5;
  1033. }
  1034. }
  1035. };
  1036. uint64_t RewardEvaluator::getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const
  1037. {
  1038. if(ai->buildAnalyzer->hasAnyBuilding(town->getFaction(), bi.id))
  1039. return 0;
  1040. auto creaturesToUpgrade = ai->armyManager->getTotalCreaturesAvailable(bi.baseCreatureID);
  1041. auto upgradedPower = ai->armyManager->evaluateStackPower(bi.creatureID.toCreature(), creaturesToUpgrade.count);
  1042. return upgradedPower - creaturesToUpgrade.power;
  1043. }
  1044. PriorityEvaluator::PriorityEvaluator(const Nullkiller * ai)
  1045. :ai(ai)
  1046. {
  1047. initVisitTile();
  1048. evaluationContextBuilders.push_back(std::make_shared<ExecuteHeroChainEvaluationContextBuilder>(ai));
  1049. evaluationContextBuilders.push_back(std::make_shared<BuildThisEvaluationContextBuilder>());
  1050. evaluationContextBuilders.push_back(std::make_shared<ClusterEvaluationContextBuilder>(ai));
  1051. evaluationContextBuilders.push_back(std::make_shared<HeroExchangeEvaluator>());
  1052. evaluationContextBuilders.push_back(std::make_shared<ArmyUpgradeEvaluator>());
  1053. evaluationContextBuilders.push_back(std::make_shared<DefendTownEvaluator>());
  1054. evaluationContextBuilders.push_back(std::make_shared<ExchangeSwapTownHeroesContextBuilder>());
  1055. evaluationContextBuilders.push_back(std::make_shared<DismissHeroContextBuilder>(ai));
  1056. evaluationContextBuilders.push_back(std::make_shared<StayAtTownManaRecoveryEvaluator>());
  1057. evaluationContextBuilders.push_back(std::make_shared<ExplorePointEvaluator>());
  1058. }
  1059. EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal) const
  1060. {
  1061. Goals::TGoalVec parts;
  1062. EvaluationContext context(ai);
  1063. if(goal->goalType == Goals::COMPOSITION)
  1064. {
  1065. parts = goal->decompose(ai);
  1066. }
  1067. else
  1068. {
  1069. parts.push_back(goal);
  1070. }
  1071. for(auto subgoal : parts)
  1072. {
  1073. context.goldCost += subgoal->goldCost;
  1074. context.buildingCost += subgoal->buildingCost;
  1075. for(auto builder : evaluationContextBuilders)
  1076. {
  1077. builder->buildEvaluationContext(context, subgoal);
  1078. }
  1079. }
  1080. return context;
  1081. }
  1082. float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
  1083. {
  1084. auto evaluationContext = buildEvaluationContext(task);
  1085. int rewardType = (evaluationContext.goldReward > 0 ? 1 : 0)
  1086. + (evaluationContext.armyReward > 0 ? 1 : 0)
  1087. + (evaluationContext.skillReward > 0 ? 1 : 0)
  1088. + (evaluationContext.strategicalValue > 0 ? 1 : 0);
  1089. float goldRewardPerTurn = evaluationContext.goldReward / std::log2f(2 + evaluationContext.movementCost * 10);
  1090. double result = 0;
  1091. float fuzzyResult = 0;
  1092. try
  1093. {
  1094. armyLossPersentageVariable->setValue(evaluationContext.armyLossPersentage);
  1095. heroRoleVariable->setValue(evaluationContext.heroRole);
  1096. mainTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::MAIN]);
  1097. scoutTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::SCOUT]);
  1098. goldRewardVariable->setValue(goldRewardPerTurn);
  1099. armyRewardVariable->setValue(evaluationContext.armyReward);
  1100. armyGrowthVariable->setValue(evaluationContext.armyGrowth);
  1101. skillRewardVariable->setValue(evaluationContext.skillReward);
  1102. dangerVariable->setValue(evaluationContext.danger);
  1103. rewardTypeVariable->setValue(rewardType);
  1104. closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio);
  1105. strategicalValueVariable->setValue(evaluationContext.strategicalValue);
  1106. goldPressureVariable->setValue(ai->buildAnalyzer->getGoldPressure());
  1107. goldCostVariable->setValue(evaluationContext.goldCost / ((float)ai->getFreeResources()[EGameResID::GOLD] + (float)ai->buildAnalyzer->getDailyIncome()[EGameResID::GOLD] + 1.0f));
  1108. turnVariable->setValue(evaluationContext.turn);
  1109. fearVariable->setValue(evaluationContext.enemyHeroDangerRatio);
  1110. engine->process();
  1111. fuzzyResult = value->getValue();
  1112. }
  1113. catch (fl::Exception& fe)
  1114. {
  1115. logAi->error("evaluate VisitTile: %s", fe.getWhat());
  1116. }
  1117. if (ai->settings->isUseFuzzy())
  1118. {
  1119. result = fuzzyResult;
  1120. }
  1121. else
  1122. {
  1123. float score = 0;
  1124. float maxWillingToLose = ai->cb->getTownsInfo().empty() ? 1 : 0.25;
  1125. logAi->trace("BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, isDefend: %d, fuzzy: %f",
  1126. priorityTier,
  1127. task->toString(),
  1128. evaluationContext.armyLossPersentage,
  1129. (int)evaluationContext.turn,
  1130. evaluationContext.movementCostByRole[HeroRole::MAIN],
  1131. evaluationContext.movementCostByRole[HeroRole::SCOUT],
  1132. goldRewardPerTurn,
  1133. evaluationContext.goldCost,
  1134. evaluationContext.armyReward,
  1135. evaluationContext.armyGrowth,
  1136. evaluationContext.skillReward,
  1137. evaluationContext.danger,
  1138. evaluationContext.threatTurns,
  1139. evaluationContext.threat,
  1140. evaluationContext.heroRole == HeroRole::MAIN ? "main" : "scout",
  1141. evaluationContext.strategicalValue,
  1142. evaluationContext.conquestValue,
  1143. evaluationContext.closestWayRatio,
  1144. evaluationContext.enemyHeroDangerRatio,
  1145. evaluationContext.isDefend,
  1146. fuzzyResult);
  1147. switch (priorityTier)
  1148. {
  1149. case 1: //Take towns / kill heroes in immediate reach
  1150. {
  1151. if (evaluationContext.turn > 0)
  1152. return 0;
  1153. if(evaluationContext.conquestValue > 0)
  1154. score = 1000;
  1155. if (score == 0 || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty()))
  1156. return 0;
  1157. if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
  1158. return 0;
  1159. score *= evaluationContext.closestWayRatio;
  1160. if (evaluationContext.movementCost > 0)
  1161. score /= evaluationContext.movementCost;
  1162. break;
  1163. }
  1164. case 2: //Defend immediately threatened towns
  1165. {
  1166. if (evaluationContext.isDefend && evaluationContext.threatTurns == 0 && evaluationContext.turn == 0)
  1167. score = evaluationContext.armyInvolvement;
  1168. score *= evaluationContext.closestWayRatio;
  1169. break;
  1170. }
  1171. case 3: //Take towns / kill heroes that are further away
  1172. {
  1173. if (evaluationContext.conquestValue > 0)
  1174. score = 1000;
  1175. if (score == 0 || (evaluationContext.enemyHeroDangerRatio > 1 && (evaluationContext.turn > 0 || evaluationContext.isExchange) && !ai->cb->getTownsInfo().empty()))
  1176. return 0;
  1177. if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
  1178. return 0;
  1179. score *= evaluationContext.closestWayRatio;
  1180. if (evaluationContext.movementCost > 0)
  1181. score /= evaluationContext.movementCost;
  1182. break;
  1183. }
  1184. case 4: //Collect unguarded stuff
  1185. {
  1186. if (evaluationContext.enemyHeroDangerRatio > 1)
  1187. return 0;
  1188. if (evaluationContext.isDefend)
  1189. return 0;
  1190. if (evaluationContext.armyLossPersentage > 0)
  1191. return 0;
  1192. if (evaluationContext.involvesSailing && evaluationContext.movementCostByRole[HeroRole::MAIN] > 0)
  1193. return 0;
  1194. if (evaluationContext.buildingCost.marketValue() > 0)
  1195. return 0;
  1196. if (evaluationContext.closestWayRatio < 1)
  1197. return 0;
  1198. score += evaluationContext.strategicalValue * 1000;
  1199. score += evaluationContext.goldReward;
  1200. score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05;
  1201. score += evaluationContext.armyReward;
  1202. score += evaluationContext.armyGrowth;
  1203. if (score <= 0)
  1204. return 0;
  1205. else
  1206. score = 1000;
  1207. score *= evaluationContext.closestWayRatio;
  1208. if (evaluationContext.threat > evaluationContext.armyInvolvement && !evaluationContext.isDefend)
  1209. score *= evaluationContext.armyInvolvement / evaluationContext.threat;
  1210. if (evaluationContext.movementCost > 0)
  1211. score /= evaluationContext.movementCost;
  1212. break;
  1213. }
  1214. case 5: //Collect guarded stuff
  1215. {
  1216. if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend)
  1217. return 0;
  1218. if (evaluationContext.buildingCost.marketValue() > 0)
  1219. return 0;
  1220. if (evaluationContext.isDefend && (evaluationContext.enemyHeroDangerRatio < 1 || evaluationContext.threatTurns > 0 || evaluationContext.turn > 0))
  1221. return 0;
  1222. score += evaluationContext.strategicalValue * 1000;
  1223. score += evaluationContext.goldReward;
  1224. score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05;
  1225. score += evaluationContext.armyReward;
  1226. score += evaluationContext.armyGrowth;
  1227. score -= evaluationContext.goldCost;
  1228. score -= evaluationContext.armyInvolvement * evaluationContext.armyLossPersentage;
  1229. if (score > 0)
  1230. {
  1231. score *= evaluationContext.closestWayRatio;
  1232. if (evaluationContext.enemyHeroDangerRatio > 1)
  1233. score /= evaluationContext.enemyHeroDangerRatio;
  1234. if (evaluationContext.movementCost > 0)
  1235. score /= evaluationContext.movementCost;
  1236. score *= (maxWillingToLose - evaluationContext.armyLossPersentage);
  1237. }
  1238. break;
  1239. }
  1240. case 6: //Defend whatever if nothing else is to do
  1241. {
  1242. if (evaluationContext.enemyHeroDangerRatio > 1 && evaluationContext.isExchange)
  1243. return 0;
  1244. if (evaluationContext.isDefend)
  1245. score = evaluationContext.armyInvolvement;
  1246. score *= evaluationContext.closestWayRatio;
  1247. score /= (evaluationContext.turn + 1);
  1248. break;
  1249. }
  1250. case 0: //For buildings and buying army
  1251. {
  1252. if (maxWillingToLose - evaluationContext.armyLossPersentage < 0)
  1253. return 0;
  1254. //If we already have locked resources, we don't look at other buildings
  1255. if (ai->getLockedResources().marketValue() > 0)
  1256. return 0;
  1257. score += evaluationContext.conquestValue * 1000;
  1258. score += evaluationContext.strategicalValue * 1000;
  1259. score += evaluationContext.goldReward;
  1260. score += evaluationContext.skillReward * evaluationContext.armyInvolvement * (1 - evaluationContext.armyLossPersentage) * 0.05;
  1261. score += evaluationContext.armyReward;
  1262. score += evaluationContext.armyGrowth;
  1263. if (evaluationContext.buildingCost.marketValue() > 0)
  1264. {
  1265. if (!evaluationContext.isTradeBuilding && ai->getFreeResources()[EGameResID::WOOD] - evaluationContext.buildingCost[EGameResID::WOOD] < 5 && ai->buildAnalyzer->getDailyIncome()[EGameResID::WOOD] < 1)
  1266. {
  1267. logAi->trace("Should make sure to build market-place instead of %s", task->toString());
  1268. for (auto town : ai->cb->getTownsInfo())
  1269. {
  1270. if (!town->hasBuiltSomeTradeBuilding())
  1271. return 0;
  1272. }
  1273. }
  1274. score += 1000;
  1275. auto resourcesAvailable = evaluationContext.evaluator.ai->getFreeResources();
  1276. auto income = ai->buildAnalyzer->getDailyIncome();
  1277. score /= evaluationContext.buildingCost.marketValue();
  1278. if (resourcesAvailable < evaluationContext.buildingCost)
  1279. {
  1280. TResources needed = evaluationContext.buildingCost - resourcesAvailable;
  1281. needed.positive();
  1282. int turnsTo = needed.div(income);
  1283. if (turnsTo == INT_MAX)
  1284. return 0;
  1285. else
  1286. score /= turnsTo;
  1287. }
  1288. }
  1289. else
  1290. {
  1291. if (evaluationContext.enemyHeroDangerRatio > 1 && !evaluationContext.isDefend && evaluationContext.conquestValue == 0)
  1292. return 0;
  1293. }
  1294. break;
  1295. }
  1296. }
  1297. result = score;
  1298. }
  1299. #if NKAI_TRACE_LEVEL >= 2
  1300. logAi->trace("priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, fuzzy: %f, result %f",
  1301. priorityTier,
  1302. task->toString(),
  1303. evaluationContext.armyLossPersentage,
  1304. (int)evaluationContext.turn,
  1305. evaluationContext.movementCostByRole[HeroRole::MAIN],
  1306. evaluationContext.movementCostByRole[HeroRole::SCOUT],
  1307. goldRewardPerTurn,
  1308. evaluationContext.goldCost,
  1309. evaluationContext.armyReward,
  1310. evaluationContext.armyGrowth,
  1311. evaluationContext.skillReward,
  1312. evaluationContext.danger,
  1313. evaluationContext.threatTurns,
  1314. evaluationContext.threat,
  1315. evaluationContext.heroRole == HeroRole::MAIN ? "main" : "scout",
  1316. evaluationContext.strategicalValue,
  1317. evaluationContext.conquestValue,
  1318. evaluationContext.closestWayRatio,
  1319. evaluationContext.enemyHeroDangerRatio,
  1320. fuzzyResult,
  1321. result);
  1322. #endif
  1323. return result;
  1324. }
  1325. }