Răsfoiți Sursa

NKAI: remove 5th dimension

Andrii Danylchenko 1 an în urmă
părinte
comite
017fb204a1

+ 183 - 162
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -24,7 +24,8 @@
 namespace NKAI
 {
 
-std::shared_ptr<boost::multi_array<AIPathNode, 5>> AISharedStorage::shared;
+std::shared_ptr<boost::multi_array<AIPathNode, 4>> AISharedStorage::shared;
+uint64_t AISharedStorage::version = 0;
 boost::mutex AISharedStorage::locker;
 std::set<int3> commitedTiles;
 std::set<int3> commitedTilesInitial;
@@ -40,11 +41,24 @@ const bool DO_NOT_SAVE_TO_COMMITED_TILES = false;
 AISharedStorage::AISharedStorage(int3 sizes)
 {
 	if(!shared){
-		shared.reset(new boost::multi_array<AIPathNode, 5>(
-			boost::extents[EPathfindingLayer::NUM_LAYERS][sizes.z][sizes.x][sizes.y][AIPathfinding::NUM_CHAINS]));
-	}
+		shared.reset(new boost::multi_array<AIPathNode, 4>(
+			boost::extents[sizes.z][sizes.x][sizes.y][AIPathfinding::NUM_CHAINS]));
+
+		nodes = shared;
 
-	nodes = shared;
+		foreach_tile_pos([&](const int3 & pos)
+			{
+				for(auto i = 0; i < AIPathfinding::NUM_CHAINS; i++)
+				{
+					auto & node = get(pos)[i];
+						
+					node.version = -1;
+					node.coord = pos;
+				}
+			});
+	}
+	else
+		nodes = shared;
 }
 
 AISharedStorage::~AISharedStorage()
@@ -80,6 +94,9 @@ void AIPathNode::addSpecialAction(std::shared_ptr<const SpecialAction> action)
 AINodeStorage::AINodeStorage(const Nullkiller * ai, const int3 & Sizes)
 	: sizes(Sizes), ai(ai), cb(ai->cb.get()), nodes(Sizes)
 {
+	accesibility = std::make_unique<boost::multi_array<EPathAccessibility, 4>>(
+		boost::extents[sizes.z][sizes.x][sizes.y][EPathfindingLayer::NUM_LAYERS]);
+
 	dangerEvaluator.reset(new FuzzyHelper(ai));
 }
 
@@ -90,6 +107,8 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
 	if(heroChainPass)
 		return;
 
+	AISharedStorage::version++;
+
 	//TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline
 	const PlayerColor fowPlayer = ai->playerID;
 	const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(fowPlayer)->fogOfWarMap;
@@ -152,9 +171,9 @@ std::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
 {
 	int bucketIndex = ((uintptr_t)actor) % AIPathfinding::BUCKET_COUNT;
 	int bucketOffset = bucketIndex * AIPathfinding::BUCKET_SIZE;
-	auto chains = nodes.get(pos, layer);
+	auto chains = nodes.get(pos);
 
-	if(chains[0].blocked())
+	if(blocked(pos, layer))
 	{
 		return std::nullopt;
 	}
@@ -163,15 +182,17 @@ std::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
 	{
 		AIPathNode & node = chains[i + bucketOffset];
 
-		if(node.actor == actor)
+		if(node.version != AISharedStorage::version)
 		{
+			node.reset(layer, getAccessibility(pos, layer));
+			node.version = AISharedStorage::version;
+			node.actor = actor;
+
 			return &node;
 		}
 
-		if(!node.actor)
+		if(node.actor == actor && node.layer == layer)
 		{
-			node.actor = actor;
-
 			return &node;
 		}
 	}
@@ -226,21 +247,6 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
 	return initialNodes;
 }
 
-void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, EPathAccessibility accessibility)
-{
-	for(AIPathNode & heroNode : nodes.get(coord, layer))
-{
-		heroNode.actor = nullptr;
-		heroNode.danger = 0;
-		heroNode.manaCost = 0;
-		heroNode.specialAction.reset();
-		heroNode.armyLoss = 0;
-		heroNode.chainOther = nullptr;
-		heroNode.dayFlags = DayFlags::NONE;
-		heroNode.update(coord, layer, accessibility);
-	}
-}
-
 void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInfo & source)
 {
 	const AIPathNode * srcNode = getAINode(source.node);
@@ -306,30 +312,31 @@ void AINodeStorage::commit(
 	}
 }
 
-std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
+void AINodeStorage::calculateNeighbours(
+	std::vector<CGPathNode *> & result,
 	const PathNodeInfo & source,
+	EPathfindingLayer layer,
 	const PathfinderConfig * pathfinderConfig,
 	const CPathfinderHelper * pathfinderHelper)
 {
-	std::vector<CGPathNode *> neighbours;
-	neighbours.reserve(16);
+	std::vector<int3> accessibleNeighbourTiles;
+
+	result.clear();
+	accessibleNeighbourTiles.reserve(8);
+
+	pathfinderHelper->calculateNeighbourTiles(accessibleNeighbourTiles, source);
+
 	const AIPathNode * srcNode = getAINode(source.node);
-	auto accessibleNeighbourTiles = pathfinderHelper->getNeighbourTiles(source);
 
 	for(auto & neighbour : accessibleNeighbourTiles)
 	{
-		for(EPathfindingLayer i = EPathfindingLayer::LAND; i < EPathfindingLayer::NUM_LAYERS; i.advance(1))
-		{
-			auto nextNode = getOrCreateNode(neighbour, i, srcNode->actor);
+		auto nextNode = getOrCreateNode(neighbour, layer, srcNode->actor);
 
-			if(!nextNode || nextNode.value()->accessible == EPathAccessibility::NOT_SET)
-				continue;
+		if(!nextNode || nextNode.value()->accessible == EPathAccessibility::NOT_SET)
+			continue;
 
-			neighbours.push_back(nextNode.value());
-		}
+		result.push_back(nextNode.value());
 	}
-	
-	return neighbours;
 }
 
 constexpr std::array phisycalLayers = {EPathfindingLayer::LAND, EPathfindingLayer::SAIL};
@@ -346,19 +353,16 @@ bool AINodeStorage::increaseHeroChainTurnLimit()
 	{
 		foreach_tile_pos([&](const int3 & pos)
 		{
-			auto chains = nodes.get(pos, layer);
-
-			if(!chains[0].blocked())
-			{
-				for(AIPathNode & node : chains)
+			iterateValidNodesUntil(pos, layer, [&](AIPathNode & node)
 				{
 					if(node.turns <= heroChainTurn && node.action != EPathNodeAction::UNKNOWN)
 					{
 						commitedTiles.insert(pos);
-						break;
+						return true;
 					}
-				}
-			}
+
+					return false;
+				});
 		});
 	}
 
@@ -374,22 +378,17 @@ bool AINodeStorage::calculateHeroChainFinal()
 	{
 		foreach_tile_pos([&](const int3 & pos)
 		{
-			auto chains = nodes.get(pos, layer);
-
-			if(!chains[0].blocked())
-			{
-				for(AIPathNode & node : chains)
+			iterateValidNodes(pos, layer, [&](AIPathNode & node)
 				{
 					if(node.turns > heroChainTurn
 						&& !node.locked
 						&& node.action != EPathNodeAction::UNKNOWN
 						&& node.actor->actorExchangeCount > 1
-						&& !hasBetterChain(&node, &node, chains))
+						&& !hasBetterChain(&node, node))
 					{
 						heroChain.push_back(&node);
 					}
-				}
-			}
+				});
 		});
 	}
 
@@ -443,21 +442,19 @@ public:
 
 			for(auto layer : phisycalLayers)
 			{
-				auto chains = nodes.get(pos, layer);
+				existingChains.clear();
 
-				// fast cut inactive nodes
-				if(chains[0].blocked())
+				storage.iterateValidNodes(pos, layer, [this](AIPathNode & node)
+					{
+						if(node.turns <= heroChainTurn && node.action != EPathNodeAction::UNKNOWN)
+							existingChains.push_back(&node);
+					});
+
+				if(existingChains.empty())
 					continue;
 
-				existingChains.clear();
 				newChains.clear();
 
-				for(AIPathNode & node : chains)
-				{
-					if(node.turns <= heroChainTurn && node.action != EPathNodeAction::UNKNOWN)
-						existingChains.push_back(&node);
-				}
-
 				std::shuffle(existingChains.begin(), existingChains.end(), randomEngine);
 
 				for(AIPathNode * node : existingChains)
@@ -617,9 +614,8 @@ void HeroChainCalculationTask::cleanupInefectiveChains(std::vector<ExchangeCandi
 	vstd::erase_if(result, [&](const ExchangeCandidate & chainInfo) -> bool
 	{
 		auto pos = chainInfo.coord;
-		auto chains = nodes.get(pos, EPathfindingLayer::LAND);
-		auto isNotEffective = storage.hasBetterChain(chainInfo.carrierParent, &chainInfo, chains)
-			|| storage.hasBetterChain(chainInfo.carrierParent, &chainInfo, result);
+		auto isNotEffective = storage.hasBetterChain(chainInfo.carrierParent, chainInfo)
+			|| storage.hasBetterChain(chainInfo.carrierParent, chainInfo, result);
 
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
 		if(isNotEffective)
@@ -645,7 +641,7 @@ void HeroChainCalculationTask::calculateHeroChain(
 {
 	for(AIPathNode * node : variants)
 	{
-		if(node == srcNode || !node->actor)
+		if(node == srcNode || !node->actor || node->version != AISharedStorage::version)
 			continue;
 
 		if((node->actor->chainMask & chainMask) == 0 && (srcNode->actor->chainMask & chainMask) == 0)
@@ -1195,95 +1191,116 @@ void AINodeStorage::calculateTownPortalTeleportations(std::vector<CGPathNode *>
 
 bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
 {
-	auto pos = destination.coord;
-	auto chains = nodes.get(pos, EPathfindingLayer::LAND);
+	auto candidateNode = getAINode(destination.node);
 
-	return hasBetterChain(source.node, getAINode(destination.node), chains);
+	return hasBetterChain(source.node, *candidateNode);
 }
 
-template<class NodeRange>
 bool AINodeStorage::hasBetterChain(
-	const CGPathNode * source, 
-	const AIPathNode * candidateNode,
-	const NodeRange & chains) const
+	const CGPathNode * source,
+	const AIPathNode & candidateNode) const
 {
-	auto candidateActor = candidateNode->actor;
+	return iterateValidNodesUntil(
+		candidateNode.coord,
+		candidateNode.layer,
+		[this, &source, candidateNode](const AIPathNode & node) -> bool
+		{
+			return isOtherChainBetter(source, candidateNode, node);
+		});
+}
 
-	for(const AIPathNode & node : chains)
+template<class NodeRange>
+bool AINodeStorage::hasBetterChain(
+	const CGPathNode * source,
+	const AIPathNode & candidateNode,
+	const NodeRange & nodes) const
+{
+	for(const AIPathNode & node : nodes)
 	{
-		auto sameNode = node.actor == candidateNode->actor;
-
-		if(sameNode	|| node.action == EPathNodeAction::UNKNOWN || !node.actor || !node.actor->hero)
-		{
-			continue;
-		}
+		if(isOtherChainBetter(source, candidateNode, node))
+			return true;
+	}
 
-		if(node.danger <= candidateNode->danger && candidateNode->actor == node.actor->battleActor)
-		{
-			if(node.getCost() < candidateNode->getCost())
-			{
-#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
-				logAi->trace(
-					"Block ineficient battle move %s->%s, hero: %s[%X], army %lld, mp diff: %i",
-					source->coord.toString(),
-					candidateNode->coord.toString(),
-					candidateNode->actor->hero->getNameTranslated(),
-					candidateNode->actor->chainMask,
-					candidateNode->actor->armyValue,
-					node.moveRemains - candidateNode->moveRemains);
-#endif
-				return true;
-			}
-		}
+	return false;
+}
 
-		if(candidateActor->chainMask != node.actor->chainMask && heroChainPass != EHeroChainPass::FINAL)
-			continue;
+bool AINodeStorage::isOtherChainBetter(
+	const CGPathNode * source,
+	const AIPathNode & candidateNode,
+	const AIPathNode & other) const
+{
+	auto sameNode = other.actor == candidateNode.actor;
 
-		auto nodeActor = node.actor;
-		auto nodeArmyValue = nodeActor->armyValue - node.armyLoss;
-		auto candidateArmyValue = candidateActor->armyValue - candidateNode->armyLoss;
+	if(sameNode || other.action == EPathNodeAction::UNKNOWN || !other.actor || !other.actor->hero)
+	{
+		return false;
+	}
 
-		if(nodeArmyValue > candidateArmyValue
-			&& node.getCost() <= candidateNode->getCost())
+	if(other.danger <= candidateNode.danger && candidateNode.actor == other.actor->battleActor)
+	{
+		if(other.getCost() < candidateNode.getCost())
 		{
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
 			logAi->trace(
-				"Block ineficient move because of stronger army %s->%s, hero: %s[%X], army %lld, mp diff: %i",
+				"Block ineficient battle move %s->%s, hero: %s[%X], army %lld, mp diff: %i",
 				source->coord.toString(),
-				candidateNode->coord.toString(),
-				candidateNode->actor->hero->getNameTranslated(),
-				candidateNode->actor->chainMask,
-				candidateNode->actor->armyValue,
-				node.moveRemains - candidateNode->moveRemains);
+				candidateNode.coord.toString(),
+				candidateNode.actor->hero->getNameTranslated(),
+				candidateNode.actor->chainMask,
+				candidateNode.actor->armyValue,
+				other.moveRemains - candidateNode.moveRemains);
 #endif
 			return true;
 		}
+	}
 
-		if(heroChainPass == EHeroChainPass::FINAL)
+	if(candidateNode.actor->chainMask != other.actor->chainMask && heroChainPass != EHeroChainPass::FINAL)
+		return false;
+
+	auto nodeActor = other.actor;
+	auto nodeArmyValue = nodeActor->armyValue - other.armyLoss;
+	auto candidateArmyValue = candidateNode.actor->armyValue - candidateNode.armyLoss;
+
+	if(nodeArmyValue > candidateArmyValue
+		&& other.getCost() <= candidateNode.getCost())
+	{
+#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
+		logAi->trace(
+			"Block ineficient move because of stronger army %s->%s, hero: %s[%X], army %lld, mp diff: %i",
+			source->coord.toString(),
+			candidateNode.coord.toString(),
+			candidateNode.actor->hero->getNameTranslated(),
+			candidateNode.actor->chainMask,
+			candidateNode.actor->armyValue,
+			other.moveRemains - candidateNode.moveRemains);
+#endif
+		return true;
+	}
+
+	if(heroChainPass == EHeroChainPass::FINAL)
+	{
+		if(nodeArmyValue == candidateArmyValue
+			&& nodeActor->heroFightingStrength >= candidateNode.actor->heroFightingStrength
+			&& other.getCost() <= candidateNode.getCost())
 		{
-			if(nodeArmyValue == candidateArmyValue
-				&& nodeActor->heroFightingStrength >= candidateActor->heroFightingStrength
-				&& node.getCost() <= candidateNode->getCost())
+			if(vstd::isAlmostEqual(nodeActor->heroFightingStrength, candidateNode.actor->heroFightingStrength)
+				&& vstd::isAlmostEqual(other.getCost(), candidateNode.getCost())
+				&& &other < &candidateNode)
 			{
-				if(vstd::isAlmostEqual(nodeActor->heroFightingStrength, candidateActor->heroFightingStrength)
-					&& vstd::isAlmostEqual(node.getCost(), candidateNode->getCost())
-					&& &node < candidateNode)
-				{
-					continue;
-				}
+				return false;
+			}
 
 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
-				logAi->trace(
-					"Block ineficient move because of stronger hero %s->%s, hero: %s[%X], army %lld, mp diff: %i",
-					source->coord.toString(),
-					candidateNode->coord.toString(),
-					candidateNode->actor->hero->getNameTranslated(),
-					candidateNode->actor->chainMask,
-					candidateNode->actor->armyValue,
-					node.moveRemains - candidateNode->moveRemains);
+			logAi->trace(
+				"Block ineficient move because of stronger hero %s->%s, hero: %s[%X], army %lld, mp diff: %i",
+				source->coord.toString(),
+				candidateNode.coord.toString(),
+				candidateNode.actor->hero->getNameTranslated(),
+				candidateNode.actor->chainMask,
+				candidateNode.actor->armyValue,
+				other.moveRemains - candidateNode.moveRemains);
 #endif
-				return true;
-			}
+			return true;
 		}
 	}
 
@@ -1292,12 +1309,15 @@ bool AINodeStorage::hasBetterChain(
 
 bool AINodeStorage::isTileAccessible(const HeroPtr & hero, const int3 & pos, const EPathfindingLayer layer) const
 {
-	auto chains = nodes.get(pos, layer);
+	auto chains = nodes.get(pos);
 
 	for(const AIPathNode & node : chains)
 	{
-		if(node.action != EPathNodeAction::UNKNOWN 
-			&& node.actor && node.actor->hero == hero.h)
+		if(node.version == AISharedStorage::version
+			&& node.layer == layer
+			&& node.action != EPathNodeAction::UNKNOWN 
+			&& node.actor
+			&& node.actor->hero == hero.h)
 		{
 			return true;
 		}
@@ -1312,11 +1332,16 @@ std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand)
 
 	paths.reserve(AIPathfinding::NUM_CHAINS / 4);
 
-	auto chains = nodes.get(pos, isOnLand ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL);
+	auto layer = isOnLand ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL;
+	auto chains = nodes.get(pos);
 
 	for(const AIPathNode & node : chains)
 	{
-		if(node.action == EPathNodeAction::UNKNOWN || !node.actor || !node.actor->hero)
+		if(node.version != AISharedStorage::version
+			|| node.layer != layer
+			|| node.action == EPathNodeAction::UNKNOWN
+			|| !node.actor
+			|| !node.actor->hero)
 		{
 			continue;
 		}
@@ -1349,33 +1374,29 @@ void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path, int pa
 		if(node->chainOther)
 			fillChainInfo(node->chainOther, path, parentIndex);
 
-		//if(node->actor->hero->visitablePos() != node->coord)
-		{
-			AIPathNodeInfo pathNode;
-
-			pathNode.cost = node->getCost();
-			pathNode.targetHero = node->actor->hero;
-			pathNode.chainMask = node->actor->chainMask;
-			pathNode.specialAction = node->specialAction;
-			pathNode.turns = node->turns;
-			pathNode.danger = node->danger;
-			pathNode.coord = node->coord;
-			pathNode.parentIndex = parentIndex;
-			pathNode.actionIsBlocked = false;
-			pathNode.layer = node->layer;
-
-			if(pathNode.specialAction)
-			{
-				auto targetNode =node->theNodeBefore ?  getAINode(node->theNodeBefore) : node;
+		AIPathNodeInfo pathNode;
 
-				pathNode.actionIsBlocked = !pathNode.specialAction->canAct(targetNode);
-			}
+		pathNode.cost = node->getCost();
+		pathNode.targetHero = node->actor->hero;
+		pathNode.chainMask = node->actor->chainMask;
+		pathNode.specialAction = node->specialAction;
+		pathNode.turns = node->turns;
+		pathNode.danger = node->danger;
+		pathNode.coord = node->coord;
+		pathNode.parentIndex = parentIndex;
+		pathNode.actionIsBlocked = false;
+		pathNode.layer = node->layer;
 
-			parentIndex = path.nodes.size();
+		if(pathNode.specialAction)
+		{
+			auto targetNode =node->theNodeBefore ?  getAINode(node->theNodeBefore) : node;
 
-			path.nodes.push_back(pathNode);
+			pathNode.actionIsBlocked = !pathNode.specialAction->canAct(targetNode);
 		}
-		
+
+		parentIndex = path.nodes.size();
+
+		path.nodes.push_back(pathNode);
 		node = getAINode(node->theNodeBefore);
 	}
 }

+ 97 - 25
AI/Nullkiller/Pathfinding/AINodeStorage.h

@@ -27,8 +27,8 @@ namespace NKAI
 {
 namespace AIPathfinding
 {
-	const int BUCKET_COUNT = 3;
-	const int BUCKET_SIZE = 5;
+	const int BUCKET_COUNT = 5;
+	const int BUCKET_SIZE = 6;
 	const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE;
 	const int CHAIN_MAX_DEPTH = 4;
 }
@@ -49,15 +49,24 @@ struct AIPathNode : public CGPathNode
 	const AIPathNode * chainOther;
 	std::shared_ptr<const SpecialAction> specialAction;
 	const ChainActor * actor;
+	uint64_t version;
 
-	STRONG_INLINE
-	bool blocked() const
+	void addSpecialAction(std::shared_ptr<const SpecialAction> action);
+
+	inline void reset(EPathfindingLayer layer, EPathAccessibility accessibility)
 	{
-		return accessible == EPathAccessibility::NOT_SET
-			|| accessible == EPathAccessibility::BLOCKED;
+		CGPathNode::reset();
+
+		actor = nullptr;
+		danger = 0;
+		manaCost = 0;
+		specialAction.reset();
+		armyLoss = 0;
+		chainOther = nullptr;
+		dayFlags = DayFlags::NONE;
+		this->layer = layer;
+		accessible = accessibility;
 	}
-
-	void addSpecialAction(std::shared_ptr<const SpecialAction> action);
 };
 
 struct AIPathNodeInfo
@@ -133,21 +142,21 @@ enum EHeroChainPass
 
 class AISharedStorage
 {
-	// 1 - layer (air, water, land)
-	// 2-4 - position on map[z][x][y]
-	// 5 - chain (normal, battle, spellcast and combinations)
-	static std::shared_ptr<boost::multi_array<AIPathNode, 5>> shared;
-	std::shared_ptr<boost::multi_array<AIPathNode, 5>> nodes;
+	// 1-3 - position on map[z][x][y]
+	// 4 - chain + layer (normal, battle, spellcast and combinations, water, air)
+	static std::shared_ptr<boost::multi_array<AIPathNode, 4>> shared;
+	std::shared_ptr<boost::multi_array<AIPathNode, 4>> nodes;
 public:
 	static boost::mutex locker;
+	static uint64_t version;
 
 	AISharedStorage(int3 mapSize);
 	~AISharedStorage();
 
 	STRONG_INLINE
-	boost::detail::multi_array::sub_array<AIPathNode, 1> get(int3 tile, EPathfindingLayer layer) const
+	boost::detail::multi_array::sub_array<AIPathNode, 1> get(int3 tile) const
 	{
-		return (*nodes)[layer][tile.z][tile.x][tile.y];
+		return (*nodes)[tile.z][tile.x][tile.y];
 	}
 };
 
@@ -156,6 +165,8 @@ class AINodeStorage : public INodeStorage
 private:
 	int3 sizes;
 
+	std::unique_ptr<boost::multi_array<EPathAccessibility, 4>> accesibility;
+
 	const CPlayerSpecificInfoCallback * cb;
 	const Nullkiller * ai;
 	std::unique_ptr<FuzzyHelper> dangerEvaluator;
@@ -182,8 +193,10 @@ public:
 
 	std::vector<CGPathNode *> getInitialNodes() override;
 
-	virtual std::vector<CGPathNode *> calculateNeighbours(
+	virtual void calculateNeighbours(
+		std::vector<CGPathNode *> & result,
 		const PathNodeInfo & source,
+		EPathfindingLayer layer,
 		const PathfinderConfig * pathfinderConfig,
 		const CPathfinderHelper * pathfinderHelper) override;
 
@@ -222,21 +235,35 @@ public:
 		return aiNode->actor->hero;
 	}
 
-	bool hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const;
-
-	bool isMovementIneficient(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
+	inline bool blocked(const int3 & tile, EPathfindingLayer layer) const
 	{
-		return hasBetterChain(source, destination);
+		EPathAccessibility accessible = getAccessibility(tile, layer);
+
+		return accessible == EPathAccessibility::NOT_SET
+			|| accessible == EPathAccessibility::BLOCKED;
 	}
 
-	bool isDistanceLimitReached(const PathNodeInfo & source, CDestinationNodeInfo & destination) const;
+	bool hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const;
+	bool hasBetterChain(const CGPathNode * source, const AIPathNode & candidateNode) const;
 
 	template<class NodeRange>
 	bool hasBetterChain(
 		const CGPathNode * source, 
-		const AIPathNode * destinationNode,
+		const AIPathNode & destinationNode,
 		const NodeRange & chains) const;
 
+	bool isOtherChainBetter(
+		const CGPathNode * source,
+		const AIPathNode & candidateNode,
+		const AIPathNode & other) const;
+
+	bool isMovementIneficient(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
+	{
+		return hasBetterChain(source, destination);
+	}
+
+	bool isDistanceLimitReached(const PathNodeInfo & source, CDestinationNodeInfo & destination) const;
+
 	std::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 HeroPtr & hero, const int3 & pos, const EPathfindingLayer layer) const;
@@ -263,10 +290,17 @@ public:
 		return (uint64_t)(armyValue * ratio * ratio);
 	}
 
-	STRONG_INLINE
-	void resetTile(const int3 & tile, EPathfindingLayer layer, EPathAccessibility accessibility);
+	inline EPathAccessibility getAccessibility(const int3 & tile, EPathfindingLayer layer) const
+	{
+		return (*this->accesibility)[tile.z][tile.x][tile.y][layer];
+	}
 
-	STRONG_INLINE int getBucket(const ChainActor * actor) const
+	inline void resetTile(const int3 & tile, EPathfindingLayer layer, EPathAccessibility tileAccessibility)
+	{
+		(*this->accesibility)[tile.z][tile.x][tile.y][layer] = tileAccessibility;
+	}
+
+	inline int getBucket(const ChainActor * actor) const
 	{
 		return ((uintptr_t)actor * 395) % AIPathfinding::BUCKET_COUNT;
 	}
@@ -274,6 +308,44 @@ public:
 	void calculateTownPortalTeleportations(std::vector<CGPathNode *> & neighbours);
 	void fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const;
 
+	template<typename Fn>
+	void iterateValidNodes(const int3 & pos, EPathfindingLayer layer, Fn fn)
+	{
+		if(blocked(pos, layer))
+			return;
+
+		auto chains = nodes.get(pos);
+
+		for(AIPathNode & node : chains)
+		{
+			if(node.version != AISharedStorage::version || node.layer != layer)
+				continue;
+
+			fn(node);
+		}
+	}
+
+	template<typename Fn>
+	bool iterateValidNodesUntil(const int3 & pos, EPathfindingLayer layer, Fn predicate) const
+	{
+		if(blocked(pos, layer))
+			return false;
+
+		auto chains = nodes.get(pos);
+
+		for(AIPathNode & node : chains)
+		{
+			if(node.version != AISharedStorage::version || node.layer != layer)
+				continue;
+
+			if(predicate(node))
+				return true;
+		}
+
+		return false;
+	}
+
+
 private:
 	template<class TVector>
 	void calculateTownPortal(

+ 31 - 24
AI/Nullkiller/Pathfinding/ObjectGraph.cpp

@@ -73,6 +73,36 @@ public:
 		removeExtraConnections();
 	}
 
+	float getNeighborConnectionsCost(const int3 & pos)
+	{
+		float neighborCost = std::numeric_limits<float>::max();
+
+		if(NKAI_GRAPH_TRACE_LEVEL >= 2)
+		{
+			logAi->trace("Checking junction %s", pos.toString());
+		}
+
+		foreach_neighbour(
+			ai->cb.get(),
+			pos,
+			[this, &neighborCost](const CPlayerSpecificInfoCallback * cb, const int3 & neighbor)
+			{
+				auto costTotal = this->getConnectionsCost(neighbor);
+
+				if(costTotal.connectionsCount > 2 && costTotal.avg < neighborCost)
+				{
+					neighborCost = costTotal.avg;
+
+					if(NKAI_GRAPH_TRACE_LEVEL >= 2)
+					{
+						logAi->trace("Better node found at %s", neighbor.toString());
+					}
+				}
+			});
+
+		return neighborCost;
+	}
+
 	void addMinimalDistanceJunctions()
 	{
 		foreach_tile_pos(ai->cb.get(), [this](const CPlayerSpecificInfoCallback * cb, const int3 & pos)
@@ -88,30 +118,7 @@ public:
 				if(currentCost.connectionsCount <= 2)
 					return;
 
-				float neighborCost = currentCost.avg + 0.001f;
-
-				if(NKAI_GRAPH_TRACE_LEVEL >= 2)
-				{
-					logAi->trace("Checking junction %s", pos.toString());
-				}
-
-				foreach_neighbour(
-					ai->cb.get(),
-					pos,
-					[this, &neighborCost](const CPlayerSpecificInfoCallback * cb, const int3 & neighbor)
-					{
-						auto costTotal = this->getConnectionsCost(neighbor);
-
-						if(costTotal.avg < neighborCost)
-						{
-							neighborCost = costTotal.avg;
-
-							if(NKAI_GRAPH_TRACE_LEVEL >= 2)
-							{
-								logAi->trace("Better node found at %s", neighbor.toString());
-							}
-						}
-					});
+				float neighborCost = getNeighborConnectionsCost(pos);
 
 				if(currentCost.avg < neighborCost)
 				{

+ 11 - 7
AI/VCAI/Pathfinding/AINodeStorage.cpp

@@ -155,15 +155,21 @@ void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInf
 	});
 }
 
-std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
+void AINodeStorage::calculateNeighbours(
+	std::vector<CGPathNode *> & result,
 	const PathNodeInfo & source,
+	EPathfindingLayer layer,
 	const PathfinderConfig * pathfinderConfig,
 	const CPathfinderHelper * pathfinderHelper)
 {
-	std::vector<CGPathNode *> neighbours;
-	neighbours.reserve(16);
+	std::vector<int3> accessibleNeighbourTiles;
+
+	result.clear();
+	accessibleNeighbourTiles.reserve(8);
+
+	pathfinderHelper->calculateNeighbourTiles(accessibleNeighbourTiles, source);
+
 	const AIPathNode * srcNode = getAINode(source.node);
-	auto accessibleNeighbourTiles = pathfinderHelper->getNeighbourTiles(source);
 
 	for(auto & neighbour : accessibleNeighbourTiles)
 	{
@@ -174,11 +180,9 @@ std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
 			if(!nextNode || nextNode.value()->accessible == EPathAccessibility::NOT_SET)
 				continue;
 
-			neighbours.push_back(nextNode.value());
+			result.push_back(nextNode.value());
 		}
 	}
-
-	return neighbours;
 }
 
 void AINodeStorage::setHero(HeroPtr heroPtr, const VCAI * _ai)

+ 3 - 1
AI/VCAI/Pathfinding/AINodeStorage.h

@@ -89,8 +89,10 @@ public:
 
 	std::vector<CGPathNode *> getInitialNodes() override;
 
-	virtual std::vector<CGPathNode *> calculateNeighbours(
+	virtual void calculateNeighbours(
+		std::vector<CGPathNode *> & result,
 		const PathNodeInfo & source,
+		EPathfindingLayer layer,
 		const PathfinderConfig * pathfinderConfig,
 		const CPathfinderHelper * pathfinderHelper) override;
 

+ 37 - 34
lib/pathfinder/CPathfinder.cpp

@@ -49,30 +49,27 @@ bool CPathfinderHelper::canMoveFromNode(const PathNodeInfo & source) const
 	return true;
 }
 
-std::vector<int3> CPathfinderHelper::getNeighbourTiles(const PathNodeInfo & source) const
+void CPathfinderHelper::calculateNeighbourTiles(std::vector<int3> & result, const PathNodeInfo & source) const
 {
-	std::vector<int3> neighbourTiles;
+	result.clear();
 
 	if (!canMoveFromNode(source))
-		return neighbourTiles;
+		return;
 
-	neighbourTiles.reserve(8);
 	getNeighbours(
 		*source.tile,
 		source.node->coord,
-		neighbourTiles,
+		result,
 		boost::logic::indeterminate,
 		source.node->layer == EPathfindingLayer::SAIL);
 
 	if(source.isNodeObjectVisitable())
 	{
-		vstd::erase_if(neighbourTiles, [&](const int3 & tile) -> bool
+		vstd::erase_if(result, [&](const int3 & tile) -> bool
 		{
 			return !canMoveBetween(tile, source.nodeObject->visitablePos());
 		});
 	}
-
-	return neighbourTiles;
 }
 
 CPathfinder::CPathfinder(CGameState * _gs, std::shared_ptr<PathfinderConfig> config): 
@@ -129,6 +126,8 @@ void CPathfinder::calculatePaths()
 		pq.push(initialNode);
 	}
 
+	std::vector<CGPathNode *> neighbourNodes;
+
 	while(!pq.empty())
 	{
 		counter++;
@@ -158,43 +157,47 @@ void CPathfinder::calculatePaths()
 		source.updateInfo(hlp, gamestate);
 
 		//add accessible neighbouring nodes to the queue
-		auto neighbourNodes = config->nodeStorage->calculateNeighbours(source, config.get(), hlp);
-		for(CGPathNode * neighbour : neighbourNodes)
+		for(EPathfindingLayer layer = EPathfindingLayer::LAND; layer < EPathfindingLayer::NUM_LAYERS; layer.advance(1))
 		{
-			if(neighbour->locked)
+			if(!hlp->isLayerAvailable(layer))
 				continue;
 
-			if(!hlp->isLayerAvailable(neighbour->layer))
-				continue;
+			config->nodeStorage->calculateNeighbours(neighbourNodes, source, layer, config.get(), hlp);
 
-			destination.setNode(gamestate, neighbour);
-			hlp = config->getOrCreatePathfinderHelper(destination, gamestate);
+			for(CGPathNode * neighbour : neighbourNodes)
+			{
+				if(neighbour->locked)
+					continue;
 
-			if(!hlp->isPatrolMovementAllowed(neighbour->coord))
-				continue;
+				destination.setNode(gamestate, neighbour);
+				hlp = config->getOrCreatePathfinderHelper(destination, gamestate);
 
-			/// Check transition without tile accessability rules
-			if(source.node->layer != neighbour->layer && !isLayerTransitionPossible())
-				continue;
+				if(!hlp->isPatrolMovementAllowed(neighbour->coord))
+					continue;
 
-			destination.turn = turn;
-			destination.movementLeft = movement;
-			destination.cost = cost;
-			destination.updateInfo(hlp, gamestate);
-			destination.isGuardianTile = destination.guarded && isDestinationGuardian();
+				/// Check transition without tile accessability rules
+				if(source.node->layer != neighbour->layer && !isLayerTransitionPossible())
+					continue;
 
-			for(const auto & rule : config->rules)
-			{
-				rule->process(source, destination, config.get(), hlp);
+				destination.turn = turn;
+				destination.movementLeft = movement;
+				destination.cost = cost;
+				destination.updateInfo(hlp, gamestate);
+				destination.isGuardianTile = destination.guarded && isDestinationGuardian();
 
-				if(destination.blocked)
-					break;
-			}
+				for(const auto & rule : config->rules)
+				{
+					rule->process(source, destination, config.get(), hlp);
 
-			if(!destination.blocked)
-				push(destination.node);
+					if(destination.blocked)
+						break;
+				}
 
-		} //neighbours loop
+				if(!destination.blocked)
+					push(destination.node);
+
+			} //neighbours loop
+		}
 
 		//just add all passable teleport exits
 		hlp = config->getOrCreatePathfinderHelper(source, gamestate);

+ 1 - 1
lib/pathfinder/CPathfinder.h

@@ -96,7 +96,7 @@ public:
 	bool addTeleportWhirlpool(const CGWhirlpool * obj) const;
 	bool canMoveBetween(const int3 & a, const int3 & b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility)
 
-	std::vector<int3> getNeighbourTiles(const PathNodeInfo & source) const;
+	void calculateNeighbourTiles(std::vector<int3> & result, const PathNodeInfo & source) const;
 	std::vector<int3> getTeleportExits(const PathNodeInfo & source) const;
 
 	void getNeighbours(

+ 3 - 1
lib/pathfinder/INodeStorage.h

@@ -31,8 +31,10 @@ public:
 
 	virtual std::vector<CGPathNode *> getInitialNodes() = 0;
 
-	virtual std::vector<CGPathNode *> calculateNeighbours(
+	virtual void calculateNeighbours(
+		std::vector<CGPathNode *> & result,
 		const PathNodeInfo & source,
+		EPathfindingLayer layer,
 		const PathfinderConfig * pathfinderConfig,
 		const CPathfinderHelper * pathfinderHelper) = 0;
 

+ 13 - 13
lib/pathfinder/NodeStorage.cpp

@@ -60,29 +60,29 @@ void NodeStorage::initialize(const PathfinderOptions & options, const CGameState
 	}
 }
 
-std::vector<CGPathNode *> NodeStorage::calculateNeighbours(
+void NodeStorage::calculateNeighbours(
+	std::vector<CGPathNode *> & result,
 	const PathNodeInfo & source,
+	EPathfindingLayer layer,
 	const PathfinderConfig * pathfinderConfig,
 	const CPathfinderHelper * pathfinderHelper)
 {
-	std::vector<CGPathNode *> neighbours;
-	neighbours.reserve(16);
-	auto accessibleNeighbourTiles = pathfinderHelper->getNeighbourTiles(source);
+	std::vector<int3> accessibleNeighbourTiles;
+	
+	result.clear();
+	accessibleNeighbourTiles.reserve(8);
+	
+	pathfinderHelper->calculateNeighbourTiles(accessibleNeighbourTiles, source);
 
 	for(auto & neighbour : accessibleNeighbourTiles)
 	{
-		for(EPathfindingLayer i = EPathfindingLayer::LAND; i < EPathfindingLayer::NUM_LAYERS; i.advance(1))
-		{
-			auto * node = getNode(neighbour, i);
+		auto * node = getNode(neighbour, layer);
 
-			if(node->accessible == EPathAccessibility::NOT_SET)
-				continue;
+		if(node->accessible == EPathAccessibility::NOT_SET)
+			continue;
 
-			neighbours.push_back(node);
-		}
+		result.push_back(node);
 	}
-
-	return neighbours;
 }
 
 std::vector<CGPathNode *> NodeStorage::calculateTeleportations(

+ 3 - 1
lib/pathfinder/NodeStorage.h

@@ -36,8 +36,10 @@ public:
 
 	std::vector<CGPathNode *> getInitialNodes() override;
 
-	virtual std::vector<CGPathNode *> calculateNeighbours(
+	virtual void calculateNeighbours(
+		std::vector<CGPathNode *> & result,
 		const PathNodeInfo & source,
+		EPathfindingLayer layer,
 		const PathfinderConfig * pathfinderConfig,
 		const CPathfinderHelper * pathfinderHelper) override;