ObjectGraph.cpp 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. /*
  2. * ObjectGraph.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 "ObjectGraph.h"
  12. #include "AIPathfinderConfig.h"
  13. #include "../../../lib/CRandomGenerator.h"
  14. #include "../../../CCallback.h"
  15. #include "../../../lib/mapping/CMap.h"
  16. #include "../Engine/Nullkiller.h"
  17. #include "../../../lib/logging/VisualLogger.h"
  18. namespace NKAI
  19. {
  20. class ObjectGraphCalculator
  21. {
  22. private:
  23. ObjectGraph * target;
  24. const Nullkiller * ai;
  25. std::map<const CGHeroInstance *, HeroRole> actors;
  26. std::map<const CGHeroInstance *, const CGObjectInstance *> actorObjectMap;
  27. std::vector<std::unique_ptr<CGBoat>> temporaryBoats;
  28. std::vector<std::unique_ptr<CGHeroInstance>> temporaryActorHeroes;
  29. public:
  30. ObjectGraphCalculator(ObjectGraph * target, const Nullkiller * ai)
  31. :ai(ai), target(target)
  32. {
  33. for(auto obj : ai->memory->visitableObjs)
  34. {
  35. if(obj && obj->isVisitable() && obj->ID != Obj::HERO)
  36. {
  37. addObjectActor(obj);
  38. }
  39. }
  40. for(auto town : ai->cb->getTownsInfo())
  41. {
  42. addObjectActor(town);
  43. }
  44. PathfinderSettings ps;
  45. ps.mainTurnDistanceLimit = 5;
  46. ps.scoutTurnDistanceLimit = 1;
  47. ps.allowBypassObjects = false;
  48. ai->pathfinder->updatePaths(actors, ps);
  49. }
  50. void calculateConnections(const int3 & pos)
  51. {
  52. auto guarded = ai->cb->getGuardingCreaturePosition(pos).valid();
  53. if(guarded)
  54. return;
  55. auto paths = ai->pathfinder->getPathInfo(pos);
  56. for(AIPath & path1 : paths)
  57. {
  58. for(AIPath & path2 : paths)
  59. {
  60. if(path1.targetHero == path2.targetHero)
  61. continue;
  62. auto obj1 = actorObjectMap[path1.targetHero];
  63. auto obj2 = actorObjectMap[path2.targetHero];
  64. auto tile1 = cb->getTile(obj1->visitablePos());
  65. auto tile2 = cb->getTile(obj2->visitablePos());
  66. if(tile2->isWater() && !tile1->isWater())
  67. {
  68. auto linkTile = cb->getTile(pos);
  69. if(!linkTile->isWater() || obj1->ID != Obj::BOAT || obj1->ID != Obj::SHIPYARD)
  70. continue;
  71. }
  72. auto danger = ai->pathfinder->getStorage()->evaluateDanger(obj2->visitablePos(), path1.targetHero, true);
  73. auto updated = target->tryAddConnection(
  74. obj1->visitablePos(),
  75. obj2->visitablePos(),
  76. path1.movementCost() + path2.movementCost(),
  77. danger);
  78. if(NKAI_GRAPH_TRACE_LEVEL >= 2 && updated)
  79. {
  80. logAi->trace(
  81. "Connected %s[%s] -> %s[%s] through [%s], cost %2f",
  82. obj1->getObjectName(), obj1->visitablePos().toString(),
  83. obj2->getObjectName(), obj2->visitablePos().toString(),
  84. pos.toString(),
  85. path1.movementCost() + path2.movementCost());
  86. }
  87. }
  88. }
  89. }
  90. private:
  91. void addObjectActor(const CGObjectInstance * obj)
  92. {
  93. auto objectActor = temporaryActorHeroes.emplace_back(std::make_unique<CGHeroInstance>(obj->cb)).get();
  94. CRandomGenerator rng;
  95. auto visitablePos = obj->visitablePos();
  96. objectActor->setOwner(ai->playerID); // lets avoid having multiple colors
  97. objectActor->initHero(rng, static_cast<HeroTypeID>(0));
  98. objectActor->pos = objectActor->convertFromVisitablePos(visitablePos);
  99. objectActor->initObj(rng);
  100. if(cb->getTile(visitablePos)->isWater())
  101. {
  102. objectActor->boat = temporaryBoats.emplace_back(std::make_unique<CGBoat>(objectActor->cb)).get();
  103. }
  104. actorObjectMap[objectActor] = obj;
  105. actors[objectActor] = obj->ID == Obj::TOWN || obj->ID == Obj::SHIPYARD ? HeroRole::MAIN : HeroRole::SCOUT;
  106. target->addObject(obj);
  107. };
  108. };
  109. bool ObjectGraph::tryAddConnection(
  110. const int3 & from,
  111. const int3 & to,
  112. float cost,
  113. uint64_t danger)
  114. {
  115. return nodes[from].connections[to].update(cost, danger);
  116. }
  117. void ObjectGraph::updateGraph(const Nullkiller * ai)
  118. {
  119. auto cb = ai->cb;
  120. ObjectGraphCalculator calculator(this, ai);
  121. foreach_tile_pos(cb.get(), [this, &calculator](const CPlayerSpecificInfoCallback * cb, const int3 & pos)
  122. {
  123. if(nodes.find(pos) != nodes.end())
  124. return;
  125. calculator.calculateConnections(pos);
  126. });
  127. if(NKAI_GRAPH_TRACE_LEVEL >= 1)
  128. dumpToLog("graph");
  129. }
  130. void ObjectGraph::addObject(const CGObjectInstance * obj)
  131. {
  132. nodes[obj->visitablePos()].init(obj);
  133. }
  134. void ObjectGraph::removeObject(const CGObjectInstance * obj)
  135. {
  136. nodes[obj->visitablePos()].objectExists = false;
  137. if(obj->ID == Obj::BOAT)
  138. {
  139. vstd::erase_if(nodes[obj->visitablePos()].connections, [&](const std::pair<int3, ObjectLink> & link) -> bool
  140. {
  141. auto tile = cb->getTile(link.first, false);
  142. return tile && tile->isWater();
  143. });
  144. }
  145. }
  146. void ObjectGraph::connectHeroes(const Nullkiller * ai)
  147. {
  148. for(auto obj : ai->memory->visitableObjs)
  149. {
  150. if(obj && obj->ID == Obj::HERO)
  151. {
  152. addObject(obj);
  153. }
  154. }
  155. for(auto & node : nodes)
  156. {
  157. auto pos = node.first;
  158. auto paths = ai->pathfinder->getPathInfo(pos);
  159. for(AIPath & path : paths)
  160. {
  161. auto heroPos = path.targetHero->visitablePos();
  162. nodes[pos].connections[heroPos].update(
  163. path.movementCost(),
  164. path.getPathDanger());
  165. nodes[heroPos].connections[pos].update(
  166. path.movementCost(),
  167. path.getPathDanger());
  168. }
  169. }
  170. }
  171. void ObjectGraph::dumpToLog(std::string visualKey) const
  172. {
  173. logVisual->updateWithLock(visualKey, [&](IVisualLogBuilder & logBuilder)
  174. {
  175. for(auto & tile : nodes)
  176. {
  177. for(auto & node : tile.second.connections)
  178. {
  179. if(NKAI_GRAPH_TRACE_LEVEL >= 2)
  180. {
  181. logAi->trace(
  182. "%s -> %s: %f !%d",
  183. node.first.toString(),
  184. tile.first.toString(),
  185. node.second.cost,
  186. node.second.danger);
  187. }
  188. logBuilder.addLine(tile.first, node.first);
  189. }
  190. }
  191. });
  192. }
  193. bool GraphNodeComparer::operator()(const GraphPathNodePointer & lhs, const GraphPathNodePointer & rhs) const
  194. {
  195. return pathNodes.at(lhs.coord)[lhs.nodeType].cost > pathNodes.at(rhs.coord)[rhs.nodeType].cost;
  196. }
  197. void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkiller * ai)
  198. {
  199. graph = *ai->baseGraph;
  200. graph.connectHeroes(ai);
  201. visualKey = std::to_string(ai->playerID) + ":" + targetHero->getNameTranslated();
  202. pathNodes.clear();
  203. GraphNodeComparer cmp(pathNodes);
  204. GraphPathNode::TFibHeap pq(cmp);
  205. pathNodes[targetHero->visitablePos()][GrapthPathNodeType::NORMAL].cost = 0;
  206. pq.emplace(GraphPathNodePointer(targetHero->visitablePos(), GrapthPathNodeType::NORMAL));
  207. while(!pq.empty())
  208. {
  209. GraphPathNodePointer pos = pq.top();
  210. pq.pop();
  211. auto & node = getNode(pos);
  212. node.isInQueue = false;
  213. graph.iterateConnections(pos.coord, [&](int3 target, ObjectLink o)
  214. {
  215. auto targetNodeType = o.danger ? GrapthPathNodeType::BATTLE : pos.nodeType;
  216. auto targetPointer = GraphPathNodePointer(target, targetNodeType);
  217. auto & targetNode = getNode(targetPointer);
  218. if(targetNode.tryUpdate(pos, node, o))
  219. {
  220. if(graph.getNode(target).objTypeID == Obj::HERO)
  221. return;
  222. if(targetNode.isInQueue)
  223. {
  224. pq.increase(targetNode.handle);
  225. }
  226. else
  227. {
  228. targetNode.handle = pq.emplace(targetPointer);
  229. targetNode.isInQueue = true;
  230. }
  231. }
  232. });
  233. }
  234. }
  235. void GraphPaths::dumpToLog() const
  236. {
  237. logVisual->updateWithLock(visualKey, [&](IVisualLogBuilder & logBuilder)
  238. {
  239. for(auto & tile : pathNodes)
  240. {
  241. for(auto & node : tile.second)
  242. {
  243. if(!node.previous.valid())
  244. continue;
  245. if(NKAI_GRAPH_TRACE_LEVEL >= 2)
  246. {
  247. logAi->trace(
  248. "%s -> %s: %f !%d",
  249. node.previous.coord.toString(),
  250. tile.first.toString(),
  251. node.cost,
  252. node.danger);
  253. }
  254. logBuilder.addLine(node.previous.coord, tile.first);
  255. }
  256. }
  257. });
  258. }
  259. bool GraphPathNode::tryUpdate(const GraphPathNodePointer & pos, const GraphPathNode & prev, const ObjectLink & link)
  260. {
  261. auto newCost = prev.cost + link.cost;
  262. if(newCost < cost)
  263. {
  264. if(nodeType < pos.nodeType)
  265. {
  266. logAi->error("Linking error");
  267. }
  268. previous = pos;
  269. danger = prev.danger + link.danger;
  270. cost = newCost;
  271. return true;
  272. }
  273. return false;
  274. }
  275. void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHeroInstance * hero, const Nullkiller * ai) const
  276. {
  277. auto nodes = pathNodes.find(tile);
  278. if(nodes == pathNodes.end())
  279. return;
  280. for(auto & node : nodes->second)
  281. {
  282. if(!node.reachable())
  283. continue;
  284. std::vector<int3> tilesToPass;
  285. uint64_t danger = node.danger;
  286. float cost = node.cost;
  287. bool allowBattle = false;
  288. auto current = GraphPathNodePointer(nodes->first, node.nodeType);
  289. while(true)
  290. {
  291. auto currentTile = pathNodes.find(current.coord);
  292. if(currentTile == pathNodes.end())
  293. break;
  294. auto currentNode = currentTile->second[current.nodeType];
  295. if(!currentNode.previous.valid())
  296. break;
  297. allowBattle = allowBattle || currentNode.nodeType == GrapthPathNodeType::BATTLE;
  298. vstd::amax(danger, currentNode.danger);
  299. vstd::amax(cost, currentNode.cost);
  300. tilesToPass.push_back(current.coord);
  301. if(currentNode.cost < 2.0f)
  302. break;
  303. current = currentNode.previous;
  304. }
  305. if(tilesToPass.empty())
  306. continue;
  307. auto entryPaths = ai->pathfinder->getPathInfo(tilesToPass.back());
  308. for(auto & path : entryPaths)
  309. {
  310. if(path.targetHero != hero)
  311. continue;
  312. for(auto graphTile = tilesToPass.rbegin(); graphTile != tilesToPass.rend(); graphTile++)
  313. {
  314. AIPathNodeInfo n;
  315. n.coord = *graphTile;
  316. n.cost = cost;
  317. n.turns = static_cast<ui8>(cost) + 1; // just in case lets select worst scenario
  318. n.danger = danger;
  319. n.targetHero = hero;
  320. n.parentIndex = 0;
  321. for(auto & node : path.nodes)
  322. {
  323. node.parentIndex++;
  324. }
  325. path.nodes.insert(path.nodes.begin(), n);
  326. }
  327. path.armyLoss += ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), danger);
  328. path.targetObjectDanger = ai->pathfinder->getStorage()->evaluateDanger(tile, path.targetHero, !allowBattle);
  329. path.targetObjectArmyLoss = ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), path.targetObjectDanger);
  330. paths.push_back(path);
  331. }
  332. }
  333. }
  334. }