mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	NKAI: graph add battle layer
This commit is contained in:
		| @@ -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()); | ||||
|   | ||||
| @@ -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>( | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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; | ||||
| 			} | ||||
|  | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -48,7 +48,7 @@ namespace AIPathfinding | ||||
|  | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		 | ||||
| 		if(!allowBypassObjects) | ||||
| 			return; | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user