Browse Source

NKAI: fix sonar and refactoring

Andrii Danylchenko 1 year ago
parent
commit
bec2c0cac2

+ 3 - 7
AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp

@@ -157,12 +157,13 @@ void DangerHitMapAnalyzer::calculateTileOwners()
 	if(hitMap.shape()[0] != mapSize.x || hitMap.shape()[1] != mapSize.y || hitMap.shape()[2] != mapSize.z)
 		hitMap.resize(boost::extents[mapSize.x][mapSize.y][mapSize.z]);
 
-	std::map<const CGHeroInstance *, HeroRole> townHeroes;
+	std::vector<std::unique_ptr<CGHeroInstance>> temporaryHeroes;
 	std::map<const CGHeroInstance *, const CGTownInstance *> heroTownMap;
+	std::map<const CGHeroInstance *, HeroRole> townHeroes;
 
 	auto addTownHero = [&](const CGTownInstance * town)
 	{
-			auto townHero = new CGHeroInstance(town->cb);
+			auto townHero = temporaryHeroes.emplace_back(std::make_unique<CGHeroInstance>(town->cb)).get();
 			CRandomGenerator rng;
 			auto visitablePos = town->visitablePos();
 			
@@ -238,11 +239,6 @@ void DangerHitMapAnalyzer::calculateTileOwners()
 				hitMap[pos.x][pos.y][pos.z].closestTown = enemyTown;
 			}
 		});
-
-	for(auto h : townHeroes)
-	{
-		delete h.first;
-	}
 }
 
 const std::vector<HitMapInfo> & DangerHitMapAnalyzer::getTownThreats(const CGTownInstance * town) const

+ 103 - 103
AI/Nullkiller/Analyzers/ObjectClusterizer.cpp

@@ -189,15 +189,7 @@ bool ObjectClusterizer::shouldVisitObject(const CGObjectInstance * obj) const
 		return true; //all of the following is met
 }
 
-void ObjectClusterizer::clusterize()
-{
-	auto start = std::chrono::high_resolution_clock::now();
-
-	nearObjects.reset();
-	farObjects.reset();
-	blockedObjects.clear();
-
-	Obj ignoreObjects[] = {
+Obj ObjectClusterizer::IgnoredObjectTypes[] = {
 		Obj::BOAT,
 		Obj::EYE_OF_MAGI,
 		Obj::MONOLITH_ONE_WAY_ENTRANCE,
@@ -216,7 +208,15 @@ void ObjectClusterizer::clusterize()
 		Obj::REDWOOD_OBSERVATORY,
 		Obj::CARTOGRAPHER,
 		Obj::PILLAR_OF_FIRE
-	};
+};
+
+void ObjectClusterizer::clusterize()
+{
+	auto start = std::chrono::high_resolution_clock::now();
+
+	nearObjects.reset();
+	farObjects.reset();
+	blockedObjects.clear();
 
 	logAi->debug("Begin object clusterization");
 
@@ -225,104 +225,106 @@ void ObjectClusterizer::clusterize()
 		ai->memory->visitableObjs.end());
 
 #if NKAI_TRACE_LEVEL == 0
-	parallel_for(blocked_range<size_t>(0, objs.size()), [&](const blocked_range<size_t> & r)
+	parallel_for(blocked_range<size_t>(0, objs.size()), [&](const blocked_range<size_t> & r) {
 #else
 	blocked_range<size_t> r(0, objs.size());
 #endif
-	{
 		auto priorityEvaluator = ai->priorityEvaluators->acquire();
 
 		for(int i = r.begin(); i != r.end(); i++)
 		{
-			auto obj = objs[i];
-
-			if(!shouldVisitObject(obj))
-			{
-#if NKAI_TRACE_LEVEL >= 2
-				logAi->trace("Skip object %s%s.", obj->getObjectName(), obj->visitablePos().toString());
+			clusterizeObject(objs[i], priorityEvaluator.get());
+		}
+#if NKAI_TRACE_LEVEL == 0
+	});
 #endif
-				continue;
-			}
 
-#if NKAI_TRACE_LEVEL >= 2
-			logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString());
-#endif
+	logAi->trace("Near objects count: %i", nearObjects.objects.size());
+	logAi->trace("Far objects count: %i", farObjects.objects.size());
 
-			auto paths = ai->pathfinder->getPathInfo(obj->visitablePos(), true);
+	for(auto pair : blockedObjects)
+	{
+		logAi->trace("Cluster %s %s count: %i", pair.first->getObjectName(), pair.first->visitablePos().toString(), pair.second->objects.size());
 
-			if(paths.empty())
-			{
-#if NKAI_TRACE_LEVEL >= 2
-				logAi->trace("No paths found.");
+#if NKAI_TRACE_LEVEL >= 1
+		for(auto obj : pair.second->getObjects())
+		{
+			logAi->trace("Object %s %s", obj->getObjectName(), obj->visitablePos().toString());
+		}
 #endif
-				continue;
-			}
-
-			std::sort(paths.begin(), paths.end(), [](const AIPath & p1, const AIPath & p2) -> bool
-				{
-					return p1.movementCost() < p2.movementCost();
-				});
+	}
 
-			if(vstd::contains(ignoreObjects, obj->ID))
-			{
-				farObjects.addObject(obj, paths.front(), 0);
+	logAi->trace("Clusterization complete in %ld", timeElapsed(start));
+}
 
+void ObjectClusterizer::clusterizeObject(const CGObjectInstance * obj, PriorityEvaluator * priorityEvaluator)
+{
+	if(!shouldVisitObject(obj))
+	{
 #if NKAI_TRACE_LEVEL >= 2
-				logAi->trace("Object ignored. Moved to far objects with path %s", paths.front().toString());
+		logAi->trace("Skip object %s%s.", obj->getObjectName(), obj->visitablePos().toString());
 #endif
+		return;
+	}
 
-				continue;
-			}
-
-			std::set<const CGHeroInstance *> heroesProcessed;
-
-			for(auto & path : paths)
-			{
 #if NKAI_TRACE_LEVEL >= 2
-				logAi->trace("Checking path %s", path.toString());
+	logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString());
 #endif
 
-				if(!shouldVisit(ai, path.targetHero, obj))
-				{
+	auto paths = ai->pathfinder->getPathInfo(obj->visitablePos(), true);
+
+	if(paths.empty())
+	{
 #if NKAI_TRACE_LEVEL >= 2
-					logAi->trace("Hero %s does not need to visit %s", path.targetHero->getObjectName(), obj->getObjectName());
+		logAi->trace("No paths found.");
 #endif
-					continue;
-				}
+		return;
+	}
 
-				if(path.nodes.size() > 1)
-				{
-					auto blocker = getBlocker(path);
+	std::sort(paths.begin(), paths.end(), [](const AIPath & p1, const AIPath & p2) -> bool
+		{
+			return p1.movementCost() < p2.movementCost();
+		});
+
+	if(vstd::contains(IgnoredObjectTypes, obj->ID))
+	{
+		farObjects.addObject(obj, paths.front(), 0);
 
-					if(blocker)
-					{
-						if(vstd::contains(heroesProcessed, path.targetHero))
-						{
 #if NKAI_TRACE_LEVEL >= 2
-							logAi->trace("Hero %s is already processed.", path.targetHero->getObjectName());
+		logAi->trace("Object ignored. Moved to far objects with path %s", paths.front().toString());
 #endif
-							continue;
-						}
 
-						heroesProcessed.insert(path.targetHero);
+		return;
+	}
 
-						float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)));
+	std::set<const CGHeroInstance *> heroesProcessed;
 
-						if(priority < MIN_PRIORITY)
-							continue;
+	for(auto & path : paths)
+	{
+#if NKAI_TRACE_LEVEL >= 2
+		logAi->trace("Checking path %s", path.toString());
+#endif
 
-						ClusterMap::accessor cluster;
-						blockedObjects.insert(
-							cluster,
-							ClusterMap::value_type(blocker, std::make_shared<ObjectCluster>(blocker)));
+		if(!shouldVisit(ai, path.targetHero, obj))
+		{
+#if NKAI_TRACE_LEVEL >= 2
+			logAi->trace("Hero %s does not need to visit %s", path.targetHero->getObjectName(), obj->getObjectName());
+#endif
+			continue;
+		}
 
-						cluster->second->addObject(obj, path, priority);
+		if(path.nodes.size() > 1)
+		{
+			auto blocker = getBlocker(path);
 
+			if(blocker)
+			{
+				if(vstd::contains(heroesProcessed, path.targetHero))
+				{
 #if NKAI_TRACE_LEVEL >= 2
-						logAi->trace("Path added to cluster %s%s", blocker->getObjectName(), blocker->visitablePos().toString());
+					logAi->trace("Hero %s is already processed.", path.targetHero->getObjectName());
 #endif
-						continue;
-					}
+					continue;
 				}
 
 				heroesProcessed.insert(path.targetHero);
@@ -332,48 +334,46 @@ void ObjectClusterizer::clusterize()
 				if(priority < MIN_PRIORITY)
 					continue;
 
-				bool interestingObject = path.turn() <= 2 || priority > 0.5f;
+				ClusterMap::accessor cluster;
+				blockedObjects.insert(
+					cluster,
+					ClusterMap::value_type(blocker, std::make_shared<ObjectCluster>(blocker)));
 
-				if(interestingObject)
-				{
-					nearObjects.addObject(obj, path, priority);
-				}
-				else
-				{
-					farObjects.addObject(obj, path, priority);
-				}
+				cluster->second->addObject(obj, path, priority);
 
 #if NKAI_TRACE_LEVEL >= 2
-				logAi->trace("Path %s added to %s objects. Turn: %d, priority: %f",
-					path.toString(),
-					interestingObject ? "near" : "far",
-					path.turn(),
-					priority);
+				logAi->trace("Path added to cluster %s%s", blocker->getObjectName(), blocker->visitablePos().toString());
 #endif
+				continue;
 			}
 		}
-#if NKAI_TRACE_LEVEL == 0
-	});
-#else
-	}
-#endif
 
-	logAi->trace("Near objects count: %i", nearObjects.objects.size());
-	logAi->trace("Far objects count: %i", farObjects.objects.size());
+		heroesProcessed.insert(path.targetHero);
 
-	for(auto pair : blockedObjects)
-	{
-		logAi->trace("Cluster %s %s count: %i", pair.first->getObjectName(), pair.first->visitablePos().toString(), pair.second->objects.size());
+		float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)));
 
-#if NKAI_TRACE_LEVEL >= 1
-		for(auto obj : pair.second->getObjects())
+		if(priority < MIN_PRIORITY)
+			continue;
+
+		bool interestingObject = path.turn() <= 2 || priority > 0.5f;
+
+		if(interestingObject)
 		{
-			logAi->trace("Object %s %s", obj->getObjectName(), obj->visitablePos().toString());
+			nearObjects.addObject(obj, path, priority);
 		}
+		else
+		{
+			farObjects.addObject(obj, path, priority);
+		}
+
+#if NKAI_TRACE_LEVEL >= 2
+		logAi->trace("Path %s added to %s objects. Turn: %d, priority: %f",
+			path.toString(),
+			interestingObject ? "near" : "far",
+			path.turn(),
+			priority);
 #endif
 	}
-
-	logAi->trace("Clusterization complete in %ld", timeElapsed(start));
 }
 
 }

+ 5 - 0
AI/Nullkiller/Analyzers/ObjectClusterizer.h

@@ -49,9 +49,13 @@ public:
 
 using ClusterMap = tbb::concurrent_hash_map<const CGObjectInstance *, std::shared_ptr<ObjectCluster>>;
 
+class PriorityEvaluator;
+
 class ObjectClusterizer
 {
 private:
+	static Obj IgnoredObjectTypes[];
+
 	ObjectCluster nearObjects;
 	ObjectCluster farObjects;
 	ClusterMap blockedObjects;
@@ -68,6 +72,7 @@ public:
 
 private:
 	bool shouldVisitObject(const CGObjectInstance * obj) const;
+	void clusterizeObject(const CGObjectInstance * obj, PriorityEvaluator * priorityEvaluator);
 };
 
 }

+ 2 - 2
AI/Nullkiller/Pathfinding/AINodeStorage.h

@@ -11,8 +11,8 @@
 #pragma once
 
 #define NKAI_PATHFINDER_TRACE_LEVEL 0
-#define NKAI_GRAPH_TRACE_LEVEL 0
-#define NKAI_TRACE_LEVEL 1
+constexpr int NKAI_GRAPH_TRACE_LEVEL = 0;
+#define NKAI_TRACE_LEVEL 0
 
 #include "../../../lib/pathfinder/CGPathNode.h"
 #include "../../../lib/pathfinder/INodeStorage.h"

+ 10 - 9
AI/Nullkiller/Pathfinding/AIPathfinder.cpp

@@ -58,7 +58,7 @@ std::vector<AIPath> AIPathfinder::getPathInfo(const int3 & tile, bool includeGra
 	return info;
 }
 
-void AIPathfinder::updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes, PathfinderSettings pathfinderSettings)
+void AIPathfinder::updatePaths(const std::map<const CGHeroInstance *, HeroRole> & heroes, PathfinderSettings pathfinderSettings)
 {
 	if(!storage)
 	{
@@ -125,7 +125,7 @@ void AIPathfinder::updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes
 	logAi->trace("Recalculated paths in %ld", timeElapsed(start));
 }
 
-void AIPathfinder::updateGraphs(std::map<const CGHeroInstance *, HeroRole> heroes)
+void AIPathfinder::updateGraphs(const std::map<const CGHeroInstance *, HeroRole> & heroes)
 {
 	auto start = std::chrono::high_resolution_clock::now();
 	std::vector<const CGHeroInstance *> heroesVector;
@@ -134,22 +134,23 @@ void AIPathfinder::updateGraphs(std::map<const CGHeroInstance *, HeroRole> heroe
 
 	for(auto hero : heroes)
 	{
-		heroGraphs.emplace(hero.first->id, GraphPaths());
-		heroesVector.push_back(hero.first);
+		if(heroGraphs.try_emplace(hero.first->id, GraphPaths()).second)
+			heroesVector.push_back(hero.first);
 	}
 
-	parallel_for(blocked_range<size_t>(0, heroesVector.size()), [&](const blocked_range<size_t> & r)
+	parallel_for(blocked_range<size_t>(0, heroesVector.size()), [this, &heroesVector](const blocked_range<size_t> & r)
 		{
 			for(auto i = r.begin(); i != r.end(); i++)
 				heroGraphs.at(heroesVector[i]->id).calculatePaths(heroesVector[i], ai);
 		});
 
-#if NKAI_GRAPH_TRACE_LEVEL >= 1
-	for(auto hero : heroes)
+	if(NKAI_GRAPH_TRACE_LEVEL >= 1)
 	{
-		heroGraphs[hero.first->id].dumpToLog();
+		for(auto hero : heroes)
+		{
+			heroGraphs[hero.first->id].dumpToLog();
+		}
 	}
-#endif
 
 	logAi->trace("Graph paths updated in %lld", timeElapsed(start));
 }

+ 2 - 2
AI/Nullkiller/Pathfinding/AIPathfinder.h

@@ -46,8 +46,8 @@ public:
 	AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * ai);
 	std::vector<AIPath> getPathInfo(const int3 & tile, bool includeGraph = false) const;
 	bool isTileAccessible(const HeroPtr & hero, const int3 & tile) const;
-	void updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes, PathfinderSettings pathfinderSettings);
-	void updateGraphs(std::map<const CGHeroInstance *, HeroRole> heroes);
+	void updatePaths(const std::map<const CGHeroInstance *, HeroRole> & heroes, PathfinderSettings pathfinderSettings);
+	void updateGraphs(const std::map<const CGHeroInstance *, HeroRole> & heroes);
 	void init();
 
 	std::shared_ptr<AINodeStorage>getStorage()

+ 128 - 105
AI/Nullkiller/Pathfinding/ObjectGraph.cpp

@@ -19,126 +19,146 @@
 namespace NKAI
 {
 
-void ObjectGraph::updateGraph(const Nullkiller * ai)
+class ObjectGraphCalculator
 {
-	auto cb = ai->cb;
+private:
+	ObjectGraph * target;
+	const Nullkiller * ai;
 
 	std::map<const CGHeroInstance *, HeroRole> actors;
 	std::map<const CGHeroInstance *, const CGObjectInstance *> actorObjectMap;
-	std::vector<CGBoat *> boats;
 
-	auto addObjectActor = [&](const CGObjectInstance * obj)
-	{
-		auto objectActor = new CGHeroInstance(obj->cb);
-		CRandomGenerator rng;
-		auto visitablePos = obj->visitablePos();
+	std::vector<std::unique_ptr<CGBoat>> temporaryBoats;
+	std::vector<std::unique_ptr<CGHeroInstance>> temporaryActorHeroes;
 
-		objectActor->setOwner(ai->playerID); // lets avoid having multiple colors
-		objectActor->initHero(rng, static_cast<HeroTypeID>(0));
-		objectActor->pos = objectActor->convertFromVisitablePos(visitablePos);
-		objectActor->initObj(rng);
-
-		if(cb->getTile(visitablePos)->isWater())
+public:
+	ObjectGraphCalculator(ObjectGraph * target, const Nullkiller * ai)
+		:ai(ai), target(target)
+	{
+		for(auto obj : ai->memory->visitableObjs)
 		{
-			boats.push_back(new CGBoat(objectActor->cb));
-			objectActor->boat = boats.back();
+			if(obj && obj->isVisitable() && obj->ID != Obj::HERO)
+			{
+				addObjectActor(obj);
+			}
 		}
 
-		actorObjectMap[objectActor] = obj;
-		actors[objectActor] = obj->ID == Obj::TOWN || obj->ID == Obj::SHIPYARD ?  HeroRole::MAIN : HeroRole::SCOUT;
-		addObject(obj);
-	};
-
-	for(auto obj : ai->memory->visitableObjs)
-	{
-		if(obj && obj->isVisitable() && obj->ID != Obj::HERO)
+		for(auto town : ai->cb->getTownsInfo())
 		{
-			addObjectActor(obj);
+			addObjectActor(town);
 		}
+
+		PathfinderSettings ps;
+
+		ps.mainTurnDistanceLimit = 5;
+		ps.scoutTurnDistanceLimit = 1;
+		ps.allowBypassObjects = false;
+
+		ai->pathfinder->updatePaths(actors, ps);
 	}
 
-	for(auto town : cb->getTownsInfo())
+	void calculateConnections(const int3 & pos)
 	{
-		addObjectActor(town);
-	}
+		auto guarded = ai->cb->getGuardingCreaturePosition(pos).valid();
 
-	PathfinderSettings ps;
-	
-	ps.mainTurnDistanceLimit = 5;
-	ps.scoutTurnDistanceLimit = 1;
-	ps.allowBypassObjects = false;
+		if(guarded)
+			return;
 
-	ai->pathfinder->updatePaths(actors, ps);
+		auto paths = ai->pathfinder->getPathInfo(pos);
 
-	foreach_tile_pos(cb.get(), [&](const CPlayerSpecificInfoCallback * cb, const int3 & pos)
+		for(AIPath & path1 : paths)
 		{
-			if(nodes.find(pos) != nodes.end())
-				return;
+			for(AIPath & path2 : paths)
+			{
+				if(path1.targetHero == path2.targetHero)
+					continue;
 
-			auto guarded = ai->cb->getGuardingCreaturePosition(pos).valid();
+				auto obj1 = actorObjectMap[path1.targetHero];
+				auto obj2 = actorObjectMap[path2.targetHero];
 
-			if(guarded)
-				return;
+				auto tile1 = cb->getTile(obj1->visitablePos());
+				auto tile2 = cb->getTile(obj2->visitablePos());
 
-			auto paths = ai->pathfinder->getPathInfo(pos);
-
-			for(AIPath & path1 : paths)
-			{
-				for(AIPath & path2 : paths)
+				if(tile2->isWater() && !tile1->isWater())
 				{
-					if(path1.targetHero == path2.targetHero)
+					auto linkTile = cb->getTile(pos);
+
+					if(!linkTile->isWater() || obj1->ID != Obj::BOAT || obj1->ID != Obj::SHIPYARD)
 						continue;
+				}
 
-					auto obj1 = actorObjectMap[path1.targetHero];
-					auto obj2 = actorObjectMap[path2.targetHero];
+				auto danger = ai->pathfinder->getStorage()->evaluateDanger(obj2->visitablePos(), path1.targetHero, true);
 
-					auto tile1 = cb->getTile(obj1->visitablePos());
-					auto tile2 = cb->getTile(obj2->visitablePos());
+				auto updated = target->tryAddConnection(
+					obj1->visitablePos(),
+					obj2->visitablePos(), 
+					path1.movementCost() + path2.movementCost(),
+					danger);
 
-					if(tile2->isWater() && !tile1->isWater())
-					{
-						auto linkTile = cb->getTile(pos);
+				if(NKAI_GRAPH_TRACE_LEVEL >= 2 && updated)
+				{
+					logAi->trace(
+						"Connected %s[%s] -> %s[%s] through [%s], cost %2f",
+						obj1->getObjectName(), obj1->visitablePos().toString(),
+						obj2->getObjectName(), obj2->visitablePos().toString(),
+						pos.toString(),
+						path1.movementCost() + path2.movementCost());
+				}
+			}
+		}
+	}
 
-						if(!linkTile->isWater() || obj1->ID != Obj::BOAT || obj1->ID != Obj::SHIPYARD)
-							continue;
-					}
+private:
+	void addObjectActor(const CGObjectInstance * obj)
+	{
+		auto objectActor = temporaryActorHeroes.emplace_back(std::make_unique<CGHeroInstance>(obj->cb)).get();
 
-					auto danger = ai->pathfinder->getStorage()->evaluateDanger(obj2->visitablePos(), path1.targetHero, true);
+		CRandomGenerator rng;
+		auto visitablePos = obj->visitablePos();
 
-					auto updated = nodes[obj1->visitablePos()].connections[obj2->visitablePos()].update(
-						path1.movementCost() + path2.movementCost(),
-						danger);
+		objectActor->setOwner(ai->playerID); // lets avoid having multiple colors
+		objectActor->initHero(rng, static_cast<HeroTypeID>(0));
+		objectActor->pos = objectActor->convertFromVisitablePos(visitablePos);
+		objectActor->initObj(rng);
 
-#if NKAI_GRAPH_TRACE_LEVEL >= 2
-					if(updated)
-					{
-						logAi->trace(
-							"Connected %s[%s] -> %s[%s] through [%s], cost %2f",
-							obj1->getObjectName(), obj1->visitablePos().toString(),
-							obj2->getObjectName(), obj2->visitablePos().toString(),
-							pos.toString(),
-							path1.movementCost() + path2.movementCost());
-					}
-#else
-					(void)updated;
-#endif
-				}
-			}
-		});
+		if(cb->getTile(visitablePos)->isWater())
+		{
+			objectActor->boat = temporaryBoats.emplace_back(std::make_unique<CGBoat>(objectActor->cb)).get();
+		}
 
-	for(auto h : actorObjectMap)
-	{
-		delete h.first;
-	}
+		actorObjectMap[objectActor] = obj;
+		actors[objectActor] = obj->ID == Obj::TOWN || obj->ID == Obj::SHIPYARD ? HeroRole::MAIN : HeroRole::SCOUT;
 
-	for(auto boat : boats)
-	{
-		delete boat;
-	}
+		target->addObject(obj);
+	};
+};
+
+bool ObjectGraph::tryAddConnection(
+	const int3 & from,
+	const int3 & to,
+	float cost,
+	uint64_t danger)
+{
+	return nodes[from].connections[to].update(cost, danger);
+}
+
+void ObjectGraph::updateGraph(const Nullkiller * ai)
+{
+	auto cb = ai->cb;
+
+	ObjectGraphCalculator calculator(this, ai);
+
+	foreach_tile_pos(cb.get(), [this, &calculator](const CPlayerSpecificInfoCallback * cb, const int3 & pos)
+		{
+			if(nodes.find(pos) != nodes.end())
+				return;
+
+			calculator.calculateConnections(pos);
+		});
+
+	if(NKAI_GRAPH_TRACE_LEVEL >= 1)
+		dumpToLog("graph");
 
-#if NKAI_GRAPH_TRACE_LEVEL >= 1
-	dumpToLog("graph");
-#endif
 }
 
 void ObjectGraph::addObject(const CGObjectInstance * obj)
@@ -199,14 +219,15 @@ void ObjectGraph::dumpToLog(std::string visualKey) const
 			{
 				for(auto & node : tile.second.connections)
 				{
-#if NKAI_GRAPH_TRACE_LEVEL >= 2
-					logAi->trace(
-						"%s -> %s: %f !%d",
-						node.first.toString(),
-						tile.first.toString(),
-						node.second.cost,
-						node.second.danger);
-#endif
+					if(NKAI_GRAPH_TRACE_LEVEL >= 2)
+					{
+						logAi->trace(
+							"%s -> %s: %f !%d",
+							node.first.toString(),
+							tile.first.toString(),
+							node.second.cost,
+							node.second.danger);
+					}
 
 					logBuilder.addLine(tile.first, node.first);
 				}
@@ -278,14 +299,15 @@ void GraphPaths::dumpToLog() const
 					if(!node.previous.valid())
 						continue;
 
-#if NKAI_GRAPH_TRACE_LEVEL >= 2
-					logAi->trace(
-						"%s -> %s: %f !%d",
-						node.previous.coord.toString(),
-						tile.first.toString(),
-						node.cost,
-						node.danger);
-#endif
+					if(NKAI_GRAPH_TRACE_LEVEL >= 2)
+					{
+						logAi->trace(
+							"%s -> %s: %f !%d",
+							node.previous.coord.toString(),
+							tile.first.toString(),
+							node.cost,
+							node.danger);
+					}
 
 					logBuilder.addLine(node.previous.coord, tile.first);
 				}
@@ -343,7 +365,7 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
 
 			auto currentNode = currentTile->second[current.nodeType];
 
-			if(currentNode.cost == 0)
+			if(!currentNode.previous.valid())
 				break;
 
 			allowBattle = allowBattle || currentNode.nodeType == GrapthPathNodeType::BATTLE;
@@ -352,7 +374,7 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
 
 			tilesToPass.push_back(current.coord);
 
-			if(currentNode.cost < 2)
+			if(currentNode.cost < 2.0f)
 				break;
 
 			current = currentNode.previous;
@@ -377,6 +399,7 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
 				n.turns = static_cast<ui8>(cost) + 1; // just in case lets select worst scenario
 				n.danger = danger;
 				n.targetHero = hero;
+				n.parentIndex = 0;
 
 				for(auto & node : path.nodes)
 				{

+ 2 - 0
AI/Nullkiller/Pathfinding/ObjectGraph.h

@@ -76,6 +76,8 @@ public:
 	{
 		return nodes.at(tile);
 	}
+
+	bool tryAddConnection(const int3 & from, const int3 & to, float cost, uint64_t danger);
 };
 
 struct GraphPathNode;

+ 1 - 1
AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp

@@ -45,7 +45,7 @@ namespace AIPathfinding
 
 			if(!allowBypassObjects)
 			{
-				if (source.node->getCost() == 0)
+				if (source.node->getCost() < 0.0001f)
 					return;
 
 				// when actor represents moster graph node, we need to let him escape monster