|  | @@ -9,13 +9,17 @@
 | 
	
		
			
				|  |  |   */
 | 
	
		
			
				|  |  |  #include "StdInc.h"
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -#include "../../lib/ArtifactUtils.h"
 | 
	
		
			
				|  |  | +#include "../../lib/AsyncRunner.h"
 | 
	
		
			
				|  |  |  #include "../../lib/UnlockGuard.h"
 | 
	
		
			
				|  |  |  #include "../../lib/StartInfo.h"
 | 
	
		
			
				|  |  | +#include "../../lib/battle/CPlayerBattleCallback.h"
 | 
	
		
			
				|  |  | +#include "../../lib/entities/artifact/ArtifactUtils.h"
 | 
	
		
			
				|  |  | +#include "../../lib/entities/artifact/CArtifact.h"
 | 
	
		
			
				|  |  |  #include "../../lib/entities/building/CBuilding.h"
 | 
	
		
			
				|  |  |  #include "../../lib/mapObjects/MapObjects.h"
 | 
	
		
			
				|  |  |  #include "../../lib/mapObjects/ObjectTemplate.h"
 | 
	
		
			
				|  |  |  #include "../../lib/mapObjects/CGHeroInstance.h"
 | 
	
		
			
				|  |  | +#include "../../lib/mapping/TerrainTile.h"
 | 
	
		
			
				|  |  |  #include "../../lib/CConfigHandler.h"
 | 
	
		
			
				|  |  |  #include "../../lib/IGameSettings.h"
 | 
	
		
			
				|  |  |  #include "../../lib/gameState/CGameState.h"
 | 
	
	
		
			
				|  | @@ -32,8 +36,6 @@
 | 
	
		
			
				|  |  |  #include "AIGateway.h"
 | 
	
		
			
				|  |  |  #include "Goals/Goals.h"
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -static tbb::task_arena executeActionAsyncArena;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  namespace NKAI
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -68,12 +70,13 @@ struct SetGlobalState
 | 
	
		
			
				|  |  |  #define MAKING_TURN SET_GLOBAL_STATE(this)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  AIGateway::AIGateway()
 | 
	
		
			
				|  |  | +	:status(this)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |  	LOG_TRACE(logAi);
 | 
	
		
			
				|  |  |  	destinationTeleport = ObjectInstanceID();
 | 
	
		
			
				|  |  |  	destinationTeleportPos = int3(-1);
 | 
	
		
			
				|  |  |  	nullkiller.reset(new Nullkiller());
 | 
	
		
			
				|  |  | -	asyncTasks = std::make_unique<tbb::task_group>();
 | 
	
		
			
				|  |  | +	asyncTasks = std::make_unique<AsyncRunner>();
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  AIGateway::~AIGateway()
 | 
	
	
		
			
				|  | @@ -125,7 +128,7 @@ void AIGateway::heroMoved(const TryMoveHero & details, bool verbose)
 | 
	
		
			
				|  |  |  	else if(details.result == TryMoveHero::EMBARK && hero)
 | 
	
		
			
				|  |  |  	{
 | 
	
		
			
				|  |  |  		//make sure AI not attempt to visit used boat
 | 
	
		
			
				|  |  | -		validateObject(hero->boat);
 | 
	
		
			
				|  |  | +		validateObject(hero->getBoat());
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  	else if(details.result == TryMoveHero::DISEMBARK && o1)
 | 
	
		
			
				|  |  |  	{
 | 
	
	
		
			
				|  | @@ -266,7 +269,7 @@ void AIGateway::heroVisitsTown(const CGHeroInstance * hero, const CGTownInstance
 | 
	
		
			
				|  |  |  	NET_EVENT_HANDLER;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -void AIGateway::tileHidden(const std::unordered_set<int3> & pos)
 | 
	
		
			
				|  |  | +void AIGateway::tileHidden(const FowTilesType & pos)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |  	LOG_TRACE(logAi);
 | 
	
		
			
				|  |  |  	NET_EVENT_HANDLER;
 | 
	
	
		
			
				|  | @@ -274,7 +277,7 @@ void AIGateway::tileHidden(const std::unordered_set<int3> & pos)
 | 
	
		
			
				|  |  |  	nullkiller->memory->removeInvisibleObjects(myCb.get());
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -void AIGateway::tileRevealed(const std::unordered_set<int3> & pos)
 | 
	
		
			
				|  |  | +void AIGateway::tileRevealed(const FowTilesType & pos)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |  	LOG_TRACE(logAi);
 | 
	
		
			
				|  |  |  	NET_EVENT_HANDLER;
 | 
	
	
		
			
				|  | @@ -324,9 +327,15 @@ void AIGateway::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID her
 | 
	
		
			
				|  |  |  	});
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +void AIGateway::heroExperienceChanged(const CGHeroInstance * hero, si64 val)
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +	LOG_TRACE_PARAMS(logAi, "val '%i'", val);
 | 
	
		
			
				|  |  | +	NET_EVENT_HANDLER;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  void AIGateway::heroPrimarySkillChanged(const CGHeroInstance * hero, PrimarySkill which, si64 val)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  | -	LOG_TRACE_PARAMS(logAi, "which '%i', val '%i'", static_cast<int>(which) % val);
 | 
	
		
			
				|  |  | +	LOG_TRACE_PARAMS(logAi, "which '%i', val '%i'", which.getNum() % val);
 | 
	
		
			
				|  |  |  	NET_EVENT_HANDLER;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -575,7 +584,6 @@ void AIGateway::initGameInterface(std::shared_ptr<Environment> env, std::shared_
 | 
	
		
			
				|  |  |  	NET_EVENT_HANDLER;
 | 
	
		
			
				|  |  |  	playerID = *myCb->getPlayerID();
 | 
	
		
			
				|  |  |  	myCb->waitTillRealize = true;
 | 
	
		
			
				|  |  | -	myCb->unlockGsWhenWaiting = true;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	nullkiller->init(CB, this);
 | 
	
		
			
				|  |  |  	
 | 
	
	
		
			
				|  | @@ -593,11 +601,11 @@ void AIGateway::yourTurn(QueryID queryID)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	nullkiller->makingTurnInterrupption.reset();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	executeActionAsyncArena.enqueue(asyncTasks->defer([this]()
 | 
	
		
			
				|  |  | +	asyncTasks->run([this]()
 | 
	
		
			
				|  |  |  	{
 | 
	
		
			
				|  |  |  		ScopedThreadName guard("NKAI::makingTurn");
 | 
	
		
			
				|  |  |  		makeTurn();
 | 
	
		
			
				|  |  | -	}));
 | 
	
		
			
				|  |  | +	});
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  void AIGateway::heroGotLevel(const CGHeroInstance * hero, PrimarySkill pskill, std::vector<SecondarySkill> & skills, QueryID queryID)
 | 
	
	
		
			
				|  | @@ -629,7 +637,7 @@ void AIGateway::commanderGotLevel(const CCommanderInstance * commander, std::vec
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |  	LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID);
 | 
	
		
			
				|  |  |  	NET_EVENT_HANDLER;
 | 
	
		
			
				|  |  | -	status.addQuery(queryID, boost::str(boost::format("Commander %s of %s got level %d") % commander->name % commander->armyObj->nodeName() % (int)commander->level));
 | 
	
		
			
				|  |  | +	status.addQuery(queryID, boost::str(boost::format("Commander %s of %s got level %d") % commander->name % commander->getArmy()->nodeName() % (int)commander->level));
 | 
	
		
			
				|  |  |  	executeActionAsync("commanderGotLevel", [this, queryID](){ answerQuery(queryID, 0); });
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -774,7 +782,7 @@ void AIGateway::showGarrisonDialog(const CArmedInstance * up, const CGHeroInstan
 | 
	
		
			
				|  |  |  	//you can't request action from action-response thread
 | 
	
		
			
				|  |  |  	executeActionAsync("showGarrisonDialog", [this, up, down, removableUnits, queryID]()
 | 
	
		
			
				|  |  |  	{
 | 
	
		
			
				|  |  | -		if(removableUnits && up->tempOwner == down->tempOwner && nullkiller->settings->isGarrisonTroopsUsageAllowed() && !cb->getStartInfo()->isRestorationOfErathiaCampaign())
 | 
	
		
			
				|  |  | +		if(removableUnits && up->tempOwner == down->tempOwner && nullkiller->settings->isGarrisonTroopsUsageAllowed() && !cb->getStartInfo()->restrictedGarrisonsForAI())
 | 
	
		
			
				|  |  |  		{
 | 
	
		
			
				|  |  |  			pickBestCreatures(down, up);
 | 
	
		
			
				|  |  |  		}
 | 
	
	
		
			
				|  | @@ -817,11 +825,11 @@ bool AIGateway::makePossibleUpgrades(const CArmedInstance * obj)
 | 
	
		
			
				|  |  |  					int oldValue = s->getCreature()->getAIValue();
 | 
	
		
			
				|  |  |  					int newValue = upgID.toCreature()->getAIValue();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -					if(newValue > oldValue && nullkiller->getFreeResources().canAfford(upgradeInfo.getUpgradeCostsFor(upgID) * s->count))
 | 
	
		
			
				|  |  | +					if(newValue > oldValue && nullkiller->getFreeResources().canAfford(upgradeInfo.getUpgradeCostsFor(upgID) * s->getCount()))
 | 
	
		
			
				|  |  |  					{
 | 
	
		
			
				|  |  |  						myCb->upgradeCreature(obj, SlotID(i), upgID);
 | 
	
		
			
				|  |  |  						upgraded = true;
 | 
	
		
			
				|  |  | -						logAi->debug("Upgraded %d %s to %s", s->count, upgradeInfo.oldID.toCreature()->getNamePluralTranslated(), 
 | 
	
		
			
				|  |  | +						logAi->debug("Upgraded %d %s to %s", s->getCount(), upgradeInfo.oldID.toCreature()->getNamePluralTranslated(),
 | 
	
		
			
				|  |  |  							upgradeInfo.getUpgrade().toCreature()->getNamePluralTranslated());
 | 
	
		
			
				|  |  |  					}
 | 
	
		
			
				|  |  |  					else
 | 
	
	
		
			
				|  | @@ -905,19 +913,19 @@ void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h
 | 
	
		
			
				|  |  |  	switch(obj->ID)
 | 
	
		
			
				|  |  |  	{
 | 
	
		
			
				|  |  |  	case Obj::TOWN:
 | 
	
		
			
				|  |  | -		if(h->visitedTown) //we are inside, not just attacking
 | 
	
		
			
				|  |  | +		if(h->getVisitedTown()) //we are inside, not just attacking
 | 
	
		
			
				|  |  |  		{
 | 
	
		
			
				|  |  |  			makePossibleUpgrades(h.get());
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  			std::unique_lock lockGuard(nullkiller->aiStateMutex);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -			if(!h->visitedTown->garrisonHero || !nullkiller->isHeroLocked(h->visitedTown->garrisonHero))
 | 
	
		
			
				|  |  | -				moveCreaturesToHero(h->visitedTown);
 | 
	
		
			
				|  |  | +			if(!h->getVisitedTown()->getGarrisonHero() || !nullkiller->isHeroLocked(h->getVisitedTown()->getGarrisonHero()))
 | 
	
		
			
				|  |  | +				moveCreaturesToHero(h->getVisitedTown());
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  			if(nullkiller->heroManager->getHeroRole(h) == HeroRole::MAIN && !h->hasSpellbook()
 | 
	
		
			
				|  |  |  				&& nullkiller->getFreeGold() >= GameConstants::SPELLBOOK_GOLD_COST)
 | 
	
		
			
				|  |  |  			{
 | 
	
		
			
				|  |  | -				if(h->visitedTown->hasBuilt(BuildingID::MAGES_GUILD_1))
 | 
	
		
			
				|  |  | +				if(h->getVisitedTown()->hasBuilt(BuildingID::MAGES_GUILD_1))
 | 
	
		
			
				|  |  |  					cb->buyArtifact(h.get(), ArtifactID::SPELLBOOK);
 | 
	
		
			
				|  |  |  			}
 | 
	
		
			
				|  |  |  		}
 | 
	
	
		
			
				|  | @@ -930,9 +938,9 @@ void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  void AIGateway::moveCreaturesToHero(const CGTownInstance * t)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  | -	if(t->visitingHero && t->armedGarrison() && t->visitingHero->tempOwner == t->tempOwner)
 | 
	
		
			
				|  |  | +	if(t->getVisitingHero() && t->armedGarrison() && t->getVisitingHero()->tempOwner == t->tempOwner)
 | 
	
		
			
				|  |  |  	{
 | 
	
		
			
				|  |  | -		pickBestCreatures(t->visitingHero, t->getUpperArmy());
 | 
	
		
			
				|  |  | +		pickBestCreatures(t->getVisitingHero(), t->getUpperArmy());
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -1040,6 +1048,7 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
 | 
	
		
			
				|  |  |  	auto equipBest = [](const CGHeroInstance * h, const CGHeroInstance * otherh, bool giveStuffToFirstHero) -> void
 | 
	
		
			
				|  |  |  	{
 | 
	
		
			
				|  |  |  		bool changeMade = false;
 | 
	
		
			
				|  |  | +		std::set<std::pair<ArtifactInstanceID, ArtifactInstanceID> > swappedSet;
 | 
	
		
			
				|  |  |  		do
 | 
	
		
			
				|  |  |  		{
 | 
	
		
			
				|  |  |  			changeMade = false;
 | 
	
	
		
			
				|  | @@ -1050,22 +1059,22 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
 | 
	
		
			
				|  |  |  			{
 | 
	
		
			
				|  |  |  				for(auto p : h->artifactsWorn)
 | 
	
		
			
				|  |  |  				{
 | 
	
		
			
				|  |  | -					if(p.second.artifact)
 | 
	
		
			
				|  |  | +					if(p.second.getArt())
 | 
	
		
			
				|  |  |  						allArtifacts.push_back(ArtifactLocation(h->id, p.first));
 | 
	
		
			
				|  |  |  				}
 | 
	
		
			
				|  |  |  			}
 | 
	
		
			
				|  |  |  			for(auto slot : h->artifactsInBackpack)
 | 
	
		
			
				|  |  | -				allArtifacts.push_back(ArtifactLocation(h->id, h->getArtPos(slot.artifact)));
 | 
	
		
			
				|  |  | +				allArtifacts.push_back(ArtifactLocation(h->id, h->getArtPos(slot.getArt())));
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  			if(otherh)
 | 
	
		
			
				|  |  |  			{
 | 
	
		
			
				|  |  |  				for(auto p : otherh->artifactsWorn)
 | 
	
		
			
				|  |  |  				{
 | 
	
		
			
				|  |  | -					if(p.second.artifact)
 | 
	
		
			
				|  |  | +					if(p.second.getArt())
 | 
	
		
			
				|  |  |  						allArtifacts.push_back(ArtifactLocation(otherh->id, p.first));
 | 
	
		
			
				|  |  |  				}
 | 
	
		
			
				|  |  |  				for(auto slot : otherh->artifactsInBackpack)
 | 
	
		
			
				|  |  | -					allArtifacts.push_back(ArtifactLocation(otherh->id, otherh->getArtPos(slot.artifact)));
 | 
	
		
			
				|  |  | +					allArtifacts.push_back(ArtifactLocation(otherh->id, otherh->getArtPos(slot.getArt())));
 | 
	
		
			
				|  |  |  			}
 | 
	
		
			
				|  |  |  			//we give stuff to one hero or another, depending on giveStuffToFirstHero
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -1087,7 +1096,7 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
 | 
	
		
			
				|  |  |  				auto s = artHolder->getSlot(location.slot);
 | 
	
		
			
				|  |  |  				if(!s || s->locked) //we can't move locks
 | 
	
		
			
				|  |  |  					continue;
 | 
	
		
			
				|  |  | -				auto artifact = s->artifact;
 | 
	
		
			
				|  |  | +				auto artifact = s->getArt();
 | 
	
		
			
				|  |  |  				if(!artifact)
 | 
	
		
			
				|  |  |  					continue;
 | 
	
		
			
				|  |  |  				//FIXME: why are the above possible to be null?
 | 
	
	
		
			
				|  | @@ -1111,21 +1120,30 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
 | 
	
		
			
				|  |  |  					for(auto slot : artifact->getType()->getPossibleSlots().at(target->bearerType()))
 | 
	
		
			
				|  |  |  					{
 | 
	
		
			
				|  |  |  						auto otherSlot = target->getSlot(slot);
 | 
	
		
			
				|  |  | -						if(otherSlot && otherSlot->artifact) //we need to exchange artifact for better one
 | 
	
		
			
				|  |  | +						if(otherSlot && otherSlot->getArt()) //we need to exchange artifact for better one
 | 
	
		
			
				|  |  |  						{
 | 
	
		
			
				|  |  | -							int64_t otherArtifactScore = getArtifactScoreForHero(target, otherSlot->artifact);
 | 
	
		
			
				|  |  | -							logAi->trace( "Comparing artifacts of %s: %s vs %s. Score: %d vs %d", target->getHeroTypeName(), artifact->getType()->getJsonKey(), otherSlot->artifact->getType()->getJsonKey(), artifactScore, otherArtifactScore);
 | 
	
		
			
				|  |  | +							int64_t otherArtifactScore = getArtifactScoreForHero(target, otherSlot->getArt());
 | 
	
		
			
				|  |  | +							logAi->trace( "Comparing artifacts of %s: %s vs %s. Score: %d vs %d", target->getHeroTypeName(), artifact->getType()->getJsonKey(), otherSlot->getArt()->getType()->getJsonKey(), artifactScore, otherArtifactScore);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  							//if that artifact is better than what we have, pick it
 | 
	
		
			
				|  |  |  							//combined artifacts are not always allowed to move
 | 
	
		
			
				|  |  |  							if(artifactScore > otherArtifactScore && artifact->canBePutAt(target, slot, true))
 | 
	
		
			
				|  |  |  							{
 | 
	
		
			
				|  |  | +								std::pair swapPair = std::minmax<ArtifactInstanceID>({artifact->getId(), otherSlot->artifactID});
 | 
	
		
			
				|  |  | +								if (swappedSet.find(swapPair) != swappedSet.end())
 | 
	
		
			
				|  |  | +								{
 | 
	
		
			
				|  |  | +									logAi->warn(
 | 
	
		
			
				|  |  | +										"Artifacts % s < -> % s have already swapped before, ignored.",
 | 
	
		
			
				|  |  | +										artifact->getType()->getJsonKey(),
 | 
	
		
			
				|  |  | +										otherSlot->getArt()->getType()->getJsonKey());
 | 
	
		
			
				|  |  | +									continue;
 | 
	
		
			
				|  |  | +								}
 | 
	
		
			
				|  |  |  								logAi->trace(
 | 
	
		
			
				|  |  |  									"Exchange artifacts %s <-> %s",
 | 
	
		
			
				|  |  |  									artifact->getType()->getJsonKey(),
 | 
	
		
			
				|  |  | -									otherSlot->artifact->getType()->getJsonKey());
 | 
	
		
			
				|  |  | +									otherSlot->getArt()->getType()->getJsonKey());
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -								if(!otherSlot->artifact->canBePutAt(artHolder, location.slot, true))
 | 
	
		
			
				|  |  | +								if(!otherSlot->getArt()->canBePutAt(artHolder, location.slot, true))
 | 
	
		
			
				|  |  |  								{
 | 
	
		
			
				|  |  |  									ArtifactLocation destLocation(target->id, slot);
 | 
	
		
			
				|  |  |  									ArtifactLocation backpack(artHolder->id, ArtifactPosition::BACKPACK_START);
 | 
	
	
		
			
				|  | @@ -1135,10 +1153,11 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
 | 
	
		
			
				|  |  |  								}
 | 
	
		
			
				|  |  |  								else
 | 
	
		
			
				|  |  |  								{
 | 
	
		
			
				|  |  | -									cb->swapArtifacts(location, ArtifactLocation(target->id, target->getArtPos(otherSlot->artifact)));
 | 
	
		
			
				|  |  | +									cb->swapArtifacts(location, ArtifactLocation(target->id, target->getArtPos(otherSlot->getArt())));
 | 
	
		
			
				|  |  |  								}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  								changeMade = true;
 | 
	
		
			
				|  |  | +								swappedSet.insert(swapPair);
 | 
	
		
			
				|  |  |  								break;
 | 
	
		
			
				|  |  |  							}
 | 
	
		
			
				|  |  |  						}
 | 
	
	
		
			
				|  | @@ -1170,7 +1189,7 @@ void AIGateway::recruitCreatures(const CGDwelling * d, const CArmedInstance * re
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  		if(!recruiter->getSlotFor(creID).validSlot())
 | 
	
		
			
				|  |  |  		{
 | 
	
		
			
				|  |  | -			for(auto stack : recruiter->Slots())
 | 
	
		
			
				|  |  | +			for(const auto & stack : recruiter->Slots())
 | 
	
		
			
				|  |  |  			{
 | 
	
		
			
				|  |  |  				if(!stack.second->getType())
 | 
	
		
			
				|  |  |  					continue;
 | 
	
	
		
			
				|  | @@ -1272,10 +1291,10 @@ void AIGateway::addVisitableObj(const CGObjectInstance * obj)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  | -	if(h->inTownGarrison && h->visitedTown)
 | 
	
		
			
				|  |  | +	if(h->isGarrisoned() && h->getVisitedTown())
 | 
	
		
			
				|  |  |  	{
 | 
	
		
			
				|  |  | -		cb->swapGarrisonHero(h->visitedTown);
 | 
	
		
			
				|  |  | -		moveCreaturesToHero(h->visitedTown);
 | 
	
		
			
				|  |  | +		cb->swapGarrisonHero(h->getVisitedTown());
 | 
	
		
			
				|  |  | +		moveCreaturesToHero(h->getVisitedTown());
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	//TODO: consider if blockVisit objects change something in our checks: AIUtility::isBlockVisitObj()
 | 
	
	
		
			
				|  | @@ -1322,8 +1341,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
 | 
	
		
			
				|  |  |  		{
 | 
	
		
			
				|  |  |  			auto tile = cb->getTile(coord, false);
 | 
	
		
			
				|  |  |  			assert(tile);
 | 
	
		
			
				|  |  | -			return tile->topVisitableObj(ignoreHero);
 | 
	
		
			
				|  |  | -			//return cb->getTile(coord,false)->topVisitableObj(ignoreHero);
 | 
	
		
			
				|  |  | +			return cb->getObj(tile->topVisitableObj(ignoreHero), false);
 | 
	
		
			
				|  |  |  		};
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  		auto isTeleportAction = [&](EPathNodeAction action) -> bool
 | 
	
	
		
			
				|  | @@ -1533,7 +1551,7 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  				int toGive;
 | 
	
		
			
				|  |  |  				int toGet;
 | 
	
		
			
				|  |  | -				m->getOffer(res, g.resID, toGive, toGet, EMarketMode::RESOURCE_RESOURCE);
 | 
	
		
			
				|  |  | +				m->getOffer(res.getNum(), g.resID, toGive, toGet, EMarketMode::RESOURCE_RESOURCE);
 | 
	
		
			
				|  |  |  				toGive = static_cast<int>(toGive * (it->resVal / toGive)); //round down
 | 
	
		
			
				|  |  |  				//TODO trade only as much as needed
 | 
	
		
			
				|  |  |  				if (toGive) //don't try to sell 0 resources
 | 
	
	
		
			
				|  | @@ -1586,7 +1604,7 @@ void AIGateway::endTurn()
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  void AIGateway::buildArmyIn(const CGTownInstance * t)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  | -	makePossibleUpgrades(t->visitingHero);
 | 
	
		
			
				|  |  | +	makePossibleUpgrades(t->getVisitingHero());
 | 
	
		
			
				|  |  |  	makePossibleUpgrades(t);
 | 
	
		
			
				|  |  |  	recruitCreatures(t, t->getUpperArmy());
 | 
	
		
			
				|  |  |  	moveCreaturesToHero(t);
 | 
	
	
		
			
				|  | @@ -1608,13 +1626,13 @@ void AIGateway::executeActionAsync(const std::string & description, const std::f
 | 
	
		
			
				|  |  |  	if (!asyncTasks)
 | 
	
		
			
				|  |  |  		throw std::runtime_error("Attempt to execute task on shut down AI state!");
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	executeActionAsyncArena.enqueue(asyncTasks->defer([this, description, whatToDo]()
 | 
	
		
			
				|  |  | +	asyncTasks->run([this, description, whatToDo]()
 | 
	
		
			
				|  |  |  	{
 | 
	
		
			
				|  |  |  		ScopedThreadName guard("NKAI::" + description);
 | 
	
		
			
				|  |  |  		SET_GLOBAL_STATE(this);
 | 
	
		
			
				|  |  |  		std::shared_lock gsLock(CGameState::mutex);
 | 
	
		
			
				|  |  |  		whatToDo();
 | 
	
		
			
				|  |  | -	}));
 | 
	
		
			
				|  |  | +	});
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  void AIGateway::lostHero(HeroPtr h)
 | 
	
	
		
			
				|  | @@ -1667,7 +1685,8 @@ void AIGateway::validateObject(ObjectIdRef obj)
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -AIStatus::AIStatus()
 | 
	
		
			
				|  |  | +AIStatus::AIStatus(AIGateway * gateway)
 | 
	
		
			
				|  |  | +	: gateway(gateway)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |  	battle = NO_BATTLE;
 | 
	
		
			
				|  |  |  	havingTurn = false;
 | 
	
	
		
			
				|  | @@ -1749,7 +1768,10 @@ void AIStatus::waitTillFree()
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |  	std::unique_lock<std::mutex> lock(mx);
 | 
	
		
			
				|  |  |  	while(battle != NO_BATTLE || !remainingQueries.empty() || !objectsBeingVisited.empty() || ongoingHeroMovement)
 | 
	
		
			
				|  |  | +	{
 | 
	
		
			
				|  |  |  		cv.wait_for(lock, std::chrono::milliseconds(10));
 | 
	
		
			
				|  |  | +		gateway->nullkiller->makingTurnInterrupption.interruptionPoint();
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  bool AIStatus::haveTurn()
 |