ObjectGraph.cpp 8.3 KB

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