瀏覽代碼

NK: fix AI speed

Andrii Danylchenko 3 年之前
父節點
當前提交
2434fe3f6f

+ 2 - 2
AI/Nullkiller/AIGateway.cpp

@@ -766,10 +766,10 @@ void AIGateway::makeTurn()
 		logAi->debug("Making turn thread has been interrupted. We'll end without calling endTurn.");
 		return;
 	}
-	/*catch (std::exception & e)
+	catch (std::exception & e)
 	{
 		logAi->debug("Making turn thread has caught an exception: %s", e.what());
-	}*/
+	}
 
 	endTurn();
 }

+ 12 - 7
AI/Nullkiller/Analyzers/ObjectClusterizer.cpp

@@ -120,18 +120,23 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons
 
 		auto blocker = blockers.front();
 
+		if(isObjectPassable(ai, blocker))
+			continue;
+
 		if(blocker->ID == Obj::GARRISON
-			|| blocker->ID == Obj::MONSTER
-			|| blocker->ID == Obj::GARRISON2
-			|| blocker->ID == Obj::BORDERGUARD
-			|| blocker->ID == Obj::BORDER_GATE
-			|| blocker->ID == Obj::SHIPYARD)
+			|| blocker->ID == Obj::GARRISON2)
 		{
-			if(!isObjectPassable(ai, blocker))
+			if(dynamic_cast<const CArmedInstance *>(blocker)->getArmyStrength() == 0)
+				continue;
+			else
 				return blocker;
 		}
 
-		if(blocker->ID == Obj::QUEST_GUARD && node->actionIsBlocked)
+		if(blocker->ID == Obj::MONSTER
+			|| blocker->ID == Obj::BORDERGUARD
+			|| blocker->ID == Obj::BORDER_GATE
+			|| blocker->ID == Obj::SHIPYARD
+			|| (blocker->ID == Obj::QUEST_GUARD && node->actionIsBlocked))
 		{
 			return blocker;
 		}

+ 20 - 8
AI/Nullkiller/Behaviors/DefenceBehavior.cpp

@@ -47,14 +47,14 @@ Goals::TGoalVec DefenceBehavior::decompose() const
 
 void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInstance * town) const
 {
-	logAi->debug("Evaluating defence for %s", town->name);
+	logAi->trace("Evaluating defence for %s", town->name);
 
 	auto treatNode = ai->nullkiller->dangerHitMap->getObjectTreat(town);
 	auto treats = { treatNode.fastestDanger, treatNode.maximumDanger };
 
 	if(!treatNode.fastestDanger.hero)
 	{
-		logAi->debug("No treat found for town %s", town->name);
+		logAi->trace("No treat found for town %s", town->name);
 
 		return;
 	}
@@ -73,7 +73,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 			return;
 		}
 
-		logAi->debug(
+		logAi->trace(
 			"Hero %s in garrison of town %s is suposed to defend the town",
 			town->garrisonHero->name,
 			town->name);
@@ -85,7 +85,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 
 	if(reinforcement)
 	{
-		logAi->debug("Town %s can buy defence army %lld", town->name, reinforcement);
+		logAi->trace("Town %s can buy defence army %lld", town->name, reinforcement);
 		tasks.push_back(Goals::sptr(Goals::BuyArmy(town, reinforcement).setpriority(0.5f)));
 	}
 
@@ -93,7 +93,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 
 	for(auto & treat : treats)
 	{
-		logAi->debug(
+		logAi->trace(
 			"Town %s has treat %lld in %s turns, hero: %s",
 			town->name,
 			treat.danger,
@@ -104,6 +104,12 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 
 		for(AIPath & path : paths)
 		{
+			if(town->visitingHero && path.targetHero != town->visitingHero.get())
+				continue;
+
+			if(town->visitingHero && path.getHeroStrength() < town->visitingHero->getHeroStrength())
+				continue;
+
 			if(path.getHeroStrength() > treat.danger)
 			{
 				if((path.turn() <= treat.turn && dayOfWeek + treat.turn < 6 && isSafeToVisit(path.targetHero, path.heroArmy, treat.danger))
@@ -111,11 +117,13 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 					|| path.turn() < treat.turn - 1
 					|| (path.turn() < treat.turn && treat.turn >= 2))
 				{
-					logAi->debug(
+#if AI_TRACE_LEVEL >= 1
+					logAi->trace(
 						"Hero %s can eliminate danger for town %s using path %s.",
 						path.targetHero->name,
 						town->name,
 						path.toString());
+#endif
 
 					treatIsUnderControl = true;
 					break;
@@ -140,7 +148,9 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 
 					if(cb->getHeroesInfo().size() < ALLOWED_ROAMING_HEROES)
 					{
-						logAi->debug("Hero %s can be recruited to defend %s", hero->name, town->name);
+#if AI_TRACE_LEVEL >= 1
+						logAi->trace("Hero %s can be recruited to defend %s", hero->name, town->name);
+#endif
 						tasks.push_back(Goals::sptr(Goals::RecruitHero(town, hero).setpriority(1)));
 						continue;
 					}
@@ -174,7 +184,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 
 		if(paths.empty())
 		{
-			logAi->debug("No ways to defend town %s", town->name);
+			logAi->trace("No ways to defend town %s", town->name);
 
 			continue;
 		}
@@ -197,9 +207,11 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 #endif
 			if(path.turn() <= treat.turn - 2)
 			{
+#if AI_TRACE_LEVEL >= 1
 				logAi->trace("Deffer defence of %s by %s because he has enough time to rich the town next trun",
 					town->name,
 					path.targetHero->name);
+#endif
 
 				defferedPaths[path.targetHero].push_back(i);
 

+ 81 - 49
AI/Nullkiller/Engine/Nullkiller.cpp

@@ -116,9 +116,10 @@ void Nullkiller::resetAiState()
 	playerID = ai->playerID;
 	lockedHeroes.clear();
 	dangerHitMap->reset();
+	useHeroChain = true;
 }
 
-void Nullkiller::updateAiState(int pass)
+void Nullkiller::updateAiState(int pass, bool fast)
 {
 	boost::this_thread::interruption_point();
 
@@ -126,39 +127,42 @@ void Nullkiller::updateAiState(int pass)
 
 	activeHero = nullptr;
 
-	memory->removeInvisibleObjects(cb.get());
+	if(!fast)
+	{
+		memory->removeInvisibleObjects(cb.get());
 
-	dangerHitMap->updateHitMap();
+		dangerHitMap->updateHitMap();
 
-	boost::this_thread::interruption_point();
+		boost::this_thread::interruption_point();
 
-	heroManager->update();
-	logAi->trace("Updating paths");
+		heroManager->update();
+		logAi->trace("Updating paths");
 
-	std::map<const CGHeroInstance *, HeroRole> activeHeroes;
+		std::map<const CGHeroInstance *, HeroRole> activeHeroes;
 
-	for(auto hero : cb->getHeroesInfo())
-	{
-		if(getHeroLockedReason(hero) == HeroLockedReason::DEFENCE)
-			continue;
+		for(auto hero : cb->getHeroesInfo())
+		{
+			if(getHeroLockedReason(hero) == HeroLockedReason::DEFENCE)
+				continue;
 
-		activeHeroes[hero] = heroManager->getHeroRole(hero);
-	}
+			activeHeroes[hero] = heroManager->getHeroRole(hero);
+		}
 
-	PathfinderSettings cfg;
-	cfg.useHeroChain = true;
-	cfg.scoutTurnDistanceLimit = SCOUT_TURN_DISTANCE_LIMIT;
+		PathfinderSettings cfg;
+		cfg.useHeroChain = useHeroChain;
+		cfg.scoutTurnDistanceLimit = SCOUT_TURN_DISTANCE_LIMIT;
 
-	if(scanDepth != ScanDepth::FULL)
-	{
-		cfg.mainTurnDistanceLimit = MAIN_TURN_DISTANCE_LIMIT * ((int)scanDepth + 1);
-	}
+		if(scanDepth != ScanDepth::FULL)
+		{
+			cfg.mainTurnDistanceLimit = MAIN_TURN_DISTANCE_LIMIT * ((int)scanDepth + 1);
+		}
 
-	pathfinder->updatePaths(activeHeroes, cfg);
+		pathfinder->updatePaths(activeHeroes, cfg);
 
-	armyManager->update();
+		objectClusterizer->clusterize();
+	}
 
-	objectClusterizer->clusterize();
+	armyManager->update();
 	buildAnalyzer->update();
 	decomposer->reset();
 
@@ -213,13 +217,30 @@ void Nullkiller::makeTurn()
 	{
 		updateAiState(i);
 
+		Goals::TTask bestTask = taskptr(Goals::Invalid());
+		
+		do
+		{
+			Goals::TTaskVec fastTasks = {
+				choseBestTask(sptr(BuyArmyBehavior()), 1),
+				choseBestTask(sptr(RecruitHeroBehavior()), 1),
+				choseBestTask(sptr(BuildingBehavior()), 1)
+			};
+
+			bestTask = choseBestTask(fastTasks);
+
+			if(bestTask->priority >= 1)
+			{
+				executeTask(bestTask);
+				updateAiState(i, true);
+			}
+		} while(bestTask->priority >= 1);
+
 		Goals::TTaskVec bestTasks = {
-			choseBestTask(sptr(BuyArmyBehavior()), 1),
+			bestTask,
 			choseBestTask(sptr(CaptureObjectsBehavior()), 1),
 			choseBestTask(sptr(ClusterBehavior()), MAX_DEPTH),
-			choseBestTask(sptr(RecruitHeroBehavior()), 1),
 			choseBestTask(sptr(DefenceBehavior()), MAX_DEPTH),
-			choseBestTask(sptr(BuildingBehavior()), 1),
 			choseBestTask(sptr(GatherArmyBehavior()), MAX_DEPTH)
 		};
 
@@ -228,19 +249,25 @@ void Nullkiller::makeTurn()
 			bestTasks.push_back(choseBestTask(sptr(StartupBehavior()), 1));
 		}
 
-		Goals::TTask bestTask = choseBestTask(bestTasks);
+		bestTask = choseBestTask(bestTasks);
+
 		HeroPtr hero = bestTask->getHero();
 
+		HeroRole heroRole = HeroRole::MAIN;
+
+		if(hero.validAndSet())
+			heroRole = heroManager->getHeroRole(hero);
+
+		if(heroRole != HeroRole::MAIN || bestTask->getHeroExchangeCount() <= 1)
+			useHeroChain = false;
+
 		if(bestTask->priority < NEXT_SCAN_MIN_PRIORITY
 			&& scanDepth != ScanDepth::FULL)
 		{
-			HeroRole heroRole = HeroRole::MAIN;
-
-			if(hero.validAndSet())
-				heroRole = heroManager->getHeroRole(hero);
-
 			if(heroRole == HeroRole::MAIN || bestTask->priority < MIN_PRIORITY)
 			{
+				useHeroChain = false;
+
 				logAi->trace(
 					"Goal %s has too low priority %f so increasing scan depth",
 					bestTask->toString(),
@@ -258,26 +285,31 @@ void Nullkiller::makeTurn()
 			return;
 		}
 
-		std::string taskDescr = bestTask->toString();
+		executeTask(bestTask);
+	}
+}
 
-		boost::this_thread::interruption_point();
-		logAi->debug("Trying to realize %s (value %2.3f)", taskDescr, bestTask->priority);
+void Nullkiller::executeTask(Goals::TTask task)
+{
+	std::string taskDescr = task->toString();
 
-		try
-		{
-			bestTask->accept(ai.get());
-		}
-		catch(goalFulfilledException &)
-		{
-			logAi->trace("Task %s completed", bestTask->toString());
-		}
-		catch(std::exception & e)
-		{
-			logAi->debug("Failed to realize subgoal of type %s, I will stop.", taskDescr);
-			logAi->debug("The error message was: %s", e.what());
+	boost::this_thread::interruption_point();
+	logAi->debug("Trying to realize %s (value %2.3f)", taskDescr, task->priority);
 
-			return;
-		}
+	try
+	{
+		task->accept(ai.get());
+	}
+	catch(goalFulfilledException &)
+	{
+		logAi->trace("Task %s completed", task->toString());
+	}
+	catch(std::exception & e)
+	{
+		logAi->debug("Failed to realize subgoal of type %s, I will stop.", taskDescr);
+		logAi->debug("The error message was: %s", e.what());
+
+		throw;
 	}
 }
 

+ 3 - 1
AI/Nullkiller/Engine/Nullkiller.h

@@ -51,6 +51,7 @@ private:
 	std::map<const CGHeroInstance *, HeroLockedReason> lockedHeroes;
 	ScanDepth scanDepth;
 	TResources lockedResources;
+	bool useHeroChain;
 
 public:
 	std::unique_ptr<DangerHitMapAnalyzer> dangerHitMap;
@@ -86,7 +87,8 @@ public:
 
 private:
 	void resetAiState();
-	void updateAiState(int pass);
+	void updateAiState(int pass, bool fast = false);
 	Goals::TTask choseBestTask(Goals::TSubgoal behavior, int decompositionMaxDepth) const;
 	Goals::TTask choseBestTask(Goals::TTaskVec & tasks) const;
+	void executeTask(Goals::TTask task);
 };

+ 1 - 0
AI/Nullkiller/Goals/AbstractGoal.h

@@ -162,6 +162,7 @@ namespace Goals
 		virtual std::string toString() const = 0;
 		virtual HeroPtr getHero() const = 0;
 		virtual ~ITask() {}
+		virtual int getHeroExchangeCount() const = 0;
 	};
 }
 

+ 2 - 0
AI/Nullkiller/Goals/CGoal.h

@@ -90,6 +90,8 @@ namespace Goals
 		virtual bool isElementar() const override { return true; }
 
 		virtual HeroPtr getHero() const override { return AbstractGoal::hero; }
+
+		virtual int getHeroExchangeCount() const override { return 0; }
 	};
 
 	class DLL_EXPORT Invalid : public ElementarGoal<Invalid>

+ 5 - 0
AI/Nullkiller/Goals/Composition.cpp

@@ -72,4 +72,9 @@ Composition & Composition::addNext(TSubgoal goal)
 bool Composition::isElementar() const
 {
 	return subtasks.back()->isElementar();
+}
+
+int Composition::getHeroExchangeCount() const
+{
+	return isElementar() ? taskptr(*subtasks.back())->getHeroExchangeCount() : 0;
 }

+ 1 - 0
AI/Nullkiller/Goals/Composition.h

@@ -36,5 +36,6 @@ namespace Goals
 		Composition & addNext(TSubgoal goal);
 		virtual TGoalVec decompose() const override;
 		virtual bool isElementar() const override;
+		virtual int getHeroExchangeCount() const override;
 	};
 }

+ 2 - 0
AI/Nullkiller/Goals/ExecuteHeroChain.h

@@ -31,6 +31,8 @@ namespace Goals
 		virtual bool operator==(const ExecuteHeroChain & other) const override;
 		const AIPath & getPath() const { return chainPath; }
 
+		virtual int getHeroExchangeCount() const override { return chainPath.exchangeCount; }
+
 	private:
 		bool moveHeroToTile(const CGHeroInstance * hero, const int3 & tile);
 	};

+ 87 - 63
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -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);
 

+ 16 - 3
AI/Nullkiller/Pathfinding/AINodeStorage.h

@@ -25,10 +25,16 @@
 
 namespace AIPathfinding
 {
-	const int BUCKET_COUNT = 11;
-	const int BUCKET_SIZE = GameConstants::MAX_HEROES_PER_PLAYER;
+#ifdef ENVIRONMENT64
+	const int BUCKET_COUNT = 7;
+#else
+	const int BUCKET_COUNT = 5;
+#endif // ENVIRONMENT64
+
+	const int BUCKET_SIZE = 5;
 	const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE;
 	const int THREAD_COUNT = 8;
+	const int CHAIN_MAX_DEPTH = 4;
 }
 
 struct AIPathNode : public CGPathNode
@@ -239,7 +245,14 @@ public:
 		return ((uintptr_t)actor * 395) % AIPathfinding::BUCKET_COUNT;
 	}
 
-
 	void calculateTownPortalTeleportations(std::vector<CGPathNode *> & neighbours);
 	void fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const;
+
+private:
+	template<class TVector>
+	void calculateTownPortal(
+		const ChainActor * actor,
+		const std::map<const CGHeroInstance *, int> & maskMap,
+		const std::vector<CGPathNode *> & initialNodes,
+		TVector & output);
 };

+ 3 - 0
AI/Nullkiller/Pathfinding/Actors.cpp

@@ -275,6 +275,9 @@ ExchangeResult HeroExchangeMap::tryExchangeNoLock(const ChainActor * other)
 
 		if(!differentMasks) return result;
 
+		if(actor->allowSpellCast || other->allowSpellCast)
+			return result;
+
 		TResources resources = ai->cb->getResourceAmount();
 
 		if(!resources.canAfford(actor->armyCost + other->armyCost))

+ 22 - 10
server/CVCMIServer.cpp

@@ -315,11 +315,28 @@ void CVCMIServer::threadHandleClient(std::shared_ptr<CConnection> c)
 	setThreadName("CVCMIServer::handleConnection");
 	c->enterLobbyConnectionMode();
 
+#ifndef _MSC_VER
 	try
 	{
+#endif
 		while(c->connected)
 		{
-			CPack * pack = c->retrievePack();
+			CPack * pack;
+			
+			try
+			{
+				pack = c->retrievePack();
+			}
+			catch(boost::system::system_error & e)
+			{
+				logNetwork->error("Network error receiving a pack. Connection %s dies. What happened: %s", c->toString(), e.what());
+
+				if(state != EServerState::LOBBY)
+					gh->handleClientDisconnection(c);
+
+				break;
+			}
+			
 			if(auto lobbyPack = dynamic_ptr_cast<CPackForLobby>(pack))
 			{
 				handleReceivedPack(std::unique_ptr<CPackForLobby>(lobbyPack));
@@ -329,14 +346,8 @@ void CVCMIServer::threadHandleClient(std::shared_ptr<CConnection> c)
 				gh->handleReceivedPack(serverPack);
 			}
 		}
-	}
-	catch(boost::system::system_error & e)
-	{
-        (void)e;
-		if(state != EServerState::LOBBY)
-			gh->handleClientDisconnection(c);
-	}
-	/*
+#ifndef _MSC_VER
+	 }
 	catch(const std::exception & e)
 	{
         (void)e;
@@ -348,7 +359,8 @@ void CVCMIServer::threadHandleClient(std::shared_ptr<CConnection> c)
 		state = EServerState::SHUTDOWN;
 		handleException();
 		throw;
-	}*/
+	}
+#endif
 
 	boost::unique_lock<boost::recursive_mutex> queueLock(mx);
 //	if(state != ENDING_AND_STARTING_GAME)