Преглед на файлове

NKAI: graph add battle layer

Andrii Danylchenko преди 1 година
родител
ревизия
b236384356

+ 11 - 3
AI/Nullkiller/Analyzers/ObjectClusterizer.cpp

@@ -224,7 +224,11 @@ void ObjectClusterizer::clusterize()
 		ai->memory->visitableObjs.begin(),
 		ai->memory->visitableObjs.end());
 
+#if NKAI_TRACE_LEVEL == 0
 	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();
 
@@ -255,9 +259,9 @@ void ObjectClusterizer::clusterize()
 			}
 
 			std::sort(paths.begin(), paths.end(), [](const AIPath & p1, const AIPath & p2) -> bool
-			{
-				return p1.movementCost() < p2.movementCost();
-			});
+				{
+					return p1.movementCost() < p2.movementCost();
+				});
 
 			if(vstd::contains(ignoreObjects, obj->ID))
 			{
@@ -348,7 +352,11 @@ void ObjectClusterizer::clusterize()
 #endif
 			}
 		}
+#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());

+ 2 - 0
AI/Nullkiller/Engine/Nullkiller.cpp

@@ -42,6 +42,8 @@ void Nullkiller::init(std::shared_ptr<CCallback> cb, PlayerColor playerID)
 	this->cb = cb;
 	this->playerID = playerID;
 
+	baseGraph.reset();
+
 	priorityEvaluator.reset(new PriorityEvaluator(this));
 	priorityEvaluators.reset(
 		new SharedPool<PriorityEvaluator>(

+ 8 - 2
AI/Nullkiller/Goals/AdventureSpellCast.cpp

@@ -39,12 +39,18 @@ void AdventureSpellCast::accept(AIGateway * ai)
 	if(hero->mana < hero->getSpellCost(spell))
 		throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getNameTranslated());
 
-	if(spellID == SpellID::TOWN_PORTAL && town && town->visitingHero)
-		throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->getNameTranslated());
 
 	if(town && spellID == SpellID::TOWN_PORTAL)
 	{
 		ai->selectedObject = town->id;
+
+		if(town->visitingHero && town->tempOwner == ai->playerID && !town->getUpperArmy()->stacksCount())
+		{
+			ai->myCb->swapGarrisonHero(town);
+		}
+
+		if(town->visitingHero)
+			throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->getNameTranslated());
 	}
 
 	auto wait = cb->waitTillRealize;

+ 4 - 4
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -1123,14 +1123,14 @@ void AINodeStorage::calculateTownPortal(
 	{
 		for(const CGTownInstance * targetTown : towns)
 		{
-			// TODO: allow to hide visiting hero in garrison
-			if(targetTown->visitingHero && maskMap.find(targetTown->visitingHero.get()) != maskMap.end())
+			if(targetTown->visitingHero
+				&& targetTown->getUpperArmy()->stacksCount()
+				&& maskMap.find(targetTown->visitingHero.get()) != maskMap.end())
 			{
 				auto basicMask = maskMap.at(targetTown->visitingHero.get());
-				bool heroIsInChain = (actor->chainMask & basicMask) != 0;
 				bool sameActorInTown = actor->chainMask == basicMask;
 
-				if(sameActorInTown || !heroIsInChain)
+				if(!sameActorInTown)
 					continue;
 			}
 

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

@@ -11,7 +11,8 @@
 #pragma once
 
 #define NKAI_PATHFINDER_TRACE_LEVEL 0
-#define NKAI_TRACE_LEVEL 2
+#define NKAI_GRAPH_TRACE_LEVEL 0
+#define NKAI_TRACE_LEVEL 0
 
 #include "../../../lib/pathfinder/CGPathNode.h"
 #include "../../../lib/pathfinder/INodeStorage.h"

+ 5 - 4
AI/Nullkiller/Pathfinding/AIPathfinder.cpp

@@ -48,9 +48,10 @@ std::vector<AIPath> AIPathfinder::getPathInfo(const int3 & tile, bool includeGra
 	{
 		for(auto hero : cb->getHeroesInfo())
 		{
-			auto & graph = heroGraphs.at(hero->id);
+			auto graph = heroGraphs.find(hero->id);
 
-			graph.addChainInfo(info, tile, hero, ai);
+			if(graph != heroGraphs.end())
+				graph->second.addChainInfo(info, tile, hero, ai);
 		}
 	}
 
@@ -140,10 +141,10 @@ void AIPathfinder::updateGraphs(std::map<const CGHeroInstance *, HeroRole> heroe
 	parallel_for(blocked_range<size_t>(0, heroesVector.size()), [&](const blocked_range<size_t> & r)
 		{
 			for(auto i = r.begin(); i != r.end(); i++)
-				heroGraphs[heroesVector[i]->id].calculatePaths(heroesVector[i], ai);
+				heroGraphs.at(heroesVector[i]->id).calculatePaths(heroesVector[i], ai);
 		});
 
-#if NKAI_TRACE_LEVEL >= 2
+#if NKAI_GRAPH_TRACE_LEVEL >= 1
 	for(auto hero : heroes)
 	{
 		heroGraphs[hero.first->id].dumpToLog();

+ 1 - 1
AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp

@@ -30,7 +30,7 @@ namespace AIPathfinding
 			std::vector<std::shared_ptr<IPathfindingRule>> rules = {
 				std::make_shared<AILayerTransitionRule>(cb, ai, nodeStorage),
 				std::make_shared<DestinationActionRule>(),
-				std::make_shared<AIMovementToDestinationRule>(nodeStorage),
+				std::make_shared<AIMovementToDestinationRule>(nodeStorage, allowBypassObjects),
 				std::make_shared<MovementCostRule>(),
 				std::make_shared<AIPreviousNodeRule>(nodeStorage),
 				std::make_shared<AIMovementAfterDestinationRule>(cb, nodeStorage, allowBypassObjects)

+ 92 - 18
AI/Nullkiller/Pathfinding/ObjectGraph.cpp

@@ -13,6 +13,7 @@
 #include "../../../CCallback.h"
 #include "../../../lib/mapping/CMap.h"
 #include "../Engine/Nullkiller.h"
+#include "../../../lib/logging/VisualLogger.h"
 
 namespace NKAI
 {
@@ -64,6 +65,14 @@ void ObjectGraph::updateGraph(const Nullkiller * ai)
 
 	foreach_tile_pos(cb.get(), [&](const CPlayerSpecificInfoCallback * cb, const int3 & pos)
 		{
+			if(nodes.find(pos) != nodes.end())
+				return;
+
+			auto guarded = ai->cb->getGuardingCreaturePosition(pos).valid();
+
+			if(guarded)
+				return;
+
 			auto paths = ai->pathfinder->getPathInfo(pos);
 
 			for(AIPath & path1 : paths)
@@ -76,9 +85,25 @@ void ObjectGraph::updateGraph(const Nullkiller * ai)
 					auto obj1 = actorObjectMap[path1.targetHero];
 					auto obj2 = actorObjectMap[path2.targetHero];
 
-					nodes[obj1->visitablePos()].connections[obj2->visitablePos()].update(
+					auto danger = ai->pathfinder->getStorage()->evaluateDanger(obj2->visitablePos(), path1.targetHero, true);
+
+					auto updated = nodes[obj1->visitablePos()].connections[obj2->visitablePos()].update(
 						path1.movementCost() + path2.movementCost(),
-						path1.getPathDanger() + path2.getPathDanger());
+						danger);
+
+#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
 				}
 			}
 		});
@@ -87,6 +112,10 @@ void ObjectGraph::updateGraph(const Nullkiller * ai)
 	{
 		delete h.first;
 	}
+
+#if NKAI_GRAPH_TRACE_LEVEL >= 1
+	dumpToLog("graph");
+#endif
 }
 
 void ObjectGraph::addObject(const CGObjectInstance * obj)
@@ -104,7 +133,7 @@ void ObjectGraph::connectHeroes(const Nullkiller * ai)
 		}
 	}
 
-	for(auto node : nodes)
+	for(auto & node : nodes)
 	{
 		auto pos = node.first;
 		auto paths = ai->pathfinder->getPathInfo(pos);
@@ -124,6 +153,29 @@ void ObjectGraph::connectHeroes(const Nullkiller * ai)
 	}
 }
 
+void ObjectGraph::dumpToLog(std::string visualKey) const
+{
+	logVisual->updateWithLock(visualKey, [&](IVisualLogBuilder & logBuilder)
+		{
+			for(auto & tile : nodes)
+			{
+				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
+
+					logBuilder.addLine(node.first, tile.first);
+				}
+			}
+		});
+}
+
 bool GraphNodeComparer::operator()(const GraphPathNodePointer & lhs, const GraphPathNodePointer & rhs) const
 {
 	return pathNodes.at(lhs.coord)[lhs.nodeType].cost > pathNodes.at(rhs.coord)[rhs.nodeType].cost;
@@ -134,6 +186,7 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil
 	graph = *ai->baseGraph;
 	graph.connectHeroes(ai);
 
+	visualKey = std::to_string(ai->playerID) + ":" + targetHero->getNameTranslated();
 	pathNodes.clear();
 
 	GraphNodeComparer cmp(pathNodes);
@@ -153,11 +206,17 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil
 
 		graph.iterateConnections(pos.coord, [&](int3 target, ObjectLink o)
 			{
-				auto targetPointer = GraphPathNodePointer(target, pos.nodeType);
+				auto graphNode = graph.getNode(target);
+				auto targetNodeType = o.danger ? GrapthPathNodeType::BATTLE : pos.nodeType;
+
+				auto targetPointer = GraphPathNodePointer(target, targetNodeType);
 				auto & targetNode = getNode(targetPointer);
 
 				if(targetNode.tryUpdate(pos, node, o))
 				{
+					if(graph.getNode(target).objTypeID == Obj::HERO)
+						return;
+
 					if(targetNode.isInQueue)
 					{
 						pq.increase(targetNode.handle);
@@ -174,21 +233,28 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil
 
 void GraphPaths::dumpToLog() const
 {
-	for(auto & tile : pathNodes)
-	{
-		for(auto & node : tile.second)
+	logVisual->updateWithLock(visualKey, [&](IVisualLogBuilder & logBuilder)
 		{
-			if(!node.previous.valid())
-				continue;
+			for(auto & tile : pathNodes)
+			{
+				for(auto & node : tile.second)
+				{
+					if(!node.previous.valid())
+						continue;
 
-			logAi->trace(
-				"%s -> %s: %f !%d",
-				node.previous.coord.toString(),
-				tile.first.toString(),
-				node.cost,
-				node.danger);
-		}
-	}
+#if NKAI_GRAPH_TRACE_LEVEL >= 2
+					logAi->trace(
+						"%s -> %s: %f !%d",
+						node.previous.coord.toString(),
+						tile.first.toString(),
+						node.cost,
+						node.danger);
+#endif
+
+					logBuilder.addLine(node.previous.coord, tile.first);
+				}
+			}
+		});
 }
 
 bool GraphPathNode::tryUpdate(const GraphPathNodePointer & pos, const GraphPathNode & prev, const ObjectLink & link)
@@ -197,6 +263,11 @@ bool GraphPathNode::tryUpdate(const GraphPathNodePointer & pos, const GraphPathN
 
 	if(newCost < cost)
 	{
+		if(nodeType < pos.nodeType)
+		{
+			logAi->error("Linking error");
+		}
+
 		previous = pos;
 		danger = prev.danger + link.danger;
 		cost = newCost;
@@ -214,7 +285,7 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
 	if(nodes == pathNodes.end())
 		return;
 
-	for(auto & node : (*nodes).second)
+	for(auto & node : nodes->second)
 	{
 		if(!node.reachable())
 			continue;
@@ -236,6 +307,9 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
 
 			auto currentNode = currentTile->second[current.nodeType];
 
+			if(currentNode.cost == 0)
+				break;
+
 			allowBattle = allowBattle || currentNode.nodeType == GrapthPathNodeType::BATTLE;
 			vstd::amax(danger, currentNode.danger);
 			vstd::amax(cost, currentNode.cost);

+ 20 - 3
AI/Nullkiller/Pathfinding/ObjectGraph.h

@@ -23,19 +23,24 @@ struct ObjectLink
 	float cost = 100000; // some big number
 	uint64_t danger = 0;
 
-	void update(float newCost, uint64_t newDanger)
+	bool update(float newCost, uint64_t newDanger)
 	{
 		if(cost > newCost)
 		{
 			cost = newCost;
 			danger = newDanger;
+
+			return true;
 		}
+
+		return false;
 	}
 };
 
 struct ObjectNode
 {
 	ObjectInstanceID objID;
+	MapObjectID objTypeID;
 	bool objectExists;
 	std::unordered_map<int3, ObjectLink> connections;
 
@@ -43,6 +48,7 @@ struct ObjectNode
 	{
 		objectExists = true;
 		objID = obj->id;
+		objTypeID = obj->ID;
 	}
 };
 
@@ -54,15 +60,21 @@ public:
 	void updateGraph(const Nullkiller * ai);
 	void addObject(const CGObjectInstance * obj);
 	void connectHeroes(const Nullkiller * ai);
+	void dumpToLog(std::string visualKey) const;
 
 	template<typename Func>
 	void iterateConnections(const int3 & pos, Func fn)
 	{
-		for(auto connection : nodes.at(pos).connections)
+		for(auto & connection : nodes.at(pos).connections)
 		{
 			fn(connection.first, connection.second);
 		}
 	}
+
+	const ObjectNode & getNode(int3 tile) const
+	{
+		return nodes.at(tile);
+	}
 };
 
 struct GraphPathNode;
@@ -134,6 +146,7 @@ class GraphPaths
 {
 	ObjectGraph graph;
 	GraphNodeStorage pathNodes;
+	std::string visualKey;
 
 public:
 	void calculatePaths(const CGHeroInstance * targetHero, const Nullkiller * ai);
@@ -143,7 +156,11 @@ public:
 private:
 	GraphPathNode & getNode(const GraphPathNodePointer & pos)
 	{
-		return pathNodes[pos.coord][pos.nodeType];
+		auto & node = pathNodes[pos.coord][pos.nodeType];
+
+		node.nodeType = pos.nodeType;
+
+		return node;
 	}
 };
 

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

@@ -48,7 +48,7 @@ namespace AIPathfinding
 
 			return;
 		}
-
+		
 		if(!allowBypassObjects)
 			return;
 

+ 25 - 8
AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp

@@ -14,8 +14,10 @@ namespace NKAI
 {
 namespace AIPathfinding
 {
-	AIMovementToDestinationRule::AIMovementToDestinationRule(std::shared_ptr<AINodeStorage> nodeStorage)
-		: nodeStorage(nodeStorage)
+	AIMovementToDestinationRule::AIMovementToDestinationRule(
+		std::shared_ptr<AINodeStorage> nodeStorage,
+		bool allowBypassObjects)
+		: nodeStorage(nodeStorage), allowBypassObjects(allowBypassObjects)
 	{
 	}
 
@@ -37,15 +39,30 @@ namespace AIPathfinding
 			return;
 		}
 
-		if(blocker == BlockingReason::SOURCE_GUARDED && nodeStorage->getAINode(source.node)->actor->allowBattle)
+		if(blocker == BlockingReason::SOURCE_GUARDED)
 		{
+			auto actor = nodeStorage->getAINode(source.node)->actor;
+
+			if(!allowBypassObjects)
+			{
+				if (source.node->getCost() == 0)
+					return;
+
+				// when actor represents moster graph node, we need to let him escape monster
+				if(!destination.guarded && cb->getGuardingCreaturePosition(source.coord) == actor->initialPosition)
+					return;
+			}
+
+			if(actor->allowBattle)
+			{
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 1
-			logAi->trace(
-				"Bypass src guard while moving from %s to %s",
-				source.coord.toString(),
-				destination.coord.toString());
+				logAi->trace(
+					"Bypass src guard while moving from %s to %s",
+					source.coord.toString(),
+					destination.coord.toString());
 #endif
-			return;
+				return;
+			}
 		}
 
 		destination.blocked = true;

+ 2 - 1
AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.h

@@ -24,9 +24,10 @@ namespace AIPathfinding
 	{
 	private:
 		std::shared_ptr<AINodeStorage> nodeStorage;
+		bool allowBypassObjects;
 
 	public:
-		AIMovementToDestinationRule(std::shared_ptr<AINodeStorage> nodeStorage);
+		AIMovementToDestinationRule(std::shared_ptr<AINodeStorage> nodeStorage, bool allowBypassObjects);
 
 		virtual void process(
 			const PathNodeInfo & source,

+ 3 - 1
lib/logging/VisualLogger.cpp

@@ -21,7 +21,9 @@ void VisualLogger::updateWithLock(std::string channel, std::function<void(IVisua
 
 	mapLines[channel].clear();
 
-	func(VisualLogBuilder(mapLines[channel]));
+	VisualLogBuilder builder(mapLines[channel]);
+	
+	func(builder);
 }
 
 void VisualLogger::visualize(ILogVisualizer & visulizer)