PriorityEvaluator.cpp 28 KB

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