Nullkiller.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  1. /*
  2. * Nullkiller.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 "Nullkiller.h"
  12. #include "../../../lib/CPlayerState.h"
  13. #include "../../lib/StartInfo.h"
  14. #include "../../lib/pathfinder/PathfinderCache.h"
  15. #include "../../lib/pathfinder/PathfinderOptions.h"
  16. #include "../AIGateway.h"
  17. #include "../Behaviors/BuildingBehavior.h"
  18. #include "../Behaviors/BuyArmyBehavior.h"
  19. #include "../Behaviors/CaptureObjectsBehavior.h"
  20. #include "../Behaviors/ClusterBehavior.h"
  21. #include "../Behaviors/DefenceBehavior.h"
  22. #include "../Behaviors/ExplorationBehavior.h"
  23. #include "../Behaviors/GatherArmyBehavior.h"
  24. #include "../Behaviors/RecruitHeroBehavior.h"
  25. #include "../Behaviors/StayAtTownBehavior.h"
  26. #include "../Goals/Invalid.h"
  27. #include "Goals/RecruitHero.h"
  28. #include <boost/range/algorithm/sort.hpp>
  29. namespace NK2AI
  30. {
  31. using namespace Goals;
  32. // while we play vcmieagles graph can be shared
  33. std::unique_ptr<ObjectGraph> Nullkiller::baseGraph;
  34. Nullkiller::Nullkiller()
  35. : activeHero(nullptr)
  36. , scanDepth(ScanDepth::MAIN_FULL)
  37. , useHeroChain(true)
  38. , memory(std::make_unique<AIMemory>())
  39. {
  40. }
  41. Nullkiller::~Nullkiller() = default;
  42. bool canUseOpenMap(const std::shared_ptr<CCallback>& cb, const PlayerColor playerID)
  43. {
  44. if(!cb->getStartInfo()->extraOptionsInfo.cheatsAllowed)
  45. {
  46. return false;
  47. }
  48. const TeamState * team = cb->getPlayerTeam(playerID);
  49. const auto hasHumanInTeam = vstd::contains_if(
  50. team->players,
  51. [cb](const PlayerColor teamMateID) -> bool
  52. {
  53. return cb->getPlayerState(teamMateID)->isHuman();
  54. }
  55. );
  56. return !hasHumanInTeam;
  57. }
  58. void Nullkiller::init(const std::shared_ptr<CCallback> & cbInput, AIGateway * aiGwInput)
  59. {
  60. cc = cbInput;
  61. aiGw = aiGwInput;
  62. playerID = aiGwInput->playerID;
  63. settings = std::make_unique<Settings>(cc->getStartInfo()->difficulty);
  64. PathfinderOptions pathfinderOptions(*cc);
  65. pathfinderOptions.useTeleportTwoWay = true;
  66. pathfinderOptions.useTeleportOneWay = settings->isOneWayMonolithUsageAllowed();
  67. pathfinderOptions.useTeleportOneWayRandom = settings->isOneWayMonolithUsageAllowed();
  68. pathfinderCache = std::make_unique<PathfinderCache>(cc.get(), pathfinderOptions);
  69. if(canUseOpenMap(cc, playerID))
  70. {
  71. useObjectGraph = settings->isObjectGraphAllowed();
  72. openMap = settings->isOpenMap() || useObjectGraph;
  73. }
  74. else
  75. {
  76. useObjectGraph = false;
  77. openMap = false;
  78. }
  79. baseGraph.reset();
  80. priorityEvaluator.reset(new PriorityEvaluator(this));
  81. priorityEvaluators.reset(
  82. new SharedPool<PriorityEvaluator>(
  83. [&]()->std::unique_ptr<PriorityEvaluator>
  84. {
  85. return std::make_unique<PriorityEvaluator>(this);
  86. }));
  87. dangerHitMap.reset(new DangerHitMapAnalyzer(this));
  88. buildAnalyzer.reset(new BuildAnalyzer(this));
  89. objectClusterizer.reset(new ObjectClusterizer(this));
  90. dangerEvaluator.reset(new FuzzyHelper(this));
  91. pathfinder.reset(new AIPathfinder(cc.get(), this));
  92. armyManager.reset(new ArmyManager(cc.get(), this));
  93. heroManager.reset(new HeroManager(cc.get(), this));
  94. decomposer.reset(new DeepDecomposer(this));
  95. armyFormation.reset(new ArmyFormation(cc, this));
  96. }
  97. TaskPlanItem::TaskPlanItem(const TSubgoal & task)
  98. : affectedObjects(task->asTask()->getAffectedObjects())
  99. , task(task)
  100. {
  101. }
  102. Goals::TTaskVec TaskPlan::getTasks() const
  103. {
  104. Goals::TTaskVec result;
  105. for(auto & item : tasks)
  106. {
  107. result.push_back(taskptr(*item.task));
  108. }
  109. vstd::removeDuplicates(result);
  110. return result;
  111. }
  112. void TaskPlan::merge(const TSubgoal & task)
  113. {
  114. TGoalVec blockers;
  115. if (task->asTask()->priority <= 0)
  116. return;
  117. for(auto & item : tasks)
  118. {
  119. for(auto objid : item.affectedObjects)
  120. {
  121. if(task == item.task || task->asTask()->isObjectAffected(objid) || (task->asTask()->getHero() != nullptr && task->asTask()->getHero() == item.task->asTask()->getHero()))
  122. {
  123. if(item.task->asTask()->priority >= task->asTask()->priority)
  124. return;
  125. blockers.push_back(item.task);
  126. break;
  127. }
  128. }
  129. }
  130. vstd::erase_if(tasks, [&](const TaskPlanItem & task2)
  131. {
  132. return vstd::contains(blockers, task2.task);
  133. });
  134. tasks.emplace_back(task);
  135. }
  136. Goals::TTask Nullkiller::choseBestTask(Goals::TGoalVec & tasks) const
  137. {
  138. if(tasks.empty())
  139. {
  140. return taskptr(Invalid());
  141. }
  142. for(const TSubgoal & task : tasks)
  143. {
  144. if(task->asTask()->priority <= 0)
  145. task->asTask()->priority = priorityEvaluator->evaluate(task);
  146. }
  147. auto bestTask = *vstd::maxElementByFun(tasks, [](const Goals::TSubgoal& task) -> float
  148. {
  149. return task->asTask()->priority;
  150. });
  151. return taskptr(*bestTask);
  152. }
  153. Goals::TTaskVec Nullkiller::buildPlan(TGoalVec & tasks, int priorityTier) const
  154. {
  155. TaskPlan taskPlan;
  156. tbb::parallel_for(tbb::blocked_range<size_t>(0, tasks.size()), [this, &tasks, priorityTier](const tbb::blocked_range<size_t> & r)
  157. {
  158. SET_GLOBAL_STATE_TBB(this->aiGw);
  159. auto evaluator = this->priorityEvaluators->acquire();
  160. for(size_t i = r.begin(); i != r.end(); i++)
  161. {
  162. const auto & task = tasks[i];
  163. if(task->asTask()->priority <= 0 || priorityTier != PriorityEvaluator::PriorityTier::BUILDINGS)
  164. task->asTask()->priority = evaluator->evaluate(task, priorityTier);
  165. }
  166. });
  167. boost::range::sort(tasks, [](const TSubgoal& g1, const TSubgoal& g2) -> bool
  168. {
  169. return g2->asTask()->priority < g1->asTask()->priority;
  170. });
  171. for(const TSubgoal & task : tasks)
  172. {
  173. taskPlan.merge(task);
  174. }
  175. return taskPlan.getTasks();
  176. }
  177. void Nullkiller::decompose(Goals::TGoalVec & results, const Goals::TSubgoal& behavior, int decompositionMaxDepth) const
  178. {
  179. makingTurnInterruption.interruptionPoint();
  180. logAi->debug("Decomposing behavior %s", behavior->toString());
  181. const auto start = std::chrono::high_resolution_clock::now();
  182. decomposer->decompose(results, behavior, decompositionMaxDepth);
  183. makingTurnInterruption.interruptionPoint();
  184. logAi->debug("Decomposing behavior %s done in %ld", behavior->toString(), timeElapsed(start));
  185. }
  186. void Nullkiller::resetState()
  187. {
  188. std::unique_lock lockGuard(aiStateMutex);
  189. lockedResources = TResources();
  190. scanDepth = ScanDepth::MAIN_FULL;
  191. lockedHeroes.clear();
  192. dangerHitMap->resetHitmap();
  193. useHeroChain = true;
  194. objectClusterizer->reset();
  195. if(!baseGraph && isObjectGraphAllowed())
  196. {
  197. baseGraph = std::make_unique<ObjectGraph>();
  198. baseGraph->updateGraph(this);
  199. }
  200. }
  201. void Nullkiller::invalidatePathfinderData()
  202. {
  203. pathfinderInvalidated = true;
  204. }
  205. void Nullkiller::updateState()
  206. {
  207. #if NK2AI_TRACE_LEVEL >= 1
  208. logAi->info("PERFORMANCE: AI updateState started");
  209. #endif
  210. makingTurnInterruption.interruptionPoint();
  211. std::unique_lock lockGuard(aiStateMutex);
  212. const auto start = std::chrono::high_resolution_clock::now();
  213. activeHero = nullptr;
  214. setTargetObject(-1);
  215. decomposer->reset();
  216. buildAnalyzer->update();
  217. if(!pathfinderInvalidated && dangerHitMap->isHitMapUpToDate() && dangerHitMap->isTileOwnersUpToDate())
  218. logAi->trace("Skipping full state regeneration - up to date");
  219. else
  220. {
  221. memory->removeInvisibleObjects(cc.get());
  222. dangerHitMap->updateHitMap();
  223. dangerHitMap->calculateTileOwners();
  224. makingTurnInterruption.interruptionPoint();
  225. heroManager->update();
  226. logAi->trace("Updating paths");
  227. PathfinderSettings cfg;
  228. cfg.useHeroChain = useHeroChain;
  229. cfg.allowBypassObjects = true;
  230. if(scanDepth == ScanDepth::SMALL || isObjectGraphAllowed())
  231. {
  232. cfg.mainTurnDistanceLimit = settings->getMainHeroTurnDistanceLimit();
  233. }
  234. if(scanDepth != ScanDepth::ALL_FULL || isObjectGraphAllowed())
  235. {
  236. cfg.scoutTurnDistanceLimit = settings->getScoutHeroTurnDistanceLimit();
  237. }
  238. makingTurnInterruption.interruptionPoint();
  239. const auto heroes = getHeroesForPathfinding();
  240. pathfinder->updatePaths(heroes, cfg);
  241. if(isObjectGraphAllowed())
  242. {
  243. pathfinder->updateGraphs(
  244. heroes,
  245. scanDepth == ScanDepth::SMALL ? PathfinderSettings::MaxTurnDistanceLimit : 10,
  246. scanDepth == ScanDepth::ALL_FULL ? PathfinderSettings::MaxTurnDistanceLimit : 3);
  247. }
  248. makingTurnInterruption.interruptionPoint();
  249. objectClusterizer->clusterize();
  250. pathfinderInvalidated = false;
  251. }
  252. armyManager->update();
  253. #if NK2AI_TRACE_LEVEL >= 1
  254. if(const auto timeElapsedMs = timeElapsed(start); timeElapsedMs > 499)
  255. {
  256. logAi->warn("PERFORMANCE: AI updateState took %ld ms", timeElapsedMs);
  257. }
  258. else
  259. {
  260. logAi->info("PERFORMANCE: AI updateState took %ld ms", timeElapsedMs);
  261. }
  262. #endif
  263. }
  264. bool Nullkiller::isHeroLocked(const CGHeroInstance * hero) const
  265. {
  266. return getHeroLockedReason(hero) != HeroLockedReason::NOT_LOCKED;
  267. }
  268. bool Nullkiller::arePathHeroesLocked(const AIPath & path) const
  269. {
  270. if(getHeroLockedReason(path.targetHero) == HeroLockedReason::STARTUP)
  271. {
  272. #if NK2AI_TRACE_LEVEL >= 1
  273. logAi->trace("Hero %s is locked by STARTUP. Discarding %s", path.targetHero->getObjectName(), path.toString());
  274. #endif
  275. return true;
  276. }
  277. for(const auto & node : path.nodes)
  278. {
  279. auto lockReason = getHeroLockedReason(node.targetHero);
  280. if(lockReason != HeroLockedReason::NOT_LOCKED)
  281. {
  282. #if NK2AI_TRACE_LEVEL >= 1
  283. logAi->trace("Hero %s is locked by %d. Discarding %s", path.targetHero->getObjectName(), (int)lockReason, path.toString());
  284. #endif
  285. return true;
  286. }
  287. }
  288. return false;
  289. }
  290. HeroLockedReason Nullkiller::getHeroLockedReason(const CGHeroInstance * hero) const
  291. {
  292. auto found = lockedHeroes.find(hero);
  293. return found != lockedHeroes.end() ? found->second : HeroLockedReason::NOT_LOCKED;
  294. }
  295. void Nullkiller::makeTurn()
  296. {
  297. std::lock_guard<std::mutex> sharedStorageLock(AISharedStorage::locker);
  298. const int MAX_DEPTH = 10;
  299. resetState();
  300. Goals::TGoalVec tasks;
  301. tracePlayerStatus(true);
  302. for(int i = 1; i <= settings->getMaxPass() && cc->getPlayerStatus(playerID) == EPlayerStatus::INGAME; i++)
  303. {
  304. if (!updateStateAndExecutePriorityPass(tasks, i)) return;
  305. tasks.clear();
  306. decompose(tasks, sptr(CaptureObjectsBehavior()), 1);
  307. decompose(tasks, sptr(ClusterBehavior()), MAX_DEPTH);
  308. decompose(tasks, sptr(DefenceBehavior()), MAX_DEPTH);
  309. decompose(tasks, sptr(GatherArmyBehavior()), MAX_DEPTH);
  310. decompose(tasks, sptr(StayAtTownBehavior()), MAX_DEPTH);
  311. if(!isOpenMap())
  312. decompose(tasks, sptr(ExplorationBehavior()), MAX_DEPTH);
  313. TTaskVec selectedTasks;
  314. #if NK2AI_TRACE_LEVEL >= 1
  315. int prioOfTask = 0;
  316. #endif
  317. for (int prio = PriorityEvaluator::PriorityTier::INSTAKILL; prio <= PriorityEvaluator::PriorityTier::MAX_PRIORITY_TIER; ++prio)
  318. {
  319. #if NK2AI_TRACE_LEVEL >= 1
  320. prioOfTask = prio;
  321. #endif
  322. selectedTasks = buildPlan(tasks, prio);
  323. if (!selectedTasks.empty() || settings->isUseFuzzy())
  324. break;
  325. }
  326. boost::range::sort(selectedTasks, [](const TTask& a, const TTask& b)
  327. {
  328. return a->priority > b->priority;
  329. });
  330. if(selectedTasks.empty())
  331. {
  332. selectedTasks.push_back(taskptr(Goals::Invalid()));
  333. }
  334. bool hasAnySuccess = false;
  335. for(const auto& selectedTask : selectedTasks)
  336. {
  337. if(cc->getPlayerStatus(playerID) != EPlayerStatus::INGAME)
  338. return;
  339. if(!areAffectedObjectsPresent(selectedTask))
  340. {
  341. logAi->debug("Affected object not found. Canceling task.");
  342. continue;
  343. }
  344. std::string taskDescription = selectedTask->toString();
  345. HeroRole heroRole = getTaskRole(selectedTask);
  346. if(heroRole != HeroRole::MAIN || selectedTask->getHeroExchangeCount() <= 1)
  347. useHeroChain = false;
  348. // TODO: better to check turn distance here instead of priority
  349. if((heroRole != HeroRole::MAIN || selectedTask->priority < SMALL_SCAN_MIN_PRIORITY)
  350. && scanDepth == ScanDepth::MAIN_FULL)
  351. {
  352. useHeroChain = false;
  353. scanDepth = ScanDepth::SMALL;
  354. logAi->trace(
  355. "Goal %s has low priority %f so decreasing scan depth to gain performance.",
  356. taskDescription,
  357. selectedTask->priority);
  358. }
  359. if((settings->isUseFuzzy() && selectedTask->priority < MIN_PRIORITY) || (!settings->isUseFuzzy() && selectedTask->priority <= 0))
  360. {
  361. auto heroes = cc->getHeroesInfo();
  362. const auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool
  363. {
  364. return h->movementPointsRemaining() > 100;
  365. });
  366. if(hasMp && scanDepth != ScanDepth::ALL_FULL)
  367. {
  368. logAi->trace(
  369. "Goal %s has too low priority %f so increasing scan depth to full.",
  370. taskDescription,
  371. selectedTask->priority);
  372. scanDepth = ScanDepth::ALL_FULL;
  373. useHeroChain = false;
  374. hasAnySuccess = true;
  375. break;
  376. }
  377. logAi->trace("Goal %s has too low priority. It is not worth doing it.", taskDescription);
  378. continue;
  379. }
  380. logAi->info("Pass %d: Performing prio %d task %s with prio: %d", i, prioOfTask, selectedTask->toString(), selectedTask->priority);
  381. if(HeroPtr heroPtr(selectedTask->getHero(), cc); selectedTask->getHero() && !heroPtr.isVerified(false))
  382. {
  383. logAi->warn("Nullkiller::makeTurn Skipping pass due to unverified hero: %s", heroPtr.nameOrDefault());
  384. }
  385. else
  386. {
  387. if(!executeTask(selectedTask))
  388. {
  389. if(hasAnySuccess)
  390. break;
  391. return;
  392. }
  393. hasAnySuccess = true;
  394. }
  395. }
  396. hasAnySuccess |= handleTrading();
  397. if(!hasAnySuccess)
  398. {
  399. logAi->trace("Nothing was done this turn. Ending turn.");
  400. tracePlayerStatus(false);
  401. return;
  402. }
  403. for (const auto *heroInfo : cc->getHeroesInfo())
  404. AIGateway::pickBestArtifacts(cc, heroInfo);
  405. if(i == settings->getMaxPass())
  406. {
  407. logAi->warn("MaxPass reached. Terminating AI turn.");
  408. }
  409. }
  410. }
  411. bool Nullkiller::updateStateAndExecutePriorityPass(Goals::TGoalVec & tempResults, const int passIndex)
  412. {
  413. updateState();
  414. Goals::TTask bestPrioPassTask = taskptr(Goals::Invalid());
  415. for(int i = 1; i <= settings->getMaxPriorityPass() && cc->getPlayerStatus(playerID) == EPlayerStatus::INGAME; i++)
  416. {
  417. tempResults.clear();
  418. decompose(tempResults, sptr(RecruitHeroBehavior()), 1);
  419. decompose(tempResults, sptr(BuyArmyBehavior()), 1);
  420. decompose(tempResults, sptr(BuildingBehavior()), 1);
  421. bestPrioPassTask = choseBestTask(tempResults);
  422. if(bestPrioPassTask->priority > 0)
  423. {
  424. logAi->info("Pass %d: Performing priorityPass %d task %s with prio: %d", passIndex, i, bestPrioPassTask->toString(), bestPrioPassTask->priority);
  425. const bool isRecruitHeroGoal = dynamic_cast<RecruitHero*>(bestPrioPassTask.get()) != nullptr;
  426. HeroPtr heroPtr(bestPrioPassTask->getHero(), cc);
  427. if(!isRecruitHeroGoal && bestPrioPassTask->getHero() && !heroPtr.isVerified(false))
  428. {
  429. logAi->warn("Nullkiller::updateStateAndExecutePriorityPass Skipping priorityPass due to unverified hero: %s", heroPtr.nameOrDefault());
  430. }
  431. else if(!executeTask(bestPrioPassTask))
  432. {
  433. logAi->warn("Task failed to execute");
  434. return false;
  435. }
  436. updateState();
  437. }
  438. else
  439. {
  440. break;
  441. }
  442. if(i == settings->getMaxPriorityPass())
  443. {
  444. logAi->warn("MaxPriorityPass reached. Terminating priorityPass loop.");
  445. }
  446. }
  447. return true;
  448. }
  449. bool Nullkiller::areAffectedObjectsPresent(const Goals::TTask & task) const
  450. {
  451. auto affectedObjs = task->getAffectedObjects();
  452. for(auto oid : affectedObjs)
  453. {
  454. if(!cc->getObj(oid, false))
  455. return false;
  456. }
  457. return true;
  458. }
  459. HeroRole Nullkiller::getTaskRole(const Goals::TTask & task) const
  460. {
  461. HeroPtr heroPtr(task->getHero(), cc);
  462. HeroRole heroRole = HeroRole::MAIN;
  463. if(heroPtr.isVerified())
  464. heroRole = heroManager->getHeroRoleOrDefault(heroPtr);
  465. return heroRole;
  466. }
  467. bool Nullkiller::executeTask(const Goals::TTask & task) const
  468. {
  469. auto start = std::chrono::high_resolution_clock::now();
  470. std::string taskDescr = task->toString();
  471. makingTurnInterruption.interruptionPoint();
  472. logAi->debug("Trying to realize %s (value %2.3f)", taskDescr, task->priority);
  473. try
  474. {
  475. task->accept(aiGw);
  476. logAi->trace("Task %s completed in %lld", taskDescr, timeElapsed(start));
  477. }
  478. catch(goalFulfilledException &)
  479. {
  480. logAi->trace("Task %s completed in %lld", taskDescr, timeElapsed(start));
  481. }
  482. catch(cannotFulfillGoalException & e)
  483. {
  484. logAi->error("Failed to realize subgoal of type %s, I will stop.", taskDescr);
  485. logAi->error("The error message was: %s", e.what());
  486. return false;
  487. }
  488. return true;
  489. }
  490. TResources Nullkiller::getFreeResources() const
  491. {
  492. auto freeRes = cc->getResourceAmount() - lockedResources;
  493. freeRes.positive();
  494. return freeRes;
  495. }
  496. void Nullkiller::lockResources(const TResources & res)
  497. {
  498. lockedResources += res;
  499. }
  500. bool Nullkiller::handleTrading()
  501. {
  502. bool haveTraded = false;
  503. bool shouldTryToTrade = true;
  504. ObjectInstanceID marketId;
  505. for (const auto town : cc->getTownsInfo())
  506. {
  507. if (town->hasBuiltSomeTradeBuilding())
  508. {
  509. marketId = town->id;
  510. }
  511. }
  512. if (!marketId.hasValue())
  513. return false;
  514. if (const CGObjectInstance* obj = cc->getObj(marketId, false))
  515. {
  516. if (const auto* m = dynamic_cast<const IMarket*>(obj))
  517. {
  518. while (shouldTryToTrade)
  519. {
  520. shouldTryToTrade = false;
  521. buildAnalyzer->update();
  522. TResources required = buildAnalyzer->getTotalResourcesRequired();
  523. TResources income = buildAnalyzer->getDailyIncome();
  524. TResources available = cc->getResourceAmount();
  525. #if NK2AI_TRACE_LEVEL >= 2
  526. logAi->debug("Available %s", available.toString());
  527. logAi->debug("Required %s", required.toString());
  528. #endif
  529. int mostWanted = -1;
  530. int mostExpendable = -1;
  531. float minRatio = std::numeric_limits<float>::max();
  532. float maxRatio = std::numeric_limits<float>::min();
  533. for (int i = 0; i < required.size(); ++i)
  534. {
  535. if (required[i] <= 0)
  536. continue;
  537. float ratio = static_cast<float>(available[i]) / required[i];
  538. if (ratio < minRatio) {
  539. minRatio = ratio;
  540. mostWanted = i;
  541. }
  542. }
  543. for (int i = 0; i < required.size(); ++i)
  544. {
  545. float ratio;
  546. if (required[i] > 0)
  547. ratio = static_cast<float>(available[i]) / required[i];
  548. else
  549. ratio = available[i];
  550. bool okToSell = false;
  551. if (i == GameResID::GOLD)
  552. {
  553. if (income[i] > 0 && !buildAnalyzer->isGoldPressureOverMax())
  554. okToSell = true;
  555. }
  556. else
  557. {
  558. if (required[i] <= 0 && income[i] > 0)
  559. okToSell = true;
  560. }
  561. if (ratio > maxRatio && okToSell) {
  562. maxRatio = ratio;
  563. mostExpendable = i;
  564. }
  565. }
  566. #if NK2AI_TRACE_LEVEL >= 2
  567. logAi->debug("mostExpendable: %d mostWanted: %d", mostExpendable, mostWanted);
  568. #endif
  569. if (mostExpendable == mostWanted || mostWanted == -1 || mostExpendable == -1)
  570. return false;
  571. int toGive;
  572. int toGet;
  573. m->getOffer(mostExpendable, mostWanted, toGive, toGet, EMarketMode::RESOURCE_RESOURCE);
  574. //logAi->info("Offer is: I get %d of %s for %d of %s at %s", toGet, mostWanted, toGive, mostExpendable, obj->getObjectName());
  575. //TODO trade only as much as needed
  576. if (toGive && toGive <= available[mostExpendable]) //don't try to sell 0 resources
  577. {
  578. cc->trade(m->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, GameResID(mostExpendable), GameResID(mostWanted), toGive);
  579. #if NK2AI_TRACE_LEVEL >= 2
  580. logAi->info("Traded %d of %s for %d of %s at %s", toGive, mostExpendable, toGet, mostWanted, obj->getObjectName());
  581. #endif
  582. haveTraded = true;
  583. shouldTryToTrade = true;
  584. }
  585. }
  586. }
  587. }
  588. return haveTraded;
  589. }
  590. std::shared_ptr<const CPathsInfo> Nullkiller::getPathsInfo(const CGHeroInstance * h) const
  591. {
  592. return pathfinderCache->getPathsInfo(h);
  593. }
  594. void Nullkiller::invalidatePaths()
  595. {
  596. pathfinderCache->invalidatePaths();
  597. }
  598. void Nullkiller::tracePlayerStatus(bool beginning) const
  599. {
  600. #if NK2AI_TRACE_LEVEL >= 1
  601. float totalHeroesStrength = 0;
  602. int totalTownsLevel = 0;
  603. for (const auto *heroInfo : cc->getHeroesInfo())
  604. {
  605. totalHeroesStrength += heroInfo->getTotalStrength();
  606. }
  607. for (const auto *townInfo : cc->getTownsInfo())
  608. {
  609. totalTownsLevel += townInfo->getTownLevel();
  610. }
  611. const auto *firstWord = beginning ? "Beginning:" : "End:";
  612. logAi->info("%s totalHeroesStrength: %f, totalTownsLevel: %d, resources: %s", firstWord, totalHeroesStrength, totalTownsLevel, cc->getResourceAmount().toString());
  613. #endif
  614. }
  615. std::map<const CGHeroInstance *, HeroRole> Nullkiller::getHeroesForPathfinding() const
  616. {
  617. std::map<const CGHeroInstance *, HeroRole> activeHeroes;
  618. for(auto hero : cc->getHeroesInfo())
  619. {
  620. if(getHeroLockedReason(hero) == HeroLockedReason::DEFENCE)
  621. continue;
  622. activeHeroes[hero] = heroManager->getHeroRoleOrDefaultInefficient(hero);
  623. }
  624. return activeHeroes;
  625. }
  626. }