Sfoglia il codice sorgente

WIP remove thread_local CCallback in HeroPtr

Mircea TheHonestCTO 3 mesi fa
parent
commit
13bd7ed8f4
29 ha cambiato i file con 171 aggiunte e 204 eliminazioni
  1. 45 47
      AI/Nullkiller2/AIGateway.cpp
  2. 2 2
      AI/Nullkiller2/AIGateway.h
  3. 33 54
      AI/Nullkiller2/AIUtility.cpp
  4. 7 7
      AI/Nullkiller2/AIUtility.h
  5. 2 7
      AI/Nullkiller2/Analyzers/DangerHitMapAnalyzer.cpp
  6. 12 23
      AI/Nullkiller2/Analyzers/DangerHitMapAnalyzer.h
  7. 19 14
      AI/Nullkiller2/Analyzers/HeroManager.cpp
  8. 3 2
      AI/Nullkiller2/Analyzers/HeroManager.h
  9. 1 1
      AI/Nullkiller2/Analyzers/ObjectClusterizer.cpp
  10. 1 1
      AI/Nullkiller2/Behaviors/BuyArmyBehavior.cpp
  11. 3 3
      AI/Nullkiller2/Behaviors/CaptureObjectsBehavior.cpp
  12. 2 2
      AI/Nullkiller2/Behaviors/DefenceBehavior.cpp
  13. 4 4
      AI/Nullkiller2/Behaviors/GatherArmyBehavior.cpp
  14. 1 1
      AI/Nullkiller2/Behaviors/RecruitHeroBehavior.cpp
  15. 1 1
      AI/Nullkiller2/Behaviors/StartupBehavior.cpp
  16. 4 6
      AI/Nullkiller2/Engine/Nullkiller.cpp
  17. 1 1
      AI/Nullkiller2/Engine/Nullkiller.h
  18. 7 7
      AI/Nullkiller2/Engine/PriorityEvaluator.cpp
  19. 1 1
      AI/Nullkiller2/Goals/AdventureSpellCast.cpp
  20. 1 1
      AI/Nullkiller2/Goals/BuyArmy.cpp
  21. 1 1
      AI/Nullkiller2/Goals/ExchangeSwapTownHeroes.cpp
  22. 6 6
      AI/Nullkiller2/Goals/ExecuteHeroChain.cpp
  23. 1 1
      AI/Nullkiller2/Goals/ExploreNeighbourTile.cpp
  24. 2 2
      AI/Nullkiller2/Helpers/ExplorationHelper.cpp
  25. 4 4
      AI/Nullkiller2/Pathfinding/AINodeStorage.cpp
  26. 1 1
      AI/Nullkiller2/Pathfinding/AINodeStorage.h
  27. 4 2
      AI/Nullkiller2/Pathfinding/AIPathfinder.h
  28. 1 1
      AI/Nullkiller2/Pathfinding/Actions/BattleAction.cpp
  29. 1 1
      AI/Nullkiller2/Pathfinding/Actions/QuestAction.cpp

+ 45 - 47
AI/Nullkiller2/AIGateway.cpp

@@ -366,7 +366,7 @@ void AIGateway::objectRemoved(const CGObjectInstance * obj, const PlayerColor &
 
 	if(obj->ID == Obj::HERO && obj->tempOwner == playerID)
 	{
-		lostHero(HeroPtr(cc->getHero(obj->id))); //we can promote, since objectRemoved is called just before actual deletion
+		lostHero(HeroPtr(cc->getHero(obj->id), cc)); //we can promote, since objectRemoved is called just before actual deletion
 	}
 
 	if(obj->ID == Obj::HERO && cc->getPlayerRelations(obj->tempOwner, playerID) == PlayerRelations::ENEMIES)
@@ -587,19 +587,17 @@ void AIGateway::heroGotLevel(const CGHeroInstance * hero, PrimarySkill pskill, s
 	NET_EVENT_HANDLER;
 
 	status.addQuery(queryID, boost::str(boost::format("Hero %s got level %d") % hero->getNameTranslated() % hero->level));
-	HeroPtr hPtr(hero);
+	HeroPtr heroPtr(hero, cc);
 
-	executeActionAsync("heroGotLevel", [this, hPtr, skills, queryID]()
+	executeActionAsync("heroGotLevel", [this, heroPtr, skills, queryID]()
 	{
 		int sel = 0;
 
-		if(hPtr.isValid())
+		if(heroPtr.isValid())
 		{
 			std::unique_lock lockGuard(nullkiller->aiStateMutex);
-
 			nullkiller->heroManager->update();
-
-			sel = nullkiller->heroManager->selectBestSkill(hPtr, skills);
+			sel = nullkiller->heroManager->selectBestSkill(heroPtr, skills);
 		}
 
 		answerQuery(queryID, sel);
@@ -632,12 +630,12 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
 			bool answer = true;
 			auto objects = cc->getVisitableObjs(target);
 
-			if(heroPtr.isValid() && target.isValid() && objects.size())
+			if(heroPtr.isValid() && target.isValid() && !objects.empty())
 			{
 				auto topObj = objects.front()->id == heroPtr->id ? objects.back() : objects.front();
 				auto objType = topObj->ID; // top object should be our hero
 				auto goalObjectID = nullkiller->getTargetObject();
-				auto danger = nullkiller->dangerEvaluator->evaluateDanger(target, heroPtr.get(cc.get()));
+				auto danger = nullkiller->dangerEvaluator->evaluateDanger(target, heroPtr.get());
 				auto ratio = static_cast<float>(danger) / heroPtr->getTotalStrength();
 
 				answer = true;
@@ -648,7 +646,7 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
 					answer = false;
 				}
 
-				logAi->trace("Query hook: %s(%s) by %s danger ratio %f", target.toString(), topObj->getObjectName(), heroPtr.name(), ratio);
+				logAi->trace("Query hook: %s(%s) by %s danger ratio %f", target.toString(), topObj->getObjectName(), heroPtr.nameOrDefault(), ratio);
 
 				if(cc->getObj(goalObjectID, false))
 				{
@@ -860,31 +858,31 @@ void AIGateway::makeTurn()
 	}
 }
 
-void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
+void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr heroPtr)
 {
-	LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->anchorPos().toString());
+	LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", heroPtr->getNameTranslated() % obj->getObjectName() % obj->anchorPos().toString());
 	switch(obj->ID)
 	{
 	case Obj::TOWN:
-		if(h->getVisitedTown()) //we are inside, not just attacking
+		if(heroPtr->getVisitedTown()) //we are inside, not just attacking
 		{
-			makePossibleUpgrades(h.get(cc.get()));
+			makePossibleUpgrades(heroPtr.get());
 
 			std::unique_lock lockGuard(nullkiller->aiStateMutex);
 
-			if(!h->getVisitedTown()->getGarrisonHero() || !nullkiller->isHeroLocked(h->getVisitedTown()->getGarrisonHero()))
-				moveCreaturesToHero(h->getVisitedTown());
+			if(!heroPtr->getVisitedTown()->getGarrisonHero() || !nullkiller->isHeroLocked(heroPtr->getVisitedTown()->getGarrisonHero()))
+				moveCreaturesToHero(heroPtr->getVisitedTown());
 
-			if(nullkiller->heroManager->getHeroRole(h) == HeroRole::MAIN && !h->hasSpellbook()
+			if(nullkiller->heroManager->getHeroRole(heroPtr) == HeroRole::MAIN && !heroPtr->hasSpellbook()
 				&& nullkiller->getFreeGold() >= GameConstants::SPELLBOOK_GOLD_COST)
 			{
-				if(h->getVisitedTown()->hasBuilt(BuildingID::MAGES_GUILD_1))
-					cc->buyArtifact(h.get(cc.get()), ArtifactID::SPELLBOOK);
+				if(heroPtr->getVisitedTown()->hasBuilt(BuildingID::MAGES_GUILD_1))
+					cc->buyArtifact(heroPtr.get(), ArtifactID::SPELLBOOK);
 			}
 		}
 		break;
 	case Obj::HILL_FORT:
-		makePossibleUpgrades(h.get(cc.get()));
+		makePossibleUpgrades(heroPtr.get());
 		break;
 	}
 }
@@ -1085,12 +1083,12 @@ std::vector<const CGObjectInstance *> AIGateway::getFlaggedObjects() const
 	return ret;
 }
 
-bool AIGateway::moveHeroToTile(int3 dst, HeroPtr hPtr)
+bool AIGateway::moveHeroToTile(const int3 dst, const HeroPtr & heroPtr)
 {
-	if(hPtr->isGarrisoned() && hPtr->getVisitedTown())
+	if(heroPtr->isGarrisoned() && heroPtr->getVisitedTown())
 	{
-		cc->swapGarrisonHero(hPtr->getVisitedTown());
-		moveCreaturesToHero(hPtr->getVisitedTown());
+		cc->swapGarrisonHero(heroPtr->getVisitedTown());
+		moveCreaturesToHero(heroPtr->getVisitedTown());
 	}
 
 	//TODO: consider if blockVisit objects change something in our checks: AIUtility::isBlockVisitObj()
@@ -1098,9 +1096,9 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr hPtr)
 	auto afterMovementCheck = [&]() -> void
 	{
 		waitTillFree(); //movement may cause battle or blocking dialog
-		if(!hPtr)
+		if(!heroPtr)
 		{
-			lostHero(hPtr);
+			lostHero(heroPtr);
 			teleportChannelProbingList.clear();
 			if(status.channelProbing()) // if hero lost during channel probing we need to switch this mode off
 				status.setChannelProbing(false);
@@ -1108,14 +1106,14 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr hPtr)
 		}
 	};
 
-	logAi->debug("Moving hero %s to tile %s", hPtr->getNameTranslated(), dst.toString());
-	int3 startHpos = hPtr->visitablePos();
+	logAi->debug("Moving hero %s to tile %s", heroPtr->getNameTranslated(), dst.toString());
+	int3 startHpos = heroPtr->visitablePos();
 	bool ret = false;
 	if(startHpos == dst)
 	{
 		//FIXME: this assertion fails also if AI moves onto defeated guarded object
 		//assert(cb->getVisitableObjs(dst).size() > 1); //there's no point in revisiting tile where there is no visitable object
-		cc->moveHero(*hPtr, hPtr->convertFromVisitablePos(dst), false);
+		cc->moveHero(*heroPtr, heroPtr->convertFromVisitablePos(dst), false);
 		afterMovementCheck(); // TODO: is it feasible to hero get killed there if game work properly?
 		// If revisiting, teleport probing is never done, and so the entries into the list would remain unused and uncleared
 		teleportChannelProbingList.clear();
@@ -1125,10 +1123,10 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr hPtr)
 	else
 	{
 		CGPath path;
-		nullkiller->getPathsInfo(hPtr.get(cc.get()))->getPath(path, dst);
+		nullkiller->getPathsInfo(heroPtr.get())->getPath(path, dst);
 		if(path.nodes.empty())
 		{
-			logAi->error("Hero %s cannot reach %s.", hPtr->getNameTranslated(), dst.toString());
+			logAi->error("Hero %s cannot reach %s.", heroPtr->getNameTranslated(), dst.toString());
 			return true;
 		}
 		int i = (int)path.nodes.size() - 1;
@@ -1168,20 +1166,20 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr hPtr)
 
 		auto doMovement = [&](int3 dst, bool transit)
 		{
-			cc->moveHero(*hPtr, hPtr->convertFromVisitablePos(dst), transit);
+			cc->moveHero(*heroPtr, heroPtr->convertFromVisitablePos(dst), transit);
 		};
 
 		auto doTeleportMovement = [&](ObjectInstanceID exitId, int3 exitPos)
 		{
 			if(cc->getObj(exitId) && cc->getObj(exitId)->ID == Obj::WHIRLPOOL)
 			{
-				nullkiller->armyFormation->rearrangeArmyForWhirlpool(*hPtr);
+				nullkiller->armyFormation->rearrangeArmyForWhirlpool(*heroPtr);
 			}
 
 			destinationTeleport = exitId;
 			if(exitPos.isValid())
 				destinationTeleportPos = exitPos;
-			cc->moveHero(*hPtr, hPtr->pos, false);
+			cc->moveHero(*heroPtr, heroPtr->pos, false);
 			destinationTeleport = ObjectInstanceID();
 			destinationTeleportPos = int3(-1);
 			afterMovementCheck();
@@ -1189,7 +1187,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr hPtr)
 
 		auto doChannelProbing = [&]() -> void
 		{
-			auto currentPos = hPtr->visitablePos();
+			auto currentPos = heroPtr->visitablePos();
 			auto currentTeleport = getObj(currentPos, true);
 
 			if(currentTeleport)
@@ -1222,7 +1220,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr hPtr)
 			int3 currentCoord = path.nodes[i].coord;
 			int3 nextCoord = path.nodes[i - 1].coord;
 
-			auto currentObject = getObj(currentCoord, currentCoord == hPtr->visitablePos());
+			auto currentObject = getObj(currentCoord, currentCoord == heroPtr->visitablePos());
 			auto nextObjectTop = getObj(nextCoord, false);
 			auto nextObject = getObj(nextCoord, true);
 			auto destTeleportObj = getDestTeleportObj(currentObject, nextObjectTop, nextObject);
@@ -1245,7 +1243,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr hPtr)
 			}
 
 			int3 endpos = path.nodes[i - 1].coord;
-			if(endpos == hPtr->visitablePos())
+			if(endpos == heroPtr->visitablePos())
 				continue;
 
 			bool isConnected = false;
@@ -1280,30 +1278,30 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr hPtr)
 		{
 			// when we take resource we do not reach its position. We even might not move
 			// also guarded town is not get visited automatically after capturing
-			ret = hPtr && i == 0;
+			ret = heroPtr && i == 0;
 		}
 	}
-	if(hPtr)
+	if(heroPtr)
 	{
-		if(auto visitedObject = vstd::frontOrNull(cc->getVisitableObjs(hPtr->visitablePos()))) //we stand on something interesting
+		if(auto visitedObject = vstd::frontOrNull(cc->getVisitableObjs(heroPtr->visitablePos()))) //we stand on something interesting
 		{
-			if(visitedObject != *hPtr)
+			if(visitedObject != *heroPtr)
 			{
-				performObjectInteraction(visitedObject, hPtr);
+				performObjectInteraction(visitedObject, heroPtr);
 				ret = true;
 			}
 		}
 	}
-	if(hPtr) //we could have lost hero after last move
+	if(heroPtr) //we could have lost hero after last move
 	{
-		ret = ret || (dst == hPtr->visitablePos());
+		ret = ret || (dst == heroPtr->visitablePos());
 
-		if(startHpos == hPtr->visitablePos() && !ret) //we didn't move and didn't reach the target
+		if(startHpos == heroPtr->visitablePos() && !ret) //we didn't move and didn't reach the target
 		{
 			throw cannotFulfillGoalException("Invalid path found!");
 		}
 
-		logAi->debug("Hero %s moved from %s to %s. Returning %d.", hPtr->getNameTranslated(), startHpos.toString(), hPtr->visitablePos().toString(), ret);
+		logAi->debug("Hero %s moved from %s to %s. Returning %d.", heroPtr->getNameTranslated(), startHpos.toString(), heroPtr->visitablePos().toString(), ret);
 	}
 	return ret;
 }
@@ -1433,7 +1431,7 @@ void AIGateway::executeActionAsync(const std::string & description, const std::f
 
 void AIGateway::lostHero(HeroPtr h)
 {
-	logAi->debug("I lost my hero %s. It's best to forget and move on.", h.name());
+	logAi->debug("I lost my hero %s. It's best to forget and move on.", h.nameOrDefault());
 	nullkiller->invalidatePathfinderData();
 }
 

+ 2 - 2
AI/Nullkiller2/AIGateway.h

@@ -201,10 +201,10 @@ public:
 	void pickBestCreatures(const CArmedInstance * army, const CArmedInstance * source); //called when we can't find a slot for new stack
 
 	void moveCreaturesToHero(const CGTownInstance * t);
-	void performObjectInteraction(const CGObjectInstance * obj, HeroPtr h);
+	void performObjectInteraction(const CGObjectInstance * obj, HeroPtr heroPtr);
 	bool makePossibleUpgrades(const CArmedInstance * obj);
 
-	bool moveHeroToTile(int3 dst, HeroPtr hPtr);
+	bool moveHeroToTile(int3 dst, const HeroPtr & heroPtr);
 	void buildStructure(const CGTownInstance * t, BuildingID building);
 
 	void lostHero(HeroPtr h); //should remove all references to hero (assigned tasks and so on)

+ 33 - 54
AI/Nullkiller2/AIUtility.cpp

@@ -46,13 +46,11 @@ ObjectIdRef::operator bool() const
 ObjectIdRef::ObjectIdRef(ObjectInstanceID _id)
 	: id(_id)
 {
-
 }
 
 ObjectIdRef::ObjectIdRef(const CGObjectInstance * obj)
 	: id(obj->id)
 {
-
 }
 
 bool ObjectIdRef::operator<(const ObjectIdRef & rhs) const
@@ -60,93 +58,77 @@ bool ObjectIdRef::operator<(const ObjectIdRef & rhs) const
 	return id < rhs.id;
 }
 
-HeroPtr::HeroPtr(const CGHeroInstance * H)
+HeroPtr::HeroPtr(const CGHeroInstance * input, std::shared_ptr<CPlayerSpecificInfoCallback> cpsic)
+	: hero(input), cpsic(cpsic)
 {
-	if(!H)
-	{
-		//init from nullptr should equal to default init
-		*this = HeroPtr();
-		return;
-	}
-
-	hero = H;
-	heroId = H->id;
-//	infosCount[ai->playerID][hid]++;
 }
 
-HeroPtr::HeroPtr()
+bool HeroPtr::operator<(const HeroPtr & rhs) const
 {
-	hero = nullptr;
-	heroId = ObjectInstanceID();
+	return idOrNone() < rhs.idOrNone();
 }
 
-HeroPtr::~HeroPtr()
+ObjectInstanceID HeroPtr::idOrNone() const
 {
-//	if(hid >= 0)
-//		infosCount[ai->playerID][hid]--;
+	if (hero)
+		return hero->id;
+	return ObjectInstanceID::NONE;
 }
 
-bool HeroPtr::operator<(const HeroPtr & rhs) const
+std::string HeroPtr::nameOrDefault() const
 {
-	return heroId < rhs.heroId;
+	if (hero)
+		return hero->getNameTextID();
+	return "<NO HERO>";
 }
 
-std::string HeroPtr::name() const
+const CGHeroInstance * HeroPtr::get() const
 {
-	if (hero)
-		return hero->getNameTextID();
-	else
-		return "<NO HERO>";
+	assert(isValid());
+	return hero;
 }
 
-const CGHeroInstance * HeroPtr::get(const CPlayerSpecificInfoCallback * cpsic, bool doWeExpectNull) const
+bool HeroPtr::validate() const
 {
-	//TODO? check if these all assertions every time we get info about hero affect efficiency
-	//
-	//behave terribly when attempting unauthorized access to hero that is not ours (or was lost)
-	assert(doWeExpectNull || hero);
+	// TODO: check if these all assertions every time we get info about hero affect efficiency
+	// behave terribly when attempting unauthorized access to hero that is not ours (or was lost)
 
 	if(hero)
 	{
-		const auto *obj = cpsic->getObj(heroId);
+		const auto *obj = cpsic->getObj(hero->id);
 		//const bool owned = obj && obj->tempOwner == ai->playerID;
 
-		if(doWeExpectNull && !obj)
-		{
-			return nullptr;
-		}
-		else
-		{
-			assert(obj);
-			//assert(owned);
-		}
+		if(!obj)
+			return false;
+
+		assert(hero == obj);
+		return true;
+		//assert(owned);
 	}
 
-	return hero;
+	return false;
 }
 
 const CGHeroInstance * HeroPtr::operator->() const
 {
-	// return get();
-	return nullptr;
+	assert(isValid());
+	return hero;
 }
 
 bool HeroPtr::isValid() const
 {
-	// return get(true);
-	return false;
+	return validate();
 }
 
 const CGHeroInstance * HeroPtr::operator*() const
 {
-	// return get();
-	return nullptr;
+	assert(isValid());
+	return hero;
 }
 
 bool HeroPtr::operator==(const HeroPtr & rhs) const
 {
-	// return hero == rhs.get(true);
-	return false;
+	return hero == rhs.get();
 }
 
 bool isSafeToVisit(const CGHeroInstance * h, const CCreatureSet * heroArmy, uint64_t dangerStrength, float safeAttackRatio)
@@ -184,12 +166,9 @@ bool isObjectRemovable(const CGObjectInstance * obj)
 	case Obj::SHIPWRECK_SURVIVOR:
 	case Obj::SPELL_SCROLL:
 		return true;
-		break;
 	default:
 		return false;
-		break;
 	}
-
 }
 
 bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater)
@@ -733,7 +712,7 @@ bool shouldVisit(const Nullkiller * aiNk, const CGHeroInstance * hero, const CGO
 		break;
 	case Obj::TREE_OF_KNOWLEDGE:
 	{
-		if(aiNk->heroManager->getHeroRole(HeroPtr(hero)) == HeroRole::SCOUT)
+		if(aiNk->heroManager->getHeroRole(hero) == HeroRole::SCOUT)
 			return false;
 
 		TResources myRes = aiNk->getFreeResources();

+ 7 - 7
AI/Nullkiller2/AIUtility.h

@@ -76,15 +76,13 @@ enum HeroRole
 
 struct DLL_EXPORT HeroPtr
 {
+private:
 	const CGHeroInstance * hero;
-	ObjectInstanceID heroId;
+	std::shared_ptr<CPlayerSpecificInfoCallback> cpsic;
+	bool validate() const;
 
 public:
-	std::string name() const;
-
-	explicit HeroPtr();
-	explicit HeroPtr(const CGHeroInstance * H);
-	~HeroPtr();
+	explicit HeroPtr(const CGHeroInstance * input, std::shared_ptr<CPlayerSpecificInfoCallback> cpsic);
 
 	explicit operator bool() const
 	{
@@ -100,7 +98,9 @@ public:
 		return !(*this == rhs);
 	}
 
-	const CGHeroInstance * get(const CPlayerSpecificInfoCallback * cpsic, bool doWeExpectNull = false) const;
+	ObjectInstanceID idOrNone() const;
+	std::string nameOrDefault() const;
+	const CGHeroInstance * get() const;
 	bool isValid() const;
 };
 

+ 2 - 7
AI/Nullkiller2/Analyzers/DangerHitMapAnalyzer.cpp

@@ -20,11 +20,6 @@ namespace NK2AI
 
 const HitMapInfo HitMapInfo::NoThreat;
 
-double HitMapInfo::value() const
-{
-	return danger / std::sqrt(turn / 3.0f + 1);
-}
-
 void logHitmap(PlayerColor playerID, DangerHitMapAnalyzer & data)
 {
 #if NK2AI_TRACE_LEVEL >= 1
@@ -137,7 +132,7 @@ void DangerHitMapAnalyzer::updateHitMap()
 
 				HitMapInfo newThreat;
 
-				newThreat.heroPtr = HeroPtr(path.targetHero);
+				newThreat.heroPtr = HeroPtr(path.targetHero, aiNk->cc);
 				newThreat.turn = path.turn();
 				newThreat.threat = path.getHeroStrength() * (1 - path.movementCost() / 2.0);
 				// TODO: Mircea: Why is this danger calculated so differently than FuzzyHelper::evaluateDanger?
@@ -167,7 +162,7 @@ void DangerHitMapAnalyzer::updateHitMap()
 						auto & threats = townThreats[obj->id];
 						auto threat = std::find_if(threats.begin(), threats.end(), [&](const HitMapInfo & i) -> bool
 							{
-								return i.heroPtr.heroId == path.targetHero->id;
+								return i.heroPtr.idOrNone() == path.targetHero->id;
 							});
 
 						if(threat == threats.end())

+ 12 - 23
AI/Nullkiller2/Analyzers/DangerHitMapAnalyzer.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "../AIUtility.h"
+#include "../Pathfinding/AIPathfinder.h"
 
 namespace NK2AI
 {
@@ -20,40 +21,28 @@ struct HitMapInfo
 {
 	static const HitMapInfo NoThreat;
 
-	uint64_t danger;
-	uint8_t turn;
-	float threat;
-	HeroPtr heroPtr;
+	uint64_t danger = 0;
+	uint8_t turn = PathfinderSettings::MaxTurnDistanceLimit;
+	float threat = 0.f;
+	HeroPtr heroPtr = HeroPtr(nullptr, nullptr);
 
-	HitMapInfo()
+	double value() const
 	{
-		reset();
+		return danger / std::sqrt(turn / 3.0f + 1);
 	}
-
-	void reset()
-	{
-		danger = 0;
-		turn = 255;
-		threat = 0;
-		heroPtr = HeroPtr();
-	}
-
-	double value() const;
 };
 
 struct HitMapNode
 {
-	HitMapInfo maximumDanger;
-	HitMapInfo fastestDanger;
-
+	HitMapInfo maximumDanger = HitMapInfo::NoThreat;
+	HitMapInfo fastestDanger = HitMapInfo::NoThreat;
 	const CGTownInstance * closestTown = nullptr;
 
-	HitMapNode() = default;
-
 	void reset()
 	{
-		maximumDanger.reset();
-		fastestDanger.reset();
+		maximumDanger = HitMapInfo::NoThreat;
+		fastestDanger = HitMapInfo::NoThreat;
+		closestTown = nullptr;
 	}
 };
 

+ 19 - 14
AI/Nullkiller2/Analyzers/HeroManager.cpp

@@ -62,7 +62,7 @@ const SecondarySkillEvaluator HeroManager::scountSkillsScores = SecondarySkillEv
 
 float HeroManager::evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const
 {
-	auto role = getHeroRole(HeroPtr(hero));
+	auto role = getHeroRole(hero);
 
 	if(role == HeroRole::MAIN)
 		return wariorSkillsScores.evaluateSecSkill(hero, skill);
@@ -134,26 +134,31 @@ void HeroManager::update()
 	{
 		if(hero->patrol.patrolling)
 		{
-			heroToRoleMap[HeroPtr(hero)] = HeroRole::MAIN;
+			heroToRoleMap[HeroPtr(hero, aiNk->cc)] = HeroRole::MAIN;
 		}
 		else
 		{
-			heroToRoleMap[HeroPtr(hero)] = (globalMainCount--) > 0 ? HeroRole::MAIN : HeroRole::SCOUT;
+			heroToRoleMap[HeroPtr(hero, aiNk->cc)] = (globalMainCount--) > 0 ? HeroRole::MAIN : HeroRole::SCOUT;
 		}
 	}
 
 	for(auto hero : myHeroes)
 	{
-		logAi->trace("Hero %s has role %s", hero->getNameTranslated(), heroToRoleMap[HeroPtr(hero)] == HeroRole::MAIN ? "main" : "scout");
+		logAi->trace("Hero %s has role %s", hero->getNameTranslated(), heroToRoleMap[HeroPtr(hero, aiNk->cc)] == HeroRole::MAIN ? "main" : "scout");
 	}
 }
 
-HeroRole HeroManager::getHeroRole(const HeroPtr & hPtr) const
+HeroRole HeroManager::getHeroRole(const  CGHeroInstance * hero) const
 {
-	if (heroToRoleMap.find(hPtr) != heroToRoleMap.end())
-		return heroToRoleMap.at(hPtr);
-	else
-		return HeroRole::SCOUT;
+	return getHeroRole(HeroPtr(hero, aiNk->cc));
+}
+
+// TODO: Mircea: Do we need this map on HeroPtr or is enough just on hero?
+HeroRole HeroManager::getHeroRole(const HeroPtr & heroPtr) const
+{
+	if(heroToRoleMap.find(heroPtr) != heroToRoleMap.end())
+		return heroToRoleMap.at(heroPtr);
+	return HeroRole::SCOUT;
 }
 
 const std::map<HeroPtr, HeroRole> & HeroManager::getHeroToRoleMap() const
@@ -161,9 +166,9 @@ const std::map<HeroPtr, HeroRole> & HeroManager::getHeroToRoleMap() const
 	return heroToRoleMap;
 }
 
-int HeroManager::selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const
+int HeroManager::selectBestSkill(const HeroPtr & heroPtr, const std::vector<SecondarySkill> & skills) const
 {
-	auto role = getHeroRole(hero);
+	auto role = getHeroRole(heroPtr);
 	auto & evaluator = role == HeroRole::MAIN ? wariorSkillsScores : scountSkillsScores;
 
 	int result = 0;
@@ -171,7 +176,7 @@ int HeroManager::selectBestSkill(const HeroPtr & hero, const std::vector<Seconda
 
 	for(int i = 0; i < skills.size(); i++)
 	{
-		auto score = evaluator.evaluateSecSkill(hero.get(aiNk->cc.get()), skills[i]);
+		auto score = evaluator.evaluateSecSkill(heroPtr.get(), skills[i]);
 
 		if(score > resultScore)
 		{
@@ -181,7 +186,7 @@ int HeroManager::selectBestSkill(const HeroPtr & hero, const std::vector<Seconda
 
 		logAi->trace(
 			"Hero %s is proposed to learn %d with score %f",
-			hero.name(),
+			heroPtr.nameOrDefault(),
 			skills[i].toEnum(),
 			score);
 	}
@@ -302,7 +307,7 @@ const CGHeroInstance * HeroManager::findWeakHeroToDismiss(uint64_t armyLimit, co
 	{
 		if(aiNk->getHeroLockedReason(existingHero) == HeroLockedReason::DEFENCE
 			|| existingHero->getArmyStrength() >armyLimit
-			|| getHeroRole(HeroPtr(existingHero)) == HeroRole::MAIN
+			|| getHeroRole(existingHero) == HeroRole::MAIN
 			|| existingHero->movementPointsRemaining()
 			|| (townToSpare != nullptr && existingHero->getVisitedTown() == townToSpare)
 			|| existingHero->artifactsWorn.size() > (existingHero->hasSpellbook() ? 2 : 1))

+ 3 - 2
AI/Nullkiller2/Analyzers/HeroManager.h

@@ -50,8 +50,9 @@ private:
 public:
 	HeroManager(CCallback * cc, const Nullkiller * aiNk) : cc(cc), aiNk(aiNk) {}
 	const std::map<HeroPtr, HeroRole> & getHeroToRoleMap() const;
-	HeroRole getHeroRole(const HeroPtr & hPtr) const;
-	int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const;
+	HeroRole getHeroRole(const CGHeroInstance * hero) const;
+	HeroRole getHeroRole(const HeroPtr & heroPtr) const;
+	int selectBestSkill(const HeroPtr & heroPtr, const std::vector<SecondarySkill> & skills) const;
 	void update();
 	float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const;
 	float evaluateHero(const CGHeroInstance * hero) const;

+ 1 - 1
AI/Nullkiller2/Analyzers/ObjectClusterizer.cpp

@@ -427,7 +427,7 @@ void ObjectClusterizer::clusterizeObject(
 		logAi->trace("ObjectClusterizer Checking path %s", path.toString());
 #endif
 
-		if(aiNk->heroManager->getHeroRole(HeroPtr(path.targetHero)) == HeroRole::SCOUT)
+		if(aiNk->heroManager->getHeroRole(path.targetHero) == HeroRole::SCOUT)
 		{
 			// TODO: Mircea: Shouldn't this be linked with scoutHeroTurnDistanceLimit?
 			// TODO: Mircea: Move to constant

+ 1 - 1
AI/Nullkiller2/Behaviors/BuyArmyBehavior.cpp

@@ -53,7 +53,7 @@ Goals::TGoalVec BuyArmyBehavior::decompose(const Nullkiller * aiNk) const
 
 		for(const CGHeroInstance * targetHero : heroes)
 		{
-			if(aiNk->heroManager->getHeroRole(HeroPtr(targetHero)) == HeroRole::MAIN)
+			if(aiNk->heroManager->getHeroRole(targetHero) == HeroRole::MAIN)
 			{
 				auto reinforcement = aiNk->armyManager->howManyReinforcementsCanGet(
 					targetHero,

+ 3 - 3
AI/Nullkiller2/Behaviors/CaptureObjectsBehavior.cpp

@@ -82,7 +82,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(
 		if (hero->getOwner() != nullkiller->playerID)
 			continue;
 
-		if(nullkiller->heroManager->getHeroRole(HeroPtr(hero)) == HeroRole::SCOUT
+		if(nullkiller->heroManager->getHeroRole(hero) == HeroRole::SCOUT
 			&& (path.getTotalDanger() == 0 || path.turn() > 0)
 			&& path.exchangeCount > 1)
 		{
@@ -135,7 +135,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(
 
 			sharedPtr.reset(newWay);
 
-			auto heroRole = nullkiller->heroManager->getHeroRole(HeroPtr(path.targetHero));
+			auto heroRole = nullkiller->heroManager->getHeroRole(path.targetHero);
 
 			auto & closestWay = closestWaysByRole[heroRole];
 
@@ -154,7 +154,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(
 
 	for(auto way : waysToVisitObj)
 	{
-		auto heroRole = nullkiller->heroManager->getHeroRole(HeroPtr(way->getPath().targetHero));
+		auto heroRole = nullkiller->heroManager->getHeroRole(way->getPath().targetHero);
 		auto closestWay = closestWaysByRole[heroRole];
 
 		if(closestWay)

+ 2 - 2
AI/Nullkiller2/Behaviors/DefenceBehavior.cpp

@@ -89,7 +89,7 @@ void handleCounterAttack(
 		&& (threat.danger == maximumDanger.danger || threat.turn < maximumDanger.turn))
 	{
 		auto heroCapturingPaths = aiNk->pathfinder->getPathInfo(threat.heroPtr->visitablePos());
-		auto goals = CaptureObjectsBehavior::getVisitGoals(heroCapturingPaths, aiNk, threat.heroPtr.get(aiNk->cc.get()));
+		auto goals = CaptureObjectsBehavior::getVisitGoals(heroCapturingPaths, aiNk, threat.heroPtr.get());
 
 		for(int i = 0; i < heroCapturingPaths.size(); i++)
 		{
@@ -132,7 +132,7 @@ bool handleGarrisonHeroFromPreviousTurn(const CGTownInstance * town, Goals::TGoa
 
 			return false;
 		}
-		else if(aiNk->heroManager->getHeroRole(HeroPtr(town->getGarrisonHero())) == HeroRole::MAIN)
+		else if(aiNk->heroManager->getHeroRole(town->getGarrisonHero()) == HeroRole::MAIN)
 		{
 			auto armyDismissLimit = 1000;
 			auto heroToDismiss = aiNk->heroManager->findWeakHeroToDismiss(armyDismissLimit);

+ 4 - 4
AI/Nullkiller2/Behaviors/GatherArmyBehavior.cpp

@@ -43,7 +43,7 @@ Goals::TGoalVec GatherArmyBehavior::decompose(const Nullkiller * aiNk) const
 
 	for(const CGHeroInstance * hero : heroes)
 	{
-		if(aiNk->heroManager->getHeroRole(HeroPtr(hero)) == HeroRole::MAIN)
+		if(aiNk->heroManager->getHeroRole(hero) == HeroRole::MAIN)
 		{
 			vstd::concatenate(tasks, deliverArmyToHero(aiNk, hero));
 		}
@@ -121,7 +121,7 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const Nullkiller * aiNk, c
 		{
 			if(!node.targetHero) continue;
 
-			auto heroRole = aiNk->heroManager->getHeroRole(HeroPtr(node.targetHero));
+			auto heroRole = aiNk->heroManager->getHeroRole(node.targetHero);
 
 			if(heroRole == HeroRole::MAIN)
 			{
@@ -239,7 +239,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * aiNk, const C
 
 	for(const AIPath & path : paths)
 	{
-		auto heroRole = aiNk->heroManager->getHeroRole(HeroPtr(path.targetHero));
+		auto heroRole = aiNk->heroManager->getHeroRole(path.targetHero);
 
 		if(heroRole == HeroRole::MAIN && path.turn() < aiNk->settings->getScoutHeroTurnDistanceLimit())
 			hasMainAround = true;
@@ -290,7 +290,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * aiNk, const C
 		auto upgrade = aiNk->armyManager->calculateCreaturesUpgrade(path.heroArmy, upgrader, availableResources);
 
 		if(!upgrader->getGarrisonHero()
-		   && (hasMainAround || aiNk->heroManager->getHeroRole(HeroPtr(path.targetHero)) == HeroRole::MAIN))
+		   && (hasMainAround || aiNk->heroManager->getHeroRole(path.targetHero) == HeroRole::MAIN))
 		{
 			ArmyUpgradeInfo armyToGetOrBuy;
 

+ 1 - 1
AI/Nullkiller2/Behaviors/RecruitHeroBehavior.cpp

@@ -52,7 +52,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * aiNk) const
 		float visitabilityRatio = 0;
 		for(const auto & [hero, role] : ourHeroes)
 		{
-			if(aiNk->dangerHitMap->getClosestTown(hero.get(aiNk->cc.get())->visitablePos()) == town)
+			if(aiNk->dangerHitMap->getClosestTown(hero.get()->visitablePos()) == town)
 				visitabilityRatio += 1.0f / ourHeroes.size();
 		}
 

+ 1 - 1
AI/Nullkiller2/Behaviors/StartupBehavior.cpp

@@ -172,7 +172,7 @@ Goals::TGoalVec StartupBehavior::decompose(const Nullkiller * aiNk) const
 				auto garrisonHeroScore = aiNk->heroManager->evaluateHero(garrisonHero);
 
 				if(visitingHeroScore > garrisonHeroScore
-					|| (aiNk->heroManager->getHeroRole(HeroPtr(garrisonHero)) == HeroRole::SCOUT && aiNk->heroManager->getHeroRole(HeroPtr(visitingHero)) == HeroRole::MAIN))
+					|| (aiNk->heroManager->getHeroRole(garrisonHero) == HeroRole::SCOUT && aiNk->heroManager->getHeroRole(visitingHero) == HeroRole::MAIN))
 				{
 					if(canRecruitHero || aiNk->armyManager->howManyReinforcementsCanGet(visitingHero, garrisonHero) > 200)
 					{

+ 4 - 6
AI/Nullkiller2/Engine/Nullkiller.cpp

@@ -265,7 +265,6 @@ void Nullkiller::updateState()
 	else
 	{
 		memory->removeInvisibleObjects(cc.get());
-
 		dangerHitMap->updateHitMap();
 		dangerHitMap->calculateTileOwners();
 
@@ -281,7 +280,7 @@ void Nullkiller::updateState()
 			if(getHeroLockedReason(hero) == HeroLockedReason::DEFENCE)
 				continue;
 
-			activeHeroes[hero] = heroManager->getHeroRole(HeroPtr(hero));
+			activeHeroes[hero] = heroManager->getHeroRole(hero);
 		}
 
 		PathfinderSettings cfg;
@@ -306,14 +305,13 @@ void Nullkiller::updateState()
 		{
 			pathfinder->updateGraphs(
 				activeHeroes,
-				scanDepth == ScanDepth::SMALL ? 255 : 10,
-				scanDepth == ScanDepth::ALL_FULL ? 255 : 3);
+				scanDepth == ScanDepth::SMALL ? PathfinderSettings::MaxTurnDistanceLimit : 10,
+				scanDepth == ScanDepth::ALL_FULL ? PathfinderSettings::MaxTurnDistanceLimit : 3);
 		}
 
 		makingTurnInterrupption.interruptionPoint();
 
 		objectClusterizer->clusterize();
-
 		pathfinderInvalidated = false;
 	}
 
@@ -549,7 +547,7 @@ bool Nullkiller::areAffectedObjectsPresent(Goals::TTask task) const
 
 HeroRole Nullkiller::getTaskRole(Goals::TTask task) const
 {
-	HeroPtr heroPtr(task->getHero());
+	HeroPtr heroPtr(task->getHero(), cc);
 	HeroRole heroRole = HeroRole::MAIN;
 
 	if(heroPtr.isValid())

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

@@ -119,7 +119,7 @@ public:
 	bool makeTurnHelperPriorityPass(Goals::TGoalVec& tempResults, int passIndex);
 	bool isActive(const CGHeroInstance * hero) const { return activeHero == hero; }
 	bool isHeroLocked(const CGHeroInstance * hero) const;
-	HeroPtr getActiveHero() { return HeroPtr(activeHero); }
+	HeroPtr getActiveHero() { return HeroPtr(activeHero, cc); }
 	HeroLockedReason getHeroLockedReason(const CGHeroInstance * hero) const;
 	int3 getTargetTile() const { return targetTile; }
 	ObjectInstanceID getTargetObject() const { return targetObject; }

+ 7 - 7
AI/Nullkiller2/Engine/PriorityEvaluator.cpp

@@ -778,7 +778,7 @@ public:
 
 		evaluationContext.addNonCriticalStrategicalValue(2.0f * armyStrength / (float)heroExchange.hero->getArmyStrength());
 		evaluationContext.conquestValue += 2.0f * armyStrength / (float)heroExchange.hero->getArmyStrength();
-		evaluationContext.heroRole = evaluationContext.evaluator.aiNk->heroManager->getHeroRole(HeroPtr(heroExchange.hero));
+		evaluationContext.heroRole = evaluationContext.evaluator.aiNk->heroManager->getHeroRole(heroExchange.hero);
 		evaluationContext.isExchange = true;
 	}
 };
@@ -898,7 +898,7 @@ public:
 		if(defendTown.getTurn() > 0 && defendTown.isCounterAttack())
 		{
 			auto ourSpeed = defendTown.hero->movementPointsLimit(true);
-			auto enemySpeed = threat.heroPtr.get(evaluationContext.evaluator.aiNk->cc.get())->movementPointsLimit(true);
+			auto enemySpeed = threat.heroPtr.get()->movementPointsLimit(true);
 
 			if(enemySpeed > ourSpeed) multiplier *= 0.7f;
 		}
@@ -958,7 +958,7 @@ public:
 		float highestCostForSingleHero = 0;
 		for(auto pair : costsPerHero)
 		{
-			auto role = evaluationContext.evaluator.aiNk->heroManager->getHeroRole(HeroPtr(pair.first));
+			auto role = evaluationContext.evaluator.aiNk->heroManager->getHeroRole(pair.first);
 			evaluationContext.movementCostByRole[role] += pair.second;
 			if (pair.second > highestCostForSingleHero)
 				highestCostForSingleHero = pair.second;
@@ -975,7 +975,7 @@ public:
 		auto army = path.heroArmy;
 
 		const CGObjectInstance * target = aiNk->cc->getObj((ObjectInstanceID)task->objid, false);
-		auto heroRole = evaluationContext.evaluator.aiNk->heroManager->getHeroRole(HeroPtr(hero));
+		auto heroRole = evaluationContext.evaluator.aiNk->heroManager->getHeroRole(hero);
 
 		if(heroRole == HeroRole::MAIN)
 			evaluationContext.heroRole = heroRole;
@@ -1056,7 +1056,7 @@ public:
 		std::shared_ptr<ObjectCluster> cluster = clusterGoal.getCluster();
 
 		auto hero = clusterGoal.hero;
-		auto role = evaluationContext.evaluator.aiNk->heroManager->getHeroRole(HeroPtr(hero));
+		auto role = evaluationContext.evaluator.aiNk->heroManager->getHeroRole(hero);
 
 		std::vector<std::pair<ObjectInstanceID, ClusterObjectInfo>> objects(cluster->objects.begin(), cluster->objects.end());
 
@@ -1113,7 +1113,7 @@ public:
 
 		if(garrisonHero && swapCommand.getLockingReason() == HeroLockedReason::DEFENCE)
 		{
-			auto defenderRole = evaluationContext.evaluator.aiNk->heroManager->getHeroRole(HeroPtr(garrisonHero));
+			auto defenderRole = evaluationContext.evaluator.aiNk->heroManager->getHeroRole(garrisonHero);
 			auto mpLeft = garrisonHero->movementPointsRemaining() / (float)garrisonHero->movementPointsLimit(true);
 
 			evaluationContext.movementCost += mpLeft;
@@ -1142,7 +1142,7 @@ public:
 		Goals::DismissHero & dismissCommand = dynamic_cast<Goals::DismissHero &>(*task);
 		const CGHeroInstance * dismissedHero = dismissCommand.getHero();
 
-		auto role = aiNk->heroManager->getHeroRole(HeroPtr(dismissedHero));
+		auto role = aiNk->heroManager->getHeroRole(dismissedHero);
 		auto mpLeft = dismissedHero->movementPointsRemaining();
 
 		evaluationContext.movementCost += mpLeft;

+ 1 - 1
AI/Nullkiller2/Goals/AdventureSpellCast.cpp

@@ -67,7 +67,7 @@ void AdventureSpellCast::accept(AIGateway * aiGw)
 	if(town && townPortalEffect)
 	{
 		// visit town
-		aiGw->moveHeroToTile(town->visitablePos(), HeroPtr(hero));
+		aiGw->moveHeroToTile(town->visitablePos(), HeroPtr(hero, aiGw->cc));
 	}
 
 	ccTl->waitTillRealize = wait;

+ 1 - 1
AI/Nullkiller2/Goals/BuyArmy.cpp

@@ -99,7 +99,7 @@ void BuyArmy::accept(AIGateway * aiGw)
 
 	if(town->getVisitingHero() && !town->getGarrisonHero())
 	{
-		aiGw->moveHeroToTile(town->visitablePos(), HeroPtr(town->getVisitingHero()));
+		aiGw->moveHeroToTile(town->visitablePos(), HeroPtr(town->getVisitingHero(), aiGw->cc));
 	}
 }
 

+ 1 - 1
AI/Nullkiller2/Goals/ExchangeSwapTownHeroes.cpp

@@ -84,7 +84,7 @@ void ExchangeSwapTownHeroes::accept(AIGateway * aiGw)
 		ccTl->swapGarrisonHero(town);
 
 	aiGw->makePossibleUpgrades(town);
-	aiGw->moveHeroToTile(town->visitablePos(), HeroPtr(getGarrisonHero()));
+	aiGw->moveHeroToTile(town->visitablePos(), HeroPtr(getGarrisonHero(), aiGw->cc));
 
 	auto upperArmy = town->getUpperArmy();
 	

+ 6 - 6
AI/Nullkiller2/Goals/ExecuteHeroChain.cpp

@@ -109,11 +109,11 @@ void ExecuteHeroChain::accept(AIGateway * aiGw)
 		auto  * node = &chainPath.nodes[i];
 
 		const CGHeroInstance * hero = node->targetHero;
-		HeroPtr heroPtr(hero);
+		HeroPtr heroPtr(hero, aiGw->cc);
 
 		if(!heroPtr.isValid())
 		{
-			logAi->error("Hero %s was lost. Exit hero chain.", heroPtr.name());
+			logAi->error("Hero %s was lost. Exit hero chain.", heroPtr.nameOrDefault());
 
 			return;
 		}
@@ -150,7 +150,7 @@ void ExecuteHeroChain::accept(AIGateway * aiGw)
 					
 					if(!heroPtr.isValid())
 					{
-						logAi->error("Hero %s was lost trying to execute special action. Exit hero chain.", heroPtr.name());
+						logAi->error("Hero %s was lost trying to execute special action. Exit hero chain.", heroPtr.nameOrDefault());
 
 						return;
 					}
@@ -231,7 +231,7 @@ void ExecuteHeroChain::accept(AIGateway * aiGw)
 					{
 						if(!heroPtr.isValid())
 						{
-							logAi->error("Hero %s was lost. Exit hero chain.", heroPtr.name());
+							logAi->error("Hero %s was lost. Exit hero chain.", heroPtr.nameOrDefault());
 
 							return;
 						}
@@ -277,7 +277,7 @@ void ExecuteHeroChain::accept(AIGateway * aiGw)
 		{
 			if(!heroPtr.isValid())
 			{
-				logAi->debug("Hero %s was killed while attempting to reach %s", heroPtr.name(), node->coord.toString());
+				logAi->debug("Hero %s was killed while attempting to reach %s", heroPtr.nameOrDefault(), node->coord.toString());
 
 				return;
 			}
@@ -303,7 +303,7 @@ bool ExecuteHeroChain::moveHeroToTile(AIGateway * aiGw, const CGHeroInstance * h
 		return true;
 	}
 
-	return aiGw->moveHeroToTile(tile, HeroPtr(hero));
+	return aiGw->moveHeroToTile(tile, HeroPtr(hero, aiGw->cc));
 }
 
 }

+ 1 - 1
AI/Nullkiller2/Goals/ExploreNeighbourTile.cpp

@@ -61,7 +61,7 @@ void ExploreNeighbourTile::accept(AIGateway * aiGw)
 
 		auto danger = aiGw->nullkiller->dangerEvaluator->evaluateDanger(target, hero, true);
 
-		if(danger > 0 || !aiGw->moveHeroToTile(target, HeroPtr(hero)))
+		if(danger > 0 || !aiGw->moveHeroToTile(target, HeroPtr(hero, aiGw->cc)))
 		{
 			return;
 		}

+ 2 - 2
AI/Nullkiller2/Helpers/ExplorationHelper.cpp

@@ -144,7 +144,7 @@ void ExplorationHelper::scanTile(const int3 & tile)
 {
 	if(tile == ourPos
 		|| !aiNk->cc->getTile(tile, false)
-		|| !aiNk->pathfinder->isTileAccessible(HeroPtr(hero), tile)) //shouldn't happen, but it does
+		|| !aiNk->pathfinder->isTileAccessible(HeroPtr(hero, aiNk->cc), tile)) //shouldn't happen, but it does
 		return;
 
 	int tilesDiscovered = howManyTilesWillBeDiscovered(tile);
@@ -224,7 +224,7 @@ bool ExplorationHelper::hasReachableNeighbor(const int3 & pos) const
 		{
 			auto isAccessible = useCPathfinderAccessibility
 				? aiNk->getPathsInfo(hero)->getPathInfo(tile)->reachable()
-				: aiNk->pathfinder->isTileAccessible(HeroPtr(hero), tile);
+				: aiNk->pathfinder->isTileAccessible(HeroPtr(hero, aiNk->cc), tile);
 
 			if(isAccessible)
 				return true;

+ 4 - 4
AI/Nullkiller2/Pathfinding/AINodeStorage.cpp

@@ -173,8 +173,8 @@ void AINodeStorage::clear()
 	heroChainPass = EHeroChainPass::INITIAL;
 	heroChainTurn = 0;
 	heroChainMaxTurns = 1;
-	turnDistanceLimit[HeroRole::MAIN] = 255;
-	turnDistanceLimit[HeroRole::SCOUT] = 255;
+	turnDistanceLimit[HeroRole::MAIN] = PathfinderSettings::MaxTurnDistanceLimit;;
+	turnDistanceLimit[HeroRole::SCOUT] = PathfinderSettings::MaxTurnDistanceLimit;;
 }
 
 std::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
@@ -1396,7 +1396,7 @@ bool AINodeStorage::isOtherChainBetter(
 	return false;
 }
 
-bool AINodeStorage::isTileAccessible(const HeroPtr & hero, const int3 & pos, const EPathfindingLayer layer) const
+bool AINodeStorage::isTileAccessible(const HeroPtr & heroPtr, const int3 & pos, const EPathfindingLayer layer) const
 {
 	auto chains = nodes.get(pos);
 
@@ -1406,7 +1406,7 @@ bool AINodeStorage::isTileAccessible(const HeroPtr & hero, const int3 & pos, con
 			&& node.layer == layer
 			&& node.action != EPathNodeAction::UNKNOWN 
 			&& node.actor
-			&& node.actor->hero == hero.hero)
+			&& node.actor->hero == heroPtr.get())
 		{
 			return true;
 		}

+ 1 - 1
AI/Nullkiller2/Pathfinding/AINodeStorage.h

@@ -266,7 +266,7 @@ public:
 
 	std::optional<AIPathNode *> getOrCreateNode(const int3 & coord, const EPathfindingLayer layer, const ChainActor * actor);
 	void calculateChainInfo(std::vector<AIPath> & result, const int3 & pos, bool isOnLand) const;
-	bool isTileAccessible(const HeroPtr & hero, const int3 & pos, const EPathfindingLayer layer) const;
+	bool isTileAccessible(const HeroPtr & heroPtr, const int3 & pos, const EPathfindingLayer layer) const;
 	void setHeroes(std::map<const CGHeroInstance *, HeroRole> heroes);
 	void setScoutTurnDistanceLimit(uint8_t distanceLimit) { turnDistanceLimit[HeroRole::SCOUT] = distanceLimit; }
 	void setMainTurnDistanceLimit(uint8_t distanceLimit) { turnDistanceLimit[HeroRole::MAIN] = distanceLimit; }

+ 4 - 2
AI/Nullkiller2/Pathfinding/AIPathfinder.h

@@ -22,6 +22,8 @@ class Nullkiller;
 
 struct PathfinderSettings
 {
+	static constexpr uint8_t MaxTurnDistanceLimit = 255;
+
 	bool useHeroChain;
 	uint8_t scoutTurnDistanceLimit;
 	uint8_t mainTurnDistanceLimit;
@@ -29,8 +31,8 @@ struct PathfinderSettings
 
 	PathfinderSettings()
 		:useHeroChain(false),
-		scoutTurnDistanceLimit(255),
-		mainTurnDistanceLimit(255),
+		scoutTurnDistanceLimit(MaxTurnDistanceLimit),
+		mainTurnDistanceLimit(MaxTurnDistanceLimit),
 		allowBypassObjects(true)
 	{ }
 };

+ 1 - 1
AI/Nullkiller2/Pathfinding/Actions/BattleAction.cpp

@@ -20,7 +20,7 @@ namespace AIPathfinding
 {
 	void BattleAction::execute(AIGateway * aiGw, const CGHeroInstance * hero) const
 	{
-		aiGw->moveHeroToTile(targetTile, HeroPtr(hero));
+		aiGw->moveHeroToTile(targetTile, HeroPtr(hero, aiGw->cc));
 	}
 
 	std::string BattleAction::toString() const

+ 1 - 1
AI/Nullkiller2/Pathfinding/Actions/QuestAction.cpp

@@ -52,7 +52,7 @@ namespace AIPathfinding
 
 	void QuestAction::execute(AIGateway * aiGw, const CGHeroInstance * hero) const
 	{
-		aiGw->moveHeroToTile(questInfo.getObject(aiGw->cc.get())->visitablePos(), HeroPtr(hero));
+		aiGw->moveHeroToTile(questInfo.getObject(aiGw->cc.get())->visitablePos(), HeroPtr(hero, aiGw->cc));
 	}
 
 	std::string QuestAction::toString() const