|  | @@ -23,23 +23,17 @@ std::shared_ptr<boost::multi_array<AIPathNode, 5>> AISharedStorage::shared;
 | 
	
		
			
				|  |  |  std::set<int3> commitedTiles;
 | 
	
		
			
				|  |  |  std::set<int3> commitedTilesInitial;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -#ifdef ENVIRONMENT64
 | 
	
		
			
				|  |  | -const int BUCKET_COUNT = 11;
 | 
	
		
			
				|  |  | -#else
 | 
	
		
			
				|  |  | -const int BUCKET_COUNT = 7;
 | 
	
		
			
				|  |  | -#endif // ENVIRONMENT64
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  const uint64_t FirstActorMask = 1;
 | 
	
		
			
				|  |  | -const int BUCKET_SIZE = GameConstants::MAX_HEROES_PER_PLAYER;
 | 
	
		
			
				|  |  | -const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE;
 | 
	
		
			
				|  |  |  const uint64_t MIN_ARMY_STRENGTH_FOR_CHAIN = 5000;
 | 
	
		
			
				|  |  |  const uint64_t MIN_ARMY_STRENGTH_FOR_NEXT_ACTOR = 1000;
 | 
	
		
			
				|  |  | +const uint64_t CHAIN_MAX_DEPTH = 4;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  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][NUM_CHAINS]));
 | 
	
		
			
				|  |  | +			boost::extents[EPathfindingLayer::NUM_LAYERS][sizes.z][sizes.x][sizes.y][AIPathfinding::NUM_CHAINS]));
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	nodes = shared;
 | 
	
	
		
			
				|  | @@ -139,16 +133,16 @@ boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
 | 
	
		
			
				|  |  |  	const EPathfindingLayer layer, 
 | 
	
		
			
				|  |  |  	const ChainActor * actor)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  | -	int bucketIndex = ((uintptr_t)actor) % BUCKET_COUNT;
 | 
	
		
			
				|  |  | -	int bucketOffset = bucketIndex * BUCKET_SIZE;
 | 
	
		
			
				|  |  | -	auto chains = nodes.get(pos, layer); //FIXME: chain was the innermost layer
 | 
	
		
			
				|  |  | +	int bucketIndex = ((uintptr_t)actor) % AIPathfinding::BUCKET_COUNT;
 | 
	
		
			
				|  |  | +	int bucketOffset = bucketIndex * AIPathfinding::BUCKET_SIZE;
 | 
	
		
			
				|  |  | +	auto chains = nodes.get(pos, layer);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	if(chains[0].blocked())
 | 
	
		
			
				|  |  |  	{
 | 
	
		
			
				|  |  |  		return boost::none;
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	for(auto i = BUCKET_SIZE - 1; i >= 0; i--)
 | 
	
		
			
				|  |  | +	for(auto i = AIPathfinding::BUCKET_SIZE - 1; i >= 0; i--)
 | 
	
		
			
				|  |  |  	{
 | 
	
		
			
				|  |  |  		AIPathNode & node = chains[i + bucketOffset];
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -171,8 +165,9 @@ boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
 | 
	
		
			
				|  |  |  std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |  	if(heroChainPass)
 | 
	
		
			
				|  |  | -{
 | 
	
		
			
				|  |  | -		calculateTownPortalTeleportations(heroChain);
 | 
	
		
			
				|  |  | +	{
 | 
	
		
			
				|  |  | +		if(heroChainTurn == 0)
 | 
	
		
			
				|  |  | +			calculateTownPortalTeleportations(heroChain);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  		return heroChain;
 | 
	
		
			
				|  |  |  	}
 | 
	
	
		
			
				|  | @@ -207,7 +202,8 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	calculateTownPortalTeleportations(initialNodes);
 | 
	
		
			
				|  |  | +	if(heroChainTurn == 0)
 | 
	
		
			
				|  |  | +		calculateTownPortalTeleportations(initialNodes);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	return initialNodes;
 | 
	
		
			
				|  |  |  }
 | 
	
	
		
			
				|  | @@ -406,8 +402,8 @@ public:
 | 
	
		
			
				|  |  |  		AINodeStorage & storage, AISharedStorage & nodes, const std::vector<int3> & tiles, uint64_t chainMask, int heroChainTurn)
 | 
	
		
			
				|  |  |  		:existingChains(), newChains(), delayedWork(), nodes(nodes), storage(storage), chainMask(chainMask), heroChainTurn(heroChainTurn), heroChain(), tiles(tiles)
 | 
	
		
			
				|  |  |  	{
 | 
	
		
			
				|  |  | -		existingChains.reserve(NUM_CHAINS);
 | 
	
		
			
				|  |  | -		newChains.reserve(NUM_CHAINS);
 | 
	
		
			
				|  |  | +		existingChains.reserve(AIPathfinding::NUM_CHAINS);
 | 
	
		
			
				|  |  | +		newChains.reserve(AIPathfinding::NUM_CHAINS);
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	void execute(const blocked_range<size_t>& r)
 | 
	
	
		
			
				|  | @@ -623,6 +619,9 @@ void HeroChainCalculationTask::calculateHeroChain(
 | 
	
		
			
				|  |  |  		if((node->actor->chainMask & chainMask) == 0 && (srcNode->actor->chainMask & chainMask) == 0)
 | 
	
		
			
				|  |  |  			continue;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +		if(node->actor->actorExchangeCount + srcNode->actor->actorExchangeCount > CHAIN_MAX_DEPTH)
 | 
	
		
			
				|  |  | +			continue;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  		if(node->action == CGPathNode::ENodeAction::BATTLE
 | 
	
		
			
				|  |  |  			|| node->action == CGPathNode::ENodeAction::TELEPORT_BATTLE
 | 
	
		
			
				|  |  |  			|| node->action == CGPathNode::ENodeAction::TELEPORT_NORMAL
 | 
	
	
		
			
				|  | @@ -994,8 +993,6 @@ struct TowmPortalFinder
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	CGPathNode * getBestInitialNodeForTownPortal(const CGTownInstance * targetTown)
 | 
	
		
			
				|  |  |  	{
 | 
	
		
			
				|  |  | -		CGPathNode * bestNode = nullptr;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  		for(CGPathNode * node : initialNodes)
 | 
	
		
			
				|  |  |  		{
 | 
	
		
			
				|  |  |  			auto aiNode = nodeStorage->getAINode(node);
 | 
	
	
		
			
				|  | @@ -1018,11 +1015,10 @@ struct TowmPortalFinder
 | 
	
		
			
				|  |  |  					continue;
 | 
	
		
			
				|  |  |  			}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -			if(!bestNode || bestNode->getCost() > node->getCost())
 | 
	
		
			
				|  |  | -				bestNode = node;
 | 
	
		
			
				|  |  | +			return node;
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		return bestNode;
 | 
	
		
			
				|  |  | +		return nullptr;
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	boost::optional<AIPathNode *> createTownPortalNode(const CGTownInstance * targetTown)
 | 
	
	
		
			
				|  | @@ -1060,6 +1056,55 @@ struct TowmPortalFinder
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +template<class TVector>
 | 
	
		
			
				|  |  | +void AINodeStorage::calculateTownPortal(
 | 
	
		
			
				|  |  | +	const ChainActor * actor,
 | 
	
		
			
				|  |  | +	const std::map<const CGHeroInstance *, int> & maskMap,
 | 
	
		
			
				|  |  | +	const std::vector<CGPathNode *> & initialNodes,
 | 
	
		
			
				|  |  | +	TVector & output)
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +	auto towns = cb->getTownsInfo(false);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool
 | 
	
		
			
				|  |  | +		{
 | 
	
		
			
				|  |  | +			return cb->getPlayerRelations(actor->hero->tempOwner, t->tempOwner) == PlayerRelations::ENEMIES;
 | 
	
		
			
				|  |  | +		});
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	if(!towns.size())
 | 
	
		
			
				|  |  | +	{
 | 
	
		
			
				|  |  | +		return; // no towns no need to run loop further
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	TowmPortalFinder townPortalFinder(actor, initialNodes, towns, this);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	if(townPortalFinder.actorCanCastTownPortal())
 | 
	
		
			
				|  |  | +	{
 | 
	
		
			
				|  |  | +		for(const CGTownInstance * targetTown : towns)
 | 
	
		
			
				|  |  | +		{
 | 
	
		
			
				|  |  | +			// TODO: allow to hide visiting hero in garrison
 | 
	
		
			
				|  |  | +			if(targetTown->visitingHero)
 | 
	
		
			
				|  |  | +			{
 | 
	
		
			
				|  |  | +				auto basicMask = maskMap.at(targetTown->visitingHero.get());
 | 
	
		
			
				|  |  | +				bool heroIsInChain = (actor->chainMask & basicMask) != 0;
 | 
	
		
			
				|  |  | +				bool sameActorInTown = actor->chainMask == basicMask;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				if(sameActorInTown || !heroIsInChain)
 | 
	
		
			
				|  |  | +					continue;
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			auto nodeOptional = townPortalFinder.createTownPortalNode(targetTown);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			if(nodeOptional)
 | 
	
		
			
				|  |  | +			{
 | 
	
		
			
				|  |  | +#if PATHFINDER_TRACE_LEVEL >= 1
 | 
	
		
			
				|  |  | +				logAi->trace("Adding town portal node at %s", targetTown->name);
 | 
	
		
			
				|  |  | +#endif
 | 
	
		
			
				|  |  | +				output.push_back(nodeOptional.get());
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  void AINodeStorage::calculateTownPortalTeleportations(std::vector<CGPathNode *> & initialNodes)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |  	std::set<const ChainActor *> actorsOfInitial;
 | 
	
	
		
			
				|  | @@ -1068,7 +1113,8 @@ void AINodeStorage::calculateTownPortalTeleportations(std::vector<CGPathNode *>
 | 
	
		
			
				|  |  |  	{
 | 
	
		
			
				|  |  |  		auto aiNode = getAINode(node);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		actorsOfInitial.insert(aiNode->actor->baseActor);
 | 
	
		
			
				|  |  | +		if(aiNode->actor->hero)
 | 
	
		
			
				|  |  | +			actorsOfInitial.insert(aiNode->actor->baseActor);
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	std::map<const CGHeroInstance *, int> maskMap;
 | 
	
	
		
			
				|  | @@ -1079,50 +1125,28 @@ void AINodeStorage::calculateTownPortalTeleportations(std::vector<CGPathNode *>
 | 
	
		
			
				|  |  |  			maskMap[basicActor->hero] = basicActor->chainMask;
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	for(const ChainActor * actor : actorsOfInitial)
 | 
	
		
			
				|  |  | -	{
 | 
	
		
			
				|  |  | -		if(!actor->hero)
 | 
	
		
			
				|  |  | -			continue;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		auto towns = cb->getTownsInfo(false);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool
 | 
	
		
			
				|  |  | -		{
 | 
	
		
			
				|  |  | -			return cb->getPlayerRelations(actor->hero->tempOwner, t->tempOwner) == PlayerRelations::ENEMIES;
 | 
	
		
			
				|  |  | -		});
 | 
	
		
			
				|  |  | +	boost::sort(initialNodes, NodeComparer<CGPathNode>());
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		if(!towns.size())
 | 
	
		
			
				|  |  | -		{
 | 
	
		
			
				|  |  | -			return; // no towns no need to run loop further
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		TowmPortalFinder townPortalFinder(actor, initialNodes, towns, this);
 | 
	
		
			
				|  |  | +	std::vector<const ChainActor *> actorsVector(actorsOfInitial.begin(), actorsOfInitial.end());
 | 
	
		
			
				|  |  | +	tbb::concurrent_vector<CGPathNode *> output;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		if(townPortalFinder.actorCanCastTownPortal())
 | 
	
		
			
				|  |  | -		{
 | 
	
		
			
				|  |  | -			for(const CGTownInstance * targetTown : towns)
 | 
	
		
			
				|  |  | +	if(actorsVector.size() * initialNodes.size() > 1000)
 | 
	
		
			
				|  |  | +	{
 | 
	
		
			
				|  |  | +		parallel_for(blocked_range<size_t>(0, actorsVector.size()), [&](const blocked_range<size_t> & r)
 | 
	
		
			
				|  |  |  			{
 | 
	
		
			
				|  |  | -				// TODO: allow to hide visiting hero in garrison
 | 
	
		
			
				|  |  | -				if(targetTown->visitingHero)
 | 
	
		
			
				|  |  | +				for(int i = r.begin(); i != r.end(); i++)
 | 
	
		
			
				|  |  |  				{
 | 
	
		
			
				|  |  | -					auto basicMask = maskMap[targetTown->visitingHero.get()];
 | 
	
		
			
				|  |  | -					bool heroIsInChain = (actor->chainMask & basicMask) != 0;
 | 
	
		
			
				|  |  | -					bool sameActorInTown = actor->chainMask == basicMask;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -					if(sameActorInTown || !heroIsInChain)
 | 
	
		
			
				|  |  | -						continue;
 | 
	
		
			
				|  |  | +					calculateTownPortal(actorsVector[i], maskMap, initialNodes, output);
 | 
	
		
			
				|  |  |  				}
 | 
	
		
			
				|  |  | +			});
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -				auto nodeOptional = townPortalFinder.createTownPortalNode(targetTown);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -				if(nodeOptional)
 | 
	
		
			
				|  |  | -				{
 | 
	
		
			
				|  |  | -#if PATHFINDER_TRACE_LEVEL >= 1
 | 
	
		
			
				|  |  | -					logAi->trace("Adding town portal node at %s", targetTown->name);
 | 
	
		
			
				|  |  | -#endif
 | 
	
		
			
				|  |  | -					initialNodes.push_back(nodeOptional.get());
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | +		std::copy(output.begin(), output.end(), std::back_inserter(initialNodes));
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +	else
 | 
	
		
			
				|  |  | +	{
 | 
	
		
			
				|  |  | +		for(auto actor : actorsVector)
 | 
	
		
			
				|  |  | +		{
 | 
	
		
			
				|  |  | +			calculateTownPortal(actor, maskMap, initialNodes, initialNodes);
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  }
 | 
	
	
		
			
				|  | @@ -1206,7 +1230,7 @@ bool AINodeStorage::hasBetterChain(
 | 
	
		
			
				|  |  |  					continue;
 | 
	
		
			
				|  |  |  				}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -#if AI_TRACE_LEVEL >= 2
 | 
	
		
			
				|  |  | +#if 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(),
 | 
	
	
		
			
				|  | @@ -1244,7 +1268,7 @@ std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |  	std::vector<AIPath> paths;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	paths.reserve(NUM_CHAINS / 4);
 | 
	
		
			
				|  |  | +	paths.reserve(AIPathfinding::NUM_CHAINS / 4);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	auto chains = nodes.get(pos, isOnLand ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL);
 | 
	
		
			
				|  |  |  
 |