浏览代码

AI pathfinding shared storage

Andrii Danylchenko 4 年之前
父节点
当前提交
0bff5f9eb6

+ 134 - 36
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -26,14 +26,14 @@ AINodeStorage::AINodeStorage(const int3 & Sizes)
 
 AINodeStorage::~AINodeStorage() = default;
 
-void AINodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero)
+void AINodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs)
 {
 	//TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline
 
 	int3 pos;
+	const PlayerColor player = ai->playerID;
 	const int3 sizes = gs->getMapSize();
-	const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(hero->tempOwner)->fogOfWarMap;
-	const PlayerColor player = hero->tempOwner;
+	const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(player)->fogOfWarMap;
 
 	//make 200% sure that these are loop invariants (also a bit shorter code), let compiler do the rest(loop unswitching)
 	const bool useFlying = options.useFlying;
@@ -70,6 +70,11 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
 	}
 }
 
+void AINodeStorage::reset()
+{
+	actors.clear();
+}
+
 const AIPathNode * AINodeStorage::getAINode(const CGPathNode * node) const
 {
 	return static_cast<const AIPathNode *>(node);
@@ -82,25 +87,20 @@ void AINodeStorage::updateAINode(CGPathNode * node, std::function<void(AIPathNod
 	updater(aiNode);
 }
 
-bool AINodeStorage::isBattleNode(const CGPathNode * node) const
-{
-	return (getAINode(node)->chainMask & BATTLE_CHAIN) > 0;
-}
-
-boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(const int3 & pos, const EPathfindingLayer layer, int chainNumber)
+boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(const int3 & pos, const EPathfindingLayer layer, const ChainActor * actor)
 {
 	auto chains = nodes[pos.x][pos.y][pos.z][layer];
 
 	for(AIPathNode & node : chains)
 	{
-		if(node.chainMask == chainNumber)
+		if(node.actor == actor)
 		{
 			return &node;
 		}
 
-		if(node.chainMask == 0)
+		if(!node.actor)
 		{
-			node.chainMask = chainNumber;
+			node.actor = actor;
 
 			return &node;
 		}
@@ -109,19 +109,33 @@ boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(const int3 & pos, c
 	return boost::none;
 }
 
-CGPathNode * AINodeStorage::getInitialNode()
+std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
 {
-	auto hpos = hero->getPosition(false);
-	auto initialNode =
-		getOrCreateNode(hpos, hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND, NORMAL_CHAIN)
-		.get();
+	std::vector<CGPathNode *> initialNodes;
+
+	for(auto actorPtr : actors)
+	{
+		const ChainActor * actor = actorPtr.get();
+		AIPathNode * initialNode =
+			getOrCreateNode(actor->initialPosition, actor->layer, actor)
+			.get();
 
-	initialNode->turns = 0;
-	initialNode->moveRemains = hero->movement;
-	initialNode->danger = 0;
-	initialNode->cost = 0.0;
+		initialNode->turns = actor->initialTurn;
+		initialNode->moveRemains = actor->initialMovement;
+		initialNode->danger = 0;
+		initialNode->cost = 0.0;
 
-	return initialNode;
+		if(actor->isMovable)
+		{
+			initialNodes.push_back(initialNode);
+		}
+		else
+		{
+			initialNode->locked = true;
+		}
+	}
+
+	return initialNodes;
 }
 
 void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility)
@@ -130,7 +144,7 @@ void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, CGPat
 	{
 		AIPathNode & heroNode = nodes[coord.x][coord.y][coord.z][layer][i];
 
-		heroNode.chainMask = 0;
+		heroNode.actor = nullptr;
 		heroNode.danger = 0;
 		heroNode.manaCost = 0;
 		heroNode.specialAction.reset();
@@ -152,9 +166,9 @@ void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInf
 		dstNode->theNodeBefore = srcNode->theNodeBefore;
 		dstNode->manaCost = srcNode->manaCost;
 
-		if(dstNode->specialAction)
+		if(dstNode->specialAction && dstNode->actor)
 		{
-			dstNode->specialAction->applyOnDestination(getHero(), destination, source, dstNode, srcNode);
+			dstNode->specialAction->applyOnDestination(dstNode->actor->hero, destination, source, dstNode, srcNode);
 		}
 	});
 }
@@ -173,7 +187,7 @@ std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
 	{
 		for(EPathfindingLayer i = EPathfindingLayer::LAND; i <= EPathfindingLayer::AIR; i.advance(1))
 		{
-			auto nextNode = getOrCreateNode(neighbour, i, srcNode->chainMask);
+			auto nextNode = getOrCreateNode(neighbour, i, srcNode->actor);
 
 			if(!nextNode || nextNode.get()->accessible == CGPathNode::NOT_SET)
 				continue;
@@ -185,11 +199,37 @@ std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
 	return neighbours;
 }
 
-void AINodeStorage::setHero(HeroPtr heroPtr, const VCAI * _ai)
+const CGHeroInstance * AINodeStorage::getHero(const CGPathNode * node) const
+{
+	auto aiNode = getAINode(node);
+
+	return aiNode->actor->hero;
+}
+
+const std::set<const CGHeroInstance *> AINodeStorage::getAllHeroes() const
+{
+	std::set<const CGHeroInstance *> heroes;
+
+	for(auto actor : actors)
+	{
+		if(actor->hero)
+			heroes.insert(hero);
+	}
+
+	return heroes;
+}
+
+void AINodeStorage::setHeroes(std::vector<HeroPtr> heroes, const VCAI * _ai)
 {
-	hero = heroPtr.get();
 	cb = _ai->myCb.get();
 	ai = _ai;
+
+	for(auto & hero : heroes)
+	{
+		uint64_t mask = 1 << actors.size();
+
+		actors.push_back(std::make_shared<ChainActor>(hero.get(), mask));
+	}
 }
 
 std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
@@ -206,7 +246,7 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
 
 		for(auto & neighbour : accessibleExits)
 		{
-			auto node = getOrCreateNode(neighbour, source.node->layer, srcNode->chainMask);
+			auto node = getOrCreateNode(neighbour, source.node->layer, srcNode->actor);
 
 			if(!node)
 				continue;
@@ -269,7 +309,7 @@ void AINodeStorage::calculateTownPortalTeleportations(
 			if(targetTown->visitingHero)
 				continue;
 
-			auto nodeOptional = getOrCreateNode(targetTown->visitablePos(), EPathfindingLayer::LAND, srcNode->chainMask | CAST_CHAIN);
+			auto nodeOptional = getOrCreateNode(targetTown->visitablePos(), EPathfindingLayer::LAND, srcNode->actor->castActor);
 
 			if(nodeOptional)
 			{
@@ -297,13 +337,14 @@ bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNode
 
 	for(const AIPathNode & node : chains)
 	{
-		auto sameNode = node.chainMask == destinationNode->chainMask;
+		auto sameNode = node.actor == destinationNode->actor;
+
 		if(sameNode	|| node.action == CGPathNode::ENodeAction::UNKNOWN)
 		{
 			continue;
 		}
 
-		if(node.danger <= destinationNode->danger && destinationNode->chainMask == 1 && node.chainMask == 0)
+		if(node.danger <= destinationNode->danger && destinationNode->actor == node.actor->battleActor)
 		{
 			if(node.cost < destinationNode->cost)
 			{
@@ -323,11 +364,20 @@ bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNode
 	return false;
 }
 
-bool AINodeStorage::isTileAccessible(const int3 & pos, const EPathfindingLayer layer) const
+bool AINodeStorage::isTileAccessible(const HeroPtr & hero, const int3 & pos, const EPathfindingLayer layer) const
 {
-	const AIPathNode & node = nodes[pos.x][pos.y][pos.z][layer][0];
+	auto chains = nodes[pos.x][pos.y][pos.z][layer];
+
+	for(const AIPathNode & node : chains)
+	{
+		if(node.action != CGPathNode::ENodeAction::UNKNOWN 
+			&& node.actor && node.actor->hero == hero.h)
+		{
+			return true;
+		}
+	}
 
-	return node.action != CGPathNode::ENodeAction::UNKNOWN;
+	return false;
 }
 
 std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand) const
@@ -338,7 +388,7 @@ std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand)
 
 	for(const AIPathNode & node : chains)
 	{
-		if(node.action == CGPathNode::ENodeAction::UNKNOWN)
+		if(node.action == CGPathNode::ENodeAction::UNKNOWN || !node.actor)
 		{
 			continue;
 		}
@@ -346,6 +396,8 @@ std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand)
 		AIPath path;
 		const AIPathNode * current = &node;
 
+		path.targetHero = node.actor->hero;
+
 		while(current != nullptr && current->coord != initialPos)
 		{
 			AIPathNodeInfo pathNode;
@@ -411,3 +463,49 @@ uint64_t AIPath::getTotalDanger(HeroPtr hero) const
 
 	return danger;
 }
+
+ChainActor::ChainActor(const CGHeroInstance * hero, int chainMask)
+	:hero(hero), isMovable(true), chainMask(chainMask)
+{
+	initialPosition = hero->visitablePos();
+	layer = hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND;
+	initialMovement = hero->movement;
+	initialTurn = 0;
+
+	setupSpecialActors();
+}
+
+void ChainActor::copyFrom(ChainActor * base)
+{
+	hero = base->hero;
+	layer = base->layer;
+	initialMovement = base->initialMovement;
+	initialTurn = base->initialTurn;
+}
+
+void ChainActor::setupSpecialActors()
+{
+	auto allActors = std::vector<ChainActor *>{ this };
+	specialActors.resize(7);
+
+	for(int i = 1; i < 8; i++)
+	{
+		ChainActor & specialActor = specialActors[i - 1];
+
+		specialActor.copyFrom(this);
+		specialActor.allowBattle = (i & 1) > 0;
+		specialActor.allowSpellCast = (i & 2) > 0;
+		specialActor.allowUseResources = (i & 4) > 0;
+
+		allActors.push_back(&specialActor);
+	}
+
+	for(int i = 0; i <= 8; i++)
+	{
+		ChainActor * actor = allActors[i];
+
+		actor->battleActor = allActors[i | 1];
+		actor->castActor = allActors[i | 2];
+		actor->resourceActor = allActors[i | 4];
+	}
+}

+ 46 - 21
AI/Nullkiller/Pathfinding/AINodeStorage.h

@@ -17,14 +17,43 @@
 #include "../Goals/AbstractGoal.h"
 #include "Actions/ISpecialAction.h"
 
-struct AIPathNode;
+class ChainActor
+{
+private:
+	std::vector<ChainActor> specialActors;
+
+	void copyFrom(ChainActor * base);
+	void setupSpecialActors();
+
+public:
+	// chain flags, can be combined meaning hero exchange and so on
+	uint64_t chainMask;
+	bool isMovable;
+	bool allowUseResources;
+	bool allowBattle;
+	bool allowSpellCast;
+	const CGHeroInstance * hero;
+	const ChainActor * battleActor;
+	const ChainActor * castActor;
+	const ChainActor * resourceActor;
+	int3 initialPosition;
+	EPathfindingLayer layer;
+	uint32_t initialMovement;
+	uint32_t initialTurn;
+
+	ChainActor()
+	{
+	}
+
+	ChainActor(const CGHeroInstance * hero, int chainMask);
+};
 
 struct AIPathNode : public CGPathNode
 {
-	uint32_t chainMask;
 	uint64_t danger;
 	uint32_t manaCost;
 	std::shared_ptr<const ISpecialAction> specialAction;
+	const ChainActor * actor;
 };
 
 struct AIPathNodeInfo
@@ -40,6 +69,7 @@ struct AIPath
 	std::vector<AIPathNodeInfo> nodes;
 	std::shared_ptr<const ISpecialAction> specialAction;
 	uint64_t targetObjectDanger;
+	const CGHeroInstance * targetHero;
 
 	AIPath();
 
@@ -65,26 +95,21 @@ private:
 	const VCAI * ai;
 	const CGHeroInstance * hero;
 	std::unique_ptr<FuzzyHelper> dangerEvaluator;
+	std::vector<std::shared_ptr<ChainActor>> actors;
 
 	STRONG_INLINE
 	void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility);
 
 public:
-	/// more than 1 chain layer allows us to have more than 1 path to each tile so we can chose more optimal one.
-	static const int NUM_CHAINS = 3;
-
-	// chain flags, can be combined
-	static const int NORMAL_CHAIN = 1;
-	static const int BATTLE_CHAIN = 2;
-	static const int CAST_CHAIN = 4;
-	static const int RESOURCE_CHAIN = 8;
-
+	/// more than 1 chain layer for each hero allows us to have more than 1 path to each tile so we can chose more optimal one.
+	static const int NUM_CHAINS = 3 * GameConstants::MAX_HEROES_PER_PLAYER;
+	
 	AINodeStorage(const int3 & sizes);
 	~AINodeStorage();
 
-	void initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero) override;
+	void initialize(const PathfinderOptions & options, const CGameState * gs) override;
 
-	virtual CGPathNode * getInitialNode() override;
+	virtual std::vector<CGPathNode *> getInitialNodes() override;
 
 	virtual std::vector<CGPathNode *> calculateNeighbours(
 		const PathNodeInfo & source,
@@ -101,18 +126,18 @@ public:
 	const AIPathNode * getAINode(const CGPathNode * node) const;
 	void updateAINode(CGPathNode * node, std::function<void (AIPathNode *)> updater);
 
-	bool isBattleNode(const CGPathNode * node) const;
 	bool hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const;
-	boost::optional<AIPathNode *> getOrCreateNode(const int3 & coord, const EPathfindingLayer layer, int chainNumber);
+	boost::optional<AIPathNode *> getOrCreateNode(const int3 & coord, const EPathfindingLayer layer, const ChainActor * actor);
 	std::vector<AIPath> getChainInfo(const int3 & pos, bool isOnLand) const;
-	bool isTileAccessible(const int3 & pos, const EPathfindingLayer layer) const;
+	bool isTileAccessible(const HeroPtr & hero, const int3 & pos, const EPathfindingLayer layer) const;
 
-	void setHero(HeroPtr heroPtr, const VCAI * ai);
+	void setHeroes(std::vector<HeroPtr> heroes, const VCAI * ai);
 
-	const CGHeroInstance * getHero() const
-	{
-		return hero;
-	}
+	const CGHeroInstance * getHero(const CGPathNode * node) const;
+
+	const std::set<const CGHeroInstance *> getAllHeroes() const;
+
+	void reset();
 
 	uint64_t evaluateDanger(const int3 &  tile) const
 	{

+ 13 - 88
AI/Nullkiller/Pathfinding/AIPathfinder.cpp

@@ -13,8 +13,7 @@
 #include "../../../CCallback.h"
 #include "../../../lib/mapping/CMap.h"
 
-std::vector<std::shared_ptr<AINodeStorage>> AIPathfinder::storagePool;
-std::map<HeroPtr, std::shared_ptr<AINodeStorage>> AIPathfinder::storageMap;
+std::shared_ptr<AINodeStorage> AIPathfinder::storage;
 
 AIPathfinder::AIPathfinder(CPlayerSpecificInfoCallback * cb, VCAI * ai)
 	:cb(cb), ai(ai)
@@ -23,22 +22,17 @@ AIPathfinder::AIPathfinder(CPlayerSpecificInfoCallback * cb, VCAI * ai)
 
 void AIPathfinder::init()
 {
-	storagePool.clear();
-	storageMap.clear();
+	storage.reset();
 }
 
 bool AIPathfinder::isTileAccessible(const HeroPtr & hero, const int3 & tile) const
 {
-	std::shared_ptr<const AINodeStorage> nodeStorage = getStorage(hero);
-
-	return nodeStorage->isTileAccessible(tile, EPathfindingLayer::LAND)
-		|| nodeStorage->isTileAccessible(tile, EPathfindingLayer::SAIL);
+	return storage->isTileAccessible(hero, tile, EPathfindingLayer::LAND)
+		|| storage->isTileAccessible(hero, tile, EPathfindingLayer::SAIL);
 }
 
 std::vector<AIPath> AIPathfinder::getPathInfo(const HeroPtr & hero, const int3 & tile) const
 {
-	std::shared_ptr<const AINodeStorage> nodeStorage = getStorage(hero);
-
 	const TerrainTile * tileInfo = cb->getTile(tile, false);
 
 	if(!tileInfo)
@@ -46,95 +40,26 @@ std::vector<AIPath> AIPathfinder::getPathInfo(const HeroPtr & hero, const int3 &
 		return std::vector<AIPath>();
 	}
 
-	return nodeStorage->getChainInfo(tile, !tileInfo->isWater());
+	return storage->getChainInfo(tile, !tileInfo->isWater());
 }
 
 void AIPathfinder::updatePaths(std::vector<HeroPtr> heroes)
 {
-	storageMap.clear();
-
-	auto calculatePaths = [&](const CGHeroInstance * hero, std::shared_ptr<AIPathfinding::AIPathfinderConfig> config)
-	{
-		logAi->debug("Recalculate paths for %s", hero->name);
-
-		cb->calculatePaths(config, hero);
-	};
-
-	std::vector<Task> calculationTasks;
-
-	for(HeroPtr hero : heroes)
+	if(!storage)
 	{
-		std::shared_ptr<AINodeStorage> nodeStorage;
-
-		if(storageMap.size() < storagePool.size())
-		{
-			nodeStorage = storagePool.at(storageMap.size());
-		}
-		else
-		{
-			nodeStorage = std::make_shared<AINodeStorage>(cb->getMapSize());
-			storagePool.push_back(nodeStorage);
-		}
-
-		storageMap[hero] = nodeStorage;
-		nodeStorage->setHero(hero, ai);
-
-		auto config = std::make_shared<AIPathfinding::AIPathfinderConfig>(cb, ai, nodeStorage);
-
-		calculationTasks.push_back(std::bind(calculatePaths, hero.get(), config));
+		storage = std::make_shared<AINodeStorage>(cb->getMapSize());
 	}
 
-	int threadsCount = std::min(
-		boost::thread::hardware_concurrency(),
-		(uint32_t)calculationTasks.size());
+	logAi->debug("Recalculate all paths");
 
-	if(threadsCount <= 1)
-	{
-		for(auto task : calculationTasks)
-		{
-			task();
-		}
-	}
-	else
-	{
-		CThreadHelper helper(&calculationTasks, threadsCount);
+	storage.reset();
+	storage->setHeroes(heroes, ai);
 
-		helper.run();
-	}
+		auto config = std::make_shared<AIPathfinding::AIPathfinderConfig>(cb, ai, storage);
+		cb->calculatePaths(config);
 }
 
 void AIPathfinder::updatePaths(const HeroPtr & hero)
 {
-	std::shared_ptr<AINodeStorage> nodeStorage;
-
-	if(!vstd::contains(storageMap, hero))
-	{
-		if(storageMap.size() < storagePool.size())
-		{
-			nodeStorage = storagePool.at(storageMap.size());
-		}
-		else
-		{
-			nodeStorage = std::make_shared<AINodeStorage>(cb->getMapSize());
-			storagePool.push_back(nodeStorage);
-		}
-
-		storageMap[hero] = nodeStorage;
-		nodeStorage->setHero(hero, ai);
-	}
-	else
-	{
-		nodeStorage = storageMap.at(hero);
-	}
-
-	logAi->debug("Recalculate paths for %s", hero->name);
-	auto config = std::make_shared<AIPathfinding::AIPathfinderConfig>(cb, ai, nodeStorage);
-
-	cb->calculatePaths(config, hero.get());
-}
-
-std::shared_ptr<const AINodeStorage> AIPathfinder::getStorage(const HeroPtr & hero) const
-{
-	return storageMap.at(hero);
+	updatePaths(std::vector<HeroPtr>{hero});
 }
-

+ 1 - 3
AI/Nullkiller/Pathfinding/AIPathfinder.h

@@ -17,12 +17,10 @@
 class AIPathfinder
 {
 private:
-	static std::vector<std::shared_ptr<AINodeStorage>> storagePool;
-	static std::map<HeroPtr, std::shared_ptr<AINodeStorage>> storageMap;
+	static std::shared_ptr<AINodeStorage> storage;
 	CPlayerSpecificInfoCallback * cb;
 	VCAI * ai;
 
-	std::shared_ptr<const AINodeStorage> getStorage(const HeroPtr & hero) const;
 public:
 	AIPathfinder(CPlayerSpecificInfoCallback * cb, VCAI * ai);
 	std::vector<AIPath> getPathInfo(const HeroPtr & hero, const int3 & tile) const;

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

@@ -37,7 +37,19 @@ namespace AIPathfinding
 		CPlayerSpecificInfoCallback * cb,
 		VCAI * ai,
 		std::shared_ptr<AINodeStorage> nodeStorage)
-		:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage))
+		:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage)), aiNodeStorage(nodeStorage)
 	{
 	}
+
+	CPathfinderHelper * AIPathfinderConfig::getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs)
+	{
+		if(!pathfindingHelper)
+		{
+			auto hero = aiNodeStorage->getHero(source.node);
+
+			pathfindingHelper.reset(new CPathfinderHelper(gs, hero, options));
+		}
+
+		return pathfindingHelper.get();
+	}
 }

+ 6 - 0
AI/Nullkiller/Pathfinding/AIPathfinderConfig.h

@@ -17,10 +17,16 @@ namespace AIPathfinding
 {
 	class AIPathfinderConfig : public PathfinderConfig
 	{
+	private:
+		std::unique_ptr<CPathfinderHelper> pathfindingHelper;
+		std::shared_ptr<AINodeStorage> aiNodeStorage;
+
 	public:
 		AIPathfinderConfig(
 			CPlayerSpecificInfoCallback * cb,
 			VCAI * ai,
 			std::shared_ptr<AINodeStorage> nodeStorage);
+
+		virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override;
 	};
 }

+ 10 - 0
AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp

@@ -22,11 +22,21 @@ namespace AIPathfinding
 		return Goals::sptr(Goals::BuildBoat(shipyard));
 	}
 
+	const ChainActor * BuildBoatAction::getActor(const ChainActor * sourceActor) const
+	{
+		return sourceActor->resourceActor;
+	}
+
 	Goals::TSubgoal SummonBoatAction::whatToDo(const HeroPtr & hero) const
 	{
 		return Goals::sptr(Goals::AdventureSpellCast(hero, SpellID::SUMMON_BOAT));
 	}
 
+	const ChainActor * SummonBoatAction::getActor(const ChainActor * sourceActor) const
+	{
+		return sourceActor->castActor;
+	}
+
 	void SummonBoatAction::applyOnDestination(
 		const CGHeroInstance * hero,
 		CDestinationNodeInfo & destination,

+ 6 - 18
AI/Nullkiller/Pathfinding/Actions/BoatActions.h

@@ -18,29 +18,13 @@ namespace AIPathfinding
 {
 	class VirtualBoatAction : public ISpecialAction
 	{
-	private:
-		uint64_t specialChain;
-
 	public:
-		VirtualBoatAction(uint64_t specialChain)
-			:specialChain(specialChain)
-		{
-		}
-
-		uint64_t getSpecialChain() const
-		{
-			return specialChain;
-		}
+		virtual const ChainActor * getActor(const ChainActor * sourceActor) const = 0;
 	};
 	
 	class SummonBoatAction : public VirtualBoatAction
 	{
 	public:
-		SummonBoatAction()
-			:VirtualBoatAction(AINodeStorage::CAST_CHAIN)
-		{
-		}
-
 		virtual Goals::TSubgoal whatToDo(const HeroPtr & hero) const override;
 
 		virtual void applyOnDestination(
@@ -52,6 +36,8 @@ namespace AIPathfinding
 
 		bool isAffordableBy(const CGHeroInstance * hero, const AIPathNode * source) const;
 
+		virtual const ChainActor * getActor(const ChainActor * sourceActor) const override;
+
 	private:
 		uint32_t getManaCost(const CGHeroInstance * hero) const;
 	};
@@ -63,10 +49,12 @@ namespace AIPathfinding
 
 	public:
 		BuildBoatAction(const IShipyard * shipyard)
-			:VirtualBoatAction(AINodeStorage::RESOURCE_CHAIN), shipyard(shipyard)
+			: shipyard(shipyard)
 		{
 		}
 
 		virtual Goals::TSubgoal whatToDo(const HeroPtr & hero) const override;
+
+		virtual const ChainActor * getActor(const ChainActor * sourceActor) const override;
 	};
 }

+ 7 - 4
AI/Nullkiller/Pathfinding/PathfindingManager.cpp

@@ -64,7 +64,7 @@ Goals::TGoalVec PathfindingManager::howToVisitObj(ObjectIdRef obj) const
 
 Goals::TGoalVec PathfindingManager::howToVisitTile(const HeroPtr & hero, const int3 & tile, bool allowGatherArmy) const
 {
-	auto result = findPath(hero, tile, allowGatherArmy, [&](int3 firstTileToGet) -> Goals::TSubgoal
+	auto result = findPaths(tile, allowGatherArmy, hero, [&](int3 firstTileToGet) -> Goals::TSubgoal
 	{
 		return sptr(Goals::VisitTile(firstTileToGet).sethero(hero).setisAbstract(true));
 	});
@@ -86,7 +86,7 @@ Goals::TGoalVec PathfindingManager::howToVisitObj(const HeroPtr & hero, ObjectId
 
 	int3 dest = obj->visitablePos();
 
-	auto result = findPath(hero, dest, allowGatherArmy, [&](int3 firstTileToGet) -> Goals::TSubgoal
+	auto result = findPaths(dest, allowGatherArmy, hero, [&](int3 firstTileToGet) -> Goals::TSubgoal
 	{
 		if(obj->ID.num == Obj::HERO && obj->getOwner() == hero->getOwner())
 			return sptr(Goals::VisitHero(obj->id.getNum()).sethero(hero).setisAbstract(true));
@@ -107,10 +107,10 @@ std::vector<AIPath> PathfindingManager::getPathsToTile(const HeroPtr & hero, con
 	return pathfinder->getPathInfo(hero, tile);
 }
 
-Goals::TGoalVec PathfindingManager::findPath(
-	HeroPtr hero,
+Goals::TGoalVec PathfindingManager::findPaths(
 	crint3 dest,
 	bool allowGatherArmy,
+	HeroPtr hero,
 	const std::function<Goals::TSubgoal(int3)> doVisitTile) const
 {
 	Goals::TGoalVec result;
@@ -125,6 +125,9 @@ Goals::TGoalVec PathfindingManager::findPath(
 
 	for(auto path : chainInfo)
 	{
+		if(hero && hero.get() != path.targetHero)
+			continue;
+
 		int3 firstTileToGet = path.firstTileToGet();
 #ifdef VCMI_TRACE_PATHFINDER
 		logAi->trace("Path found size=%i, first tile=%s", path.nodes.size(), firstTileToGet.toString());

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

@@ -60,10 +60,10 @@ private:
 	void init(CPlayerSpecificInfoCallback * CB) override;
 	void setAI(VCAI * AI) override;
 
-	Goals::TGoalVec findPath(
-		HeroPtr hero,
+	Goals::TGoalVec findPaths(
 		crint3 dest,
 		bool allowGatherArmy,
+		HeroPtr hero,
 		const std::function<Goals::TSubgoal(int3)> goalFactory) const;
 
 	Goals::TSubgoal clearWayTo(HeroPtr hero, int3 firstTileToGet) const;

+ 18 - 12
AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp

@@ -73,14 +73,16 @@ namespace AIPathfinding
 			}
 		}
 
-		auto hero = nodeStorage->getHero();
-		auto summonBoatSpell = SpellID(SpellID::SUMMON_BOAT).toSpell();
-
-		if(hero->canCastThisSpell(summonBoatSpell)
-			&& hero->getSpellSchoolLevel(summonBoatSpell) >= SecSkillLevel::ADVANCED)
+		for(const CGHeroInstance * hero : nodeStorage->getAllHeroes())
 		{
-			// TODO: For lower school level we might need to check the existance of some boat
-			summonableVirtualBoat.reset(new SummonBoatAction());
+			auto summonBoatSpell = SpellID(SpellID::SUMMON_BOAT).toSpell();
+
+			if(hero->canCastThisSpell(summonBoatSpell)
+				&& hero->getSpellSchoolLevel(summonBoatSpell) >= SecSkillLevel::ADVANCED)
+			{
+				// TODO: For lower school level we might need to check the existance of some boat
+				summonableVirtualBoats[hero] = std::make_shared<SummonBoatAction>();
+			}
 		}
 	}
 
@@ -94,11 +96,15 @@ namespace AIPathfinding
 		{
 			virtualBoat = virtualBoats.at(destination.coord);
 		}
-		else if(
-			summonableVirtualBoat
-			&& summonableVirtualBoat->isAffordableBy(nodeStorage->getHero(), nodeStorage->getAINode(source.node)))
+		else
 		{
-			virtualBoat = summonableVirtualBoat;
+			const CGHeroInstance * hero = nodeStorage->getHero(source.node);
+
+			if(vstd::contains(summonableVirtualBoats, hero)
+				&& summonableVirtualBoats.at(hero)->isAffordableBy(hero, nodeStorage->getAINode(source.node)))
+			{
+				virtualBoat = summonableVirtualBoats.at(hero);
+			}
 		}
 
 		return virtualBoat;
@@ -116,7 +122,7 @@ namespace AIPathfinding
 			auto boatNodeOptional = nodeStorage->getOrCreateNode(
 				node->coord,
 				node->layer,
-				node->chainMask | virtualBoat->getSpecialChain());
+				virtualBoat->getActor(node->actor));
 
 			if(boatNodeOptional)
 			{

+ 1 - 1
AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.h

@@ -26,7 +26,7 @@ namespace AIPathfinding
 		VCAI * ai;
 		std::map<int3, std::shared_ptr<const BuildBoatAction>> virtualBoats;
 		std::shared_ptr<AINodeStorage> nodeStorage;
-		std::shared_ptr<const SummonBoatAction> summonableVirtualBoat;
+		std::map<const CGHeroInstance *, std::shared_ptr<const SummonBoatAction>> summonableVirtualBoats;
 
 	public:
 		AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, VCAI * ai, std::shared_ptr<AINodeStorage> nodeStorage);

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

@@ -40,8 +40,7 @@ namespace AIPathfinding
 
 		if(blocker == BlockingReason::DESTINATION_BLOCKVIS && destination.nodeObject)
 		{
-			auto objID = destination.nodeObject->ID;
-			auto enemyHero = objID == Obj::HERO && destination.objectRelations == PlayerRelations::ENEMIES;
+			auto enemyHero = destination.nodeHero && destination.heroRelations == PlayerRelations::ENEMIES;
 
 			if(!enemyHero && !isObjectRemovable(destination.nodeObject))
 			{
@@ -74,7 +73,8 @@ namespace AIPathfinding
 			});
 
 			auto guardsAlreadyBypassed = destGuardians.empty() && srcGuardians.size();
-			if(guardsAlreadyBypassed && nodeStorage->isBattleNode(source.node))
+			auto srcNode = nodeStorage->getAINode(source.node);
+			if(guardsAlreadyBypassed && srcNode->actor->allowBattle)
 			{
 #ifdef VCMI_TRACE_PATHFINDER
 				logAi->trace(
@@ -90,7 +90,7 @@ namespace AIPathfinding
 			auto battleNodeOptional = nodeStorage->getOrCreateNode(
 				destination.coord,
 				destination.node->layer,
-				destNode->chainMask | AINodeStorage::BATTLE_CHAIN);
+				destNode->actor->battleActor);
 
 			if(!battleNodeOptional)
 			{

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

@@ -35,7 +35,7 @@ namespace AIPathfinding
 			return;
 		}
 
-		if(blocker == BlockingReason::SOURCE_GUARDED && nodeStorage->isBattleNode(source.node))
+		if(blocker == BlockingReason::SOURCE_GUARDED && nodeStorage->getAINode(source.node)->actor->allowBattle)
 		{
 #ifdef VCMI_TRACE_PATHFINDER
 			logAi->trace(