|  | @@ -120,7 +120,11 @@ boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
 | 
	
		
			
				|  |  |  std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |  	if(heroChainPass)
 | 
	
		
			
				|  |  | +	{
 | 
	
		
			
				|  |  | +		calculateTownPortalTeleportations(heroChain);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  		return heroChain;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	std::vector<CGPathNode *> initialNodes;
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -147,6 +151,8 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +	calculateTownPortalTeleportations(initialNodes);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  	return initialNodes;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -185,7 +191,7 @@ void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInf
 | 
	
		
			
				|  |  |  			dstNode->specialAction->applyOnDestination(dstNode->actor->hero, destination, source, dstNode, srcNode);
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -#if VCMI_TRACE_PATHFINDER >= 2
 | 
	
		
			
				|  |  | +#if AI_TRACE_LEVEL >= 2
 | 
	
		
			
				|  |  |  		logAi->trace(
 | 
	
		
			
				|  |  |  			"Commited %s -> %s, cost: %f, hero: %s, mask: %x, army: %i", 
 | 
	
		
			
				|  |  |  			source.coord.toString(),
 | 
	
	
		
			
				|  | @@ -310,7 +316,7 @@ void AINodeStorage::calculateHeroChain(
 | 
	
		
			
				|  |  |  			continue;
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -#if VCMI_TRACE_PATHFINDER >= 2
 | 
	
		
			
				|  |  | +#if AI_TRACE_LEVEL >= 2
 | 
	
		
			
				|  |  |  		logAi->trace(
 | 
	
		
			
				|  |  |  			"Thy exchange %s[%i] -> %s[%i] at %s",
 | 
	
		
			
				|  |  |  			node->actor->toString(),
 | 
	
	
		
			
				|  | @@ -335,7 +341,7 @@ void AINodeStorage::calculateHeroChain(
 | 
	
		
			
				|  |  |  		&& other->armyLoss < other->actor->armyValue
 | 
	
		
			
				|  |  |  		&& carrier->actor->canExchange(other->actor))
 | 
	
		
			
				|  |  |  	{
 | 
	
		
			
				|  |  | -#if VCMI_TRACE_PATHFINDER >= 2
 | 
	
		
			
				|  |  | +#if AI_TRACE_LEVEL >= 2
 | 
	
		
			
				|  |  |  		logAi->trace(
 | 
	
		
			
				|  |  |  			"Exchange allowed %s[%i] -> %s[%i] at %s",
 | 
	
		
			
				|  |  |  			other->actor->toString(),
 | 
	
	
		
			
				|  | @@ -352,7 +358,7 @@ void AINodeStorage::calculateHeroChain(
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  			if(hasLessMp && hasLessExperience)
 | 
	
		
			
				|  |  |  			{
 | 
	
		
			
				|  |  | -#if VCMI_TRACE_PATHFINDER >= 2
 | 
	
		
			
				|  |  | +#if AI_TRACE_LEVEL >= 2
 | 
	
		
			
				|  |  |  				logAi->trace("Exchange at %s is ineficient. Blocked.", carrier->coord.toString());
 | 
	
		
			
				|  |  |  #endif
 | 
	
		
			
				|  |  |  				return;
 | 
	
	
		
			
				|  | @@ -376,7 +382,7 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  		if(!chainNodeOptional)
 | 
	
		
			
				|  |  |  		{
 | 
	
		
			
				|  |  | -#if VCMI_TRACE_PATHFINDER >= 2
 | 
	
		
			
				|  |  | +#if AI_TRACE_LEVEL >= 2
 | 
	
		
			
				|  |  |  			logAi->trace("Exchange at %s can not allocate node. Blocked.", carrier->coord.toString());
 | 
	
		
			
				|  |  |  #endif
 | 
	
		
			
				|  |  |  			continue;
 | 
	
	
		
			
				|  | @@ -386,7 +392,7 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  		if(exchangeNode->action != CGPathNode::ENodeAction::UNKNOWN)
 | 
	
		
			
				|  |  |  		{
 | 
	
		
			
				|  |  | -#if VCMI_TRACE_PATHFINDER >= 2
 | 
	
		
			
				|  |  | +#if AI_TRACE_LEVEL >= 2
 | 
	
		
			
				|  |  |  			logAi->trace("Exchange at %s node is already in use. Blocked.", carrier->coord.toString());
 | 
	
		
			
				|  |  |  #endif
 | 
	
		
			
				|  |  |  			continue;
 | 
	
	
		
			
				|  | @@ -394,7 +400,7 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
 | 
	
		
			
				|  |  |  		
 | 
	
		
			
				|  |  |  		if(exchangeNode->turns != 0xFF && exchangeNode->cost < chainInfo.cost)
 | 
	
		
			
				|  |  |  		{
 | 
	
		
			
				|  |  | -#if VCMI_TRACE_PATHFINDER >= 2
 | 
	
		
			
				|  |  | +#if AI_TRACE_LEVEL >= 2
 | 
	
		
			
				|  |  |  			logAi->trace(
 | 
	
		
			
				|  |  |  				"Exchange at %s is is not effective enough. %f < %f", 
 | 
	
		
			
				|  |  |  				exchangeNode->coord.toString(), 
 | 
	
	
		
			
				|  | @@ -406,10 +412,16 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  		commit(exchangeNode, carrier, carrier->action, chainInfo.turns, chainInfo.moveRemains, chainInfo.cost);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +		if(carrier->specialAction || carrier->chainOther)
 | 
	
		
			
				|  |  | +		{
 | 
	
		
			
				|  |  | +			// there is some action on source tile which should be performed before we can bypass it
 | 
	
		
			
				|  |  | +			exchangeNode->theNodeBefore = carrier;
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  		exchangeNode->chainOther = other;
 | 
	
		
			
				|  |  |  		exchangeNode->armyLoss = chainInfo.armyLoss;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -#if VCMI_TRACE_PATHFINDER >= 2
 | 
	
		
			
				|  |  | +#if AI_TRACE_LEVEL >= 2
 | 
	
		
			
				|  |  |  		logAi->trace(
 | 
	
		
			
				|  |  |  			"Chain accepted at %s %s -> %s, mask %x, cost %f, army %i", 
 | 
	
		
			
				|  |  |  			exchangeNode->coord.toString(), 
 | 
	
	
		
			
				|  | @@ -566,85 +578,120 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	if(source.isInitialPosition)
 | 
	
		
			
				|  |  | -	{
 | 
	
		
			
				|  |  | -		calculateTownPortalTeleportations(source, neighbours, pathfinderHelper);
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  	return neighbours;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -void AINodeStorage::calculateTownPortalTeleportations(
 | 
	
		
			
				|  |  | -	const PathNodeInfo & source,
 | 
	
		
			
				|  |  | -	std::vector<CGPathNode *> & neighbours,
 | 
	
		
			
				|  |  | -	const CPathfinderHelper * pathfinderHelper)
 | 
	
		
			
				|  |  | +void AINodeStorage::getBestInitialNodeForTownPortal()
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +void AINodeStorage::calculateTownPortalTeleportations(std::vector<CGPathNode *> & initialNodes)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |  	SpellID spellID = SpellID::TOWN_PORTAL;
 | 
	
		
			
				|  |  |  	const CSpell * townPortal = spellID.toSpell();
 | 
	
		
			
				|  |  | -	auto srcNode = getAINode(source.node);
 | 
	
		
			
				|  |  | -	auto hero = srcNode->actor->hero;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	if(hero->canCastThisSpell(townPortal) && hero->mana >= hero->getSpellCost(townPortal))
 | 
	
		
			
				|  |  | -	{
 | 
	
		
			
				|  |  | -		auto towns = cb->getTownsInfo(false);
 | 
	
		
			
				|  |  | +	std::set<const ChainActor *> actorsOfInitial;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool
 | 
	
		
			
				|  |  | -		{
 | 
	
		
			
				|  |  | -			return cb->getPlayerRelations(hero->tempOwner, t->tempOwner) == PlayerRelations::ENEMIES;
 | 
	
		
			
				|  |  | -		});
 | 
	
		
			
				|  |  | +	for(const CGPathNode * node : initialNodes)
 | 
	
		
			
				|  |  | +	{
 | 
	
		
			
				|  |  | +		auto aiNode = getAINode(node);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		if(!towns.size())
 | 
	
		
			
				|  |  | -		{
 | 
	
		
			
				|  |  | -			return;
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | +		actorsOfInitial.insert(aiNode->actor->baseActor);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		// TODO: Copy/Paste from TownPortalMechanics
 | 
	
		
			
				|  |  | -		auto skillLevel = hero->getSpellSchoolLevel(townPortal);
 | 
	
		
			
				|  |  | -		auto movementNeeded = GameConstants::BASE_MOVEMENT_COST * (skillLevel >= 3 ? 2 : 3);
 | 
	
		
			
				|  |  | -		float movementCost = (float)movementNeeded / (float)pathfinderHelper->getMaxMovePoints(EPathfindingLayer::LAND);
 | 
	
		
			
				|  |  | +	for(const ChainActor * actor : actorsOfInitial)
 | 
	
		
			
				|  |  | +	{
 | 
	
		
			
				|  |  | +		if(!actor->hero)
 | 
	
		
			
				|  |  | +			continue;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		movementCost += source.node->cost;
 | 
	
		
			
				|  |  | +		auto hero = actor->hero;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		if(source.node->moveRemains < movementNeeded)
 | 
	
		
			
				|  |  | +		if(hero->canCastThisSpell(townPortal) && hero->mana >= hero->getSpellCost(townPortal))
 | 
	
		
			
				|  |  |  		{
 | 
	
		
			
				|  |  | -			return;
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | +			auto towns = cb->getTownsInfo(false);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		if(skillLevel < SecSkillLevel::ADVANCED)
 | 
	
		
			
				|  |  | -		{
 | 
	
		
			
				|  |  | -			const CGTownInstance * nearestTown = *vstd::minElementByFun(towns, [&](const CGTownInstance * t) -> int
 | 
	
		
			
				|  |  | +			vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool
 | 
	
		
			
				|  |  |  			{
 | 
	
		
			
				|  |  | -				return source.coord.dist2dSQ(t->visitablePos());
 | 
	
		
			
				|  |  | +				return cb->getPlayerRelations(hero->tempOwner, t->tempOwner) == PlayerRelations::ENEMIES;
 | 
	
		
			
				|  |  |  			});
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -			towns = std::vector<const CGTownInstance *>{ nearestTown };
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		for(const CGTownInstance * targetTown : towns)
 | 
	
		
			
				|  |  | -		{
 | 
	
		
			
				|  |  | -			// TODO: allow to hide visiting hero in garrison
 | 
	
		
			
				|  |  | -			if(targetTown->visitingHero)
 | 
	
		
			
				|  |  | -				continue;
 | 
	
		
			
				|  |  | +			if(!towns.size())
 | 
	
		
			
				|  |  | +			{
 | 
	
		
			
				|  |  | +				return; // no towns no need to run loop further
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -			auto nodeOptional = getOrCreateNode(targetTown->visitablePos(), EPathfindingLayer::LAND, srcNode->actor->castActor);
 | 
	
		
			
				|  |  | +			// TODO: Copy/Paste from TownPortalMechanics
 | 
	
		
			
				|  |  | +			auto skillLevel = hero->getSpellSchoolLevel(townPortal);
 | 
	
		
			
				|  |  | +			auto movementNeeded = GameConstants::BASE_MOVEMENT_COST * (skillLevel >= 3 ? 2 : 3);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -			if(nodeOptional)
 | 
	
		
			
				|  |  | +			for(const CGTownInstance * targetTown : towns)
 | 
	
		
			
				|  |  |  			{
 | 
	
		
			
				|  |  | -#ifdef VCMI_TRACE_PATHFINDER
 | 
	
		
			
				|  |  | -				logAi->trace("Adding town portal node at %s", targetTown->name);
 | 
	
		
			
				|  |  | -#endif
 | 
	
		
			
				|  |  | +				CGPathNode * bestNode = nullptr;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -				AIPathNode * node = nodeOptional.get();
 | 
	
		
			
				|  |  | +				for(CGPathNode * node : initialNodes)
 | 
	
		
			
				|  |  | +				{
 | 
	
		
			
				|  |  | +					auto aiNode = getAINode(node);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					if(aiNode->actor->baseActor != actor
 | 
	
		
			
				|  |  | +						|| node->layer != EPathfindingLayer::LAND
 | 
	
		
			
				|  |  | +						|| node->moveRemains < movementNeeded)
 | 
	
		
			
				|  |  | +					{
 | 
	
		
			
				|  |  | +						continue;
 | 
	
		
			
				|  |  | +					}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					if(skillLevel < SecSkillLevel::ADVANCED)
 | 
	
		
			
				|  |  | +					{
 | 
	
		
			
				|  |  | +						const CGTownInstance * nearestTown = *vstd::minElementByFun(towns, [&](const CGTownInstance * t) -> int
 | 
	
		
			
				|  |  | +						{
 | 
	
		
			
				|  |  | +							return node->coord.dist2dSQ(t->visitablePos());
 | 
	
		
			
				|  |  | +						});
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +						if(targetTown != nearestTown)
 | 
	
		
			
				|  |  | +							continue;
 | 
	
		
			
				|  |  | +					}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					if(!bestNode || bestNode->cost > node->cost)
 | 
	
		
			
				|  |  | +						bestNode = node;
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				if(!bestNode)
 | 
	
		
			
				|  |  | +					continue;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -				if(node->action == CGPathNode::UNKNOWN || node->cost > movementCost)
 | 
	
		
			
				|  |  | +				// TODO: allow to hide visiting hero in garrison
 | 
	
		
			
				|  |  | +				if(targetTown->visitingHero && targetTown->visitingHero != hero)
 | 
	
		
			
				|  |  | +					continue;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				auto nodeOptional = getOrCreateNode(targetTown->visitablePos(), EPathfindingLayer::LAND, actor->castActor);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				if(nodeOptional)
 | 
	
		
			
				|  |  |  				{
 | 
	
		
			
				|  |  | -					node->theNodeBefore = source.node;
 | 
	
		
			
				|  |  | -					node->specialAction.reset(new AIPathfinding::TownPortalAction(targetTown));
 | 
	
		
			
				|  |  | -					node->moveRemains = source.node->moveRemains + movementNeeded;
 | 
	
		
			
				|  |  | -					node->cost = movementCost;
 | 
	
		
			
				|  |  | +					float movementCost = (float)movementNeeded / (float)hero->maxMovePoints(EPathfindingLayer::LAND);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					movementCost += bestNode->cost;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#ifdef AI_TRACE_LEVEL >= 1
 | 
	
		
			
				|  |  | +					logAi->trace("Adding town portal node at %s", targetTown->name);
 | 
	
		
			
				|  |  | +#endif
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					AIPathNode * node = nodeOptional.get();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					if(node->action == CGPathNode::UNKNOWN || node->cost > movementCost)
 | 
	
		
			
				|  |  | +					{
 | 
	
		
			
				|  |  | +						commit(
 | 
	
		
			
				|  |  | +							node,
 | 
	
		
			
				|  |  | +							getAINode(bestNode),
 | 
	
		
			
				|  |  | +							CGPathNode::TELEPORT_NORMAL,
 | 
	
		
			
				|  |  | +							bestNode->turns,
 | 
	
		
			
				|  |  | +							bestNode->moveRemains - movementNeeded,
 | 
	
		
			
				|  |  | +							movementCost);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +						node->theNodeBefore = bestNode;
 | 
	
		
			
				|  |  | +						node->specialAction.reset(new AIPathfinding::TownPortalAction(targetTown));
 | 
	
		
			
				|  |  | +					}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					initialNodes.push_back(node);
 | 
	
		
			
				|  |  |  				}
 | 
	
		
			
				|  |  | -				
 | 
	
		
			
				|  |  | -				neighbours.push_back(node);
 | 
	
		
			
				|  |  |  			}
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  	}
 | 
	
	
		
			
				|  | @@ -679,7 +726,7 @@ bool AINodeStorage::hasBetterChain(
 | 
	
		
			
				|  |  |  		{
 | 
	
		
			
				|  |  |  			if(node.cost < candidateNode->cost)
 | 
	
		
			
				|  |  |  			{
 | 
	
		
			
				|  |  | -#ifdef VCMI_TRACE_PATHFINDER
 | 
	
		
			
				|  |  | +#ifdef AI_TRACE_LEVEL >= 1
 | 
	
		
			
				|  |  |  				logAi->trace(
 | 
	
		
			
				|  |  |  					"Block ineficient move %s:->%s, mask=%i, mp diff: %i",
 | 
	
		
			
				|  |  |  					source->coord.toString(),
 |