瀏覽代碼

Merge pull request #4220 from IvanSavenko/ai_optimize

[1.5.4] AI optimizations
Ivan Savenko 1 年之前
父節點
當前提交
11a3da3f4f

+ 2 - 2
AI/Nullkiller/AIGateway.cpp

@@ -649,7 +649,7 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
 				auto ratio = static_cast<float>(danger) / hero->getTotalStrength();
 
 				answer = topObj->id == goalObjectID; // no if we do not aim to visit this object
-				logAi->trace("Query hook: %s(%s) by %s danger ratio %f", target.toString(), topObj->getObjectName(), hero.name, ratio);
+				logAi->trace("Query hook: %s(%s) by %s danger ratio %f", target.toString(), topObj->getObjectName(), hero.name(), ratio);
 
 				if(cb->getObj(goalObjectID, false))
 				{
@@ -1574,7 +1574,7 @@ void AIGateway::requestActionASAP(std::function<void()> whatToDo)
 
 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.name());
 }
 
 void AIGateway::answerQuery(QueryID queryID, int selection)

+ 8 - 1
AI/Nullkiller/AIUtility.cpp

@@ -67,7 +67,6 @@ HeroPtr::HeroPtr(const CGHeroInstance * H)
 	}
 
 	h = H;
-	name = h->getNameTranslated();
 	hid = H->id;
 //	infosCount[ai->playerID][hid]++;
 }
@@ -89,6 +88,14 @@ bool HeroPtr::operator<(const HeroPtr & rhs) const
 	return hid < rhs.hid;
 }
 
+std::string HeroPtr::name() const
+{
+	if (h)
+		return h->getNameTextID();
+	else
+		return "<NO HERO>";
+}
+
 const CGHeroInstance * HeroPtr::get(bool doWeExpectNull) const
 {
 	return get(cb, doWeExpectNull);

+ 1 - 3
AI/Nullkiller/AIUtility.h

@@ -87,8 +87,7 @@ struct DLL_EXPORT HeroPtr
 	ObjectInstanceID hid;
 
 public:
-	std::string name;
-
+	std::string name() const;
 
 	HeroPtr();
 	HeroPtr(const CGHeroInstance * H);
@@ -117,7 +116,6 @@ public:
 	{
 		handler & h;
 		handler & hid;
-		handler & name;
 	}
 };
 

+ 2 - 1
AI/Nullkiller/Analyzers/HeroManager.cpp

@@ -176,7 +176,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,
+			hero.name(),
 			skills[i].toEnum(),
 			score);
 	}
@@ -204,6 +204,7 @@ float HeroManager::getFightingStrengthCached(const CGHeroInstance * hero) const
 {
 	auto cached = knownFightingStrength.find(hero->id);
 
+	//FIXME: fallback to hero->getFightingStrength() is VERY slow on higher difficulties (no object graph? map reveal?)
 	return cached != knownFightingStrength.end() ? cached->second : hero->getFightingStrength();
 }
 

+ 1 - 1
AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp

@@ -212,7 +212,7 @@ void CaptureObjectsBehavior::decomposeObjects(
 				vstd::concatenate(tasksLocal, getVisitGoals(paths, nullkiller, objToVisit, specificObjects));
 			}
 
-			std::lock_guard<std::mutex> lock(sync);
+			std::lock_guard<std::mutex> lock(sync); // FIXME: consider using tbb::parallel_reduce instead to avoid mutex overhead
 			vstd::concatenate(result, tasksLocal);
 		});
 }

+ 1 - 1
AI/Nullkiller/Goals/CaptureObject.h

@@ -31,7 +31,7 @@ namespace Goals
 		{
 			objid = obj->id.getNum();
 			tile = obj->visitablePos();
-			name = obj->getObjectName();
+			name = obj->typeName;
 		}
 
 		bool operator==(const CaptureObject & other) const override;

+ 5 - 5
AI/Nullkiller/Goals/ExecuteHeroChain.cpp

@@ -26,7 +26,7 @@ ExecuteHeroChain::ExecuteHeroChain(const AIPath & path, const CGObjectInstance *
 	if(obj)
 	{
 		objid = obj->id.getNum();
-		targetName = obj->getObjectName() + tile.toString();
+		targetName = obj->typeName + tile.toString();
 	}
 	else
 	{
@@ -106,7 +106,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 
 		if(!heroPtr.validAndSet())
 		{
-			logAi->error("Hero %s was lost. Exit hero chain.", heroPtr.name);
+			logAi->error("Hero %s was lost. Exit hero chain.", heroPtr.name());
 
 			return;
 		}
@@ -143,7 +143,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 					
 					if(!heroPtr.validAndSet())
 					{
-						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.name());
 
 						return;
 					}
@@ -204,7 +204,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 					{
 						if(!heroPtr.validAndSet())
 						{
-							logAi->error("Hero %s was lost. Exit hero chain.", heroPtr.name);
+							logAi->error("Hero %s was lost. Exit hero chain.", heroPtr.name());
 
 							return;
 						}
@@ -250,7 +250,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 		{
 			if(!heroPtr.validAndSet())
 			{
-				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.name(), node->coord.toString());
 
 				return;
 			}

+ 29 - 29
AI/Nullkiller/Helpers/ExplorationHelper.cpp

@@ -70,11 +70,8 @@ bool ExplorationHelper::scanMap()
 	int3 mapSize = cbp->getMapSize();
 	int perimeter = 2 * sightRadius * (mapSize.x + mapSize.y);
 
-	std::vector<int3> from;
-	std::vector<int3> to;
-
-	from.reserve(perimeter);
-	to.reserve(perimeter);
+	std::vector<int3> edgeTiles;
+	edgeTiles.reserve(perimeter);
 
 	foreach_tile_pos([&](const int3 & pos)
 		{
@@ -91,13 +88,13 @@ bool ExplorationHelper::scanMap()
 					});
 
 				if(hasInvisibleNeighbor)
-					from.push_back(pos);
+					edgeTiles.push_back(pos);
 			}
 		});
 
 	logAi->debug("Exploration scan visible area perimeter for hero %s", hero->getNameTranslated());
 
-	for(const int3 & tile : from)
+	for(const int3 & tile : edgeTiles)
 	{
 		scanTile(tile);
 	}
@@ -108,19 +105,36 @@ bool ExplorationHelper::scanMap()
 	}
 
 	allowDeadEndCancellation = false;
+	logAi->debug("Exploration scan all possible tiles for hero %s", hero->getNameTranslated());
+
+	boost::multi_array<ui8, 3> potentialTiles = *ts->fogOfWarMap;
+	std::vector<int3> tilesToExploreFrom = edgeTiles;
 
+	// WARNING: POTENTIAL BUG
+	// AI attempts to move to any tile within sight radius to reveal some new tiles
+	// however sight radius is circular, while this method assumes square radius
+	// standing on the edge of a square will NOT reveal tile in opposite corner
 	for(int i = 0; i < sightRadius; i++)
 	{
-		getVisibleNeighbours(from, to);
-		vstd::concatenate(from, to);
-		vstd::removeDuplicates(from);
-	}
+		std::vector<int3> newTilesToExploreFrom;
 
-	logAi->debug("Exploration scan all possible tiles for hero %s", hero->getNameTranslated());
+		for(const int3 & tile : tilesToExploreFrom)
+		{
+			foreach_neighbour(cbp, tile, [&](CCallback * cbp, int3 neighbour)
+			{
+				if(potentialTiles[neighbour.z][neighbour.x][neighbour.y])
+				{
+					newTilesToExploreFrom.push_back(neighbour);
+					potentialTiles[neighbour.z][neighbour.x][neighbour.y] = false;
+				}
+			});
+		}
+		for(const int3 & tile : newTilesToExploreFrom)
+		{
+			scanTile(tile);
+		}
 
-	for(const int3 & tile : from)
-	{
-		scanTile(tile);
+		std::swap(tilesToExploreFrom, newTilesToExploreFrom);
 	}
 
 	return !bestGoal->invalid();
@@ -172,20 +186,6 @@ void ExplorationHelper::scanTile(const int3 & tile)
 	}
 }
 
-void ExplorationHelper::getVisibleNeighbours(const std::vector<int3> & tiles, std::vector<int3> & out) const
-{
-	for(const int3 & tile : tiles)
-	{
-		foreach_neighbour(cbp, tile, [&](CCallback * cbp, int3 neighbour)
-			{
-				if((*(ts->fogOfWarMap))[neighbour.z][neighbour.x][neighbour.y])
-				{
-					out.push_back(neighbour);
-				}
-			});
-	}
-}
-
 int ExplorationHelper::howManyTilesWillBeDiscovered(const int3 & pos) const
 {
 	int ret = 0;

+ 0 - 1
AI/Nullkiller/Helpers/ExplorationHelper.h

@@ -46,7 +46,6 @@ public:
 private:
 	void scanTile(const int3 & tile);
 	bool hasReachableNeighbor(const int3 & pos) const;
-	void getVisibleNeighbours(const std::vector<int3> & tiles, std::vector<int3> & out) const;
 };
 
 }

+ 1 - 3
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -320,11 +320,9 @@ void AINodeStorage::calculateNeighbours(
 	const PathfinderConfig * pathfinderConfig,
 	const CPathfinderHelper * pathfinderHelper)
 {
-	std::vector<int3> accessibleNeighbourTiles;
+	NeighbourTilesVector accessibleNeighbourTiles;
 
 	result.clear();
-	accessibleNeighbourTiles.reserve(8);
-
 	pathfinderHelper->calculateNeighbourTiles(accessibleNeighbourTiles, source);
 
 	const AIPathNode * srcNode = getAINode(source.node);

+ 5 - 1
AI/Nullkiller/Pathfinding/AINodeStorage.h

@@ -23,6 +23,8 @@ constexpr int NKAI_GRAPH_TRACE_LEVEL = 0;
 #include "Actions/SpecialAction.h"
 #include "Actors.h"
 
+#include <boost/container/small_vector.hpp>
+
 namespace NKAI
 {
 namespace AIPathfinding
@@ -85,7 +87,9 @@ struct AIPathNodeInfo
 
 struct AIPath
 {
-	std::vector<AIPathNodeInfo> nodes;
+	using NodesVector = boost::container::small_vector<AIPathNodeInfo, 16>;
+
+	NodesVector nodes;
 	uint64_t targetObjectDanger;
 	uint64_t armyLoss;
 	uint64_t targetObjectArmyLoss;

+ 2 - 1
AI/Nullkiller/Pathfinding/Actions/BoatActions.cpp

@@ -141,7 +141,8 @@ namespace AIPathfinding
 	{
 		SpellID summonBoat = SpellID::SUMMON_BOAT;
 
-		return hero->getSpellCost(summonBoat.toSpell());
+		// FIXME: this should be hero->getSpellCost, however currently queries to bonus system are too slow
+		return summonBoat.toSpell()->getCost(0);
 	}
 }
 

+ 1 - 1
AI/Nullkiller/Pathfinding/GraphPaths.cpp

@@ -118,7 +118,7 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil
 
 					targetNode.specialAction = compositeAction;
 
-					auto targetGraphNode = graph.getNode(target);
+					const auto & targetGraphNode = graph.getNode(target);
 
 					if(targetGraphNode.objID.hasValue())
 					{

+ 1 - 2
AI/VCAI/Pathfinding/AINodeStorage.cpp

@@ -162,10 +162,9 @@ void AINodeStorage::calculateNeighbours(
 	const PathfinderConfig * pathfinderConfig,
 	const CPathfinderHelper * pathfinderHelper)
 {
-	std::vector<int3> accessibleNeighbourTiles;
+	NeighbourTilesVector accessibleNeighbourTiles;
 
 	result.clear();
-	accessibleNeighbourTiles.reserve(8);
 
 	pathfinderHelper->calculateNeighbourTiles(accessibleNeighbourTiles, source);
 

+ 39 - 34
lib/bonuses/BonusList.cpp

@@ -85,6 +85,9 @@ void BonusList::stackBonuses()
 
 int BonusList::totalValue() const
 {
+	if (bonuses.empty())
+		return 0;
+
 	struct BonusCollection
 	{
 		int base = 0;
@@ -96,63 +99,65 @@ int BonusList::totalValue() const
 		int indepMax = std::numeric_limits<int>::min();
 	};
 
-	auto percent = [](int64_t base, int64_t percent) -> int {
-		return static_cast<int>(std::clamp<int64_t>((base * (100 + percent)) / 100, std::numeric_limits<int>::min(), std::numeric_limits<int>::max()));
+	auto percent = [](int base, int percent) -> int {
+		return (static_cast<int64_t>(base) * (100 + percent)) / 100;
 	};
-	std::array <BonusCollection, vstd::to_underlying(BonusSource::NUM_BONUS_SOURCE)> sources = {};
-	BonusCollection any;
+
+	BonusCollection accumulated;
 	bool hasIndepMax = false;
 	bool hasIndepMin = false;
 
+	std::array<int, vstd::to_underlying(BonusSource::NUM_BONUS_SOURCE)> percentToSource = {};
+
 	for(const auto & b : bonuses)
 	{
+		switch(b->valType)
+		{
+		case BonusValueType::PERCENT_TO_SOURCE:
+			percentToSource[vstd::to_underlying(b->source)] += b->val;
+		break;
+		case BonusValueType::PERCENT_TO_TARGET_TYPE:
+			percentToSource[vstd::to_underlying(b->targetSourceType)] += b->val;
+			break;
+		}
+	}
+
+	for(const auto & b : bonuses)
+	{
+		int sourceIndex = vstd::to_underlying(b->source);
+		int valModified	= percent(b->val, percentToSource[sourceIndex]);
+
 		switch(b->valType)
 		{
 		case BonusValueType::BASE_NUMBER:
-			sources[vstd::to_underlying(b->source)].base += b->val;
+			accumulated.base += valModified;
 			break;
 		case BonusValueType::PERCENT_TO_ALL:
-			sources[vstd::to_underlying(b->source)].percentToAll += b->val;
+			accumulated.percentToAll += valModified;
 			break;
 		case BonusValueType::PERCENT_TO_BASE:
-			sources[vstd::to_underlying(b->source)].percentToBase += b->val;
-			break;
-		case BonusValueType::PERCENT_TO_SOURCE:
-			sources[vstd::to_underlying(b->source)].percentToSource += b->val;
-			break;
-		case BonusValueType::PERCENT_TO_TARGET_TYPE:
-			sources[vstd::to_underlying(b->targetSourceType)].percentToSource += b->val;
+			accumulated.percentToBase += valModified;
 			break;
 		case BonusValueType::ADDITIVE_VALUE:
-			sources[vstd::to_underlying(b->source)].additive += b->val;
+			accumulated.additive += valModified;
 			break;
 		case BonusValueType::INDEPENDENT_MAX:
 			hasIndepMax = true;
-			vstd::amax(sources[vstd::to_underlying(b->source)].indepMax, b->val);
+			vstd::amax(accumulated.indepMax, valModified);
 			break;
 		case BonusValueType::INDEPENDENT_MIN:
 			hasIndepMin = true;
-			vstd::amin(sources[vstd::to_underlying(b->source)].indepMin, b->val);
+			vstd::amin(accumulated.indepMin, valModified);
 			break;
 		}
 	}
-	for(const auto & src : sources)
-	{
-		any.base += percent(src.base, src.percentToSource);
-		any.percentToBase += percent(src.percentToBase, src.percentToSource);
-		any.percentToAll += percent(src.percentToAll, src.percentToSource);
-		any.additive += percent(src.additive, src.percentToSource);
-		if(hasIndepMin)
-			vstd::amin(any.indepMin, percent(src.indepMin, src.percentToSource));
-		if(hasIndepMax)
-			vstd::amax(any.indepMax, percent(src.indepMax, src.percentToSource));
-	}
-	any.base = percent(any.base, any.percentToBase);
-	any.base += any.additive;
-	auto valFirst = percent(any.base ,any.percentToAll);
 
-	if(hasIndepMin && hasIndepMax && any.indepMin < any.indepMax)
-		any.indepMax = any.indepMin;
+	accumulated.base = percent(accumulated.base, accumulated.percentToBase);
+	accumulated.base += accumulated.additive;
+	auto valFirst = percent(accumulated.base ,accumulated.percentToAll);
+
+	if(hasIndepMin && hasIndepMax && accumulated.indepMin < accumulated.indepMax)
+		accumulated.indepMax = accumulated.indepMin;
 
 	const int notIndepBonuses = static_cast<int>(std::count_if(bonuses.cbegin(), bonuses.cend(), [](const std::shared_ptr<Bonus>& b)
 	{
@@ -160,9 +165,9 @@ int BonusList::totalValue() const
 	}));
 
 	if(notIndepBonuses)
-		return std::clamp(valFirst, any.indepMax, any.indepMin);
+		return std::clamp(valFirst, accumulated.indepMax, accumulated.indepMin);
 
-	return hasIndepMin ? any.indepMin : hasIndepMax ? any.indepMax : 0;
+	return hasIndepMin ? accumulated.indepMin : hasIndepMax ? accumulated.indepMax : 0;
 }
 
 std::shared_ptr<Bonus> BonusList::getFirst(const CSelector &select)

+ 0 - 1
lib/logging/CLogger.cpp

@@ -157,7 +157,6 @@ void CLogger::log(ELogLevel::ELogLevel level, const boost::format & fmt) const
 
 ELogLevel::ELogLevel CLogger::getLevel() const
 {
-	TLockGuard _(mx);
 	return level;
 }
 

+ 1 - 1
lib/networkPacks/NetPacksLib.cpp

@@ -1231,7 +1231,7 @@ void RemoveObject::applyGs(CGameState *gs)
 
 	gs->map->instanceNames.erase(obj->instanceName);
 	gs->map->objects[objectID.getNum()].dellNull();
-	gs->map->calculateGuardingGreaturePositions();
+	gs->map->calculateGuardingGreaturePositions();//FIXME: excessive, update only affected tiles
 }
 
 static int getDir(const int3 & src, const int3 & dst)

+ 10 - 10
lib/pathfinder/CPathfinder.cpp

@@ -49,7 +49,7 @@ bool CPathfinderHelper::canMoveFromNode(const PathNodeInfo & source) const
 	return true;
 }
 
-void CPathfinderHelper::calculateNeighbourTiles(std::vector<int3> & result, const PathNodeInfo & source) const
+void CPathfinderHelper::calculateNeighbourTiles(NeighbourTilesVector & result, const PathNodeInfo & source) const
 {
 	result.clear();
 
@@ -239,9 +239,9 @@ void CPathfinder::calculatePaths()
 	logAi->trace("CPathfinder finished with %s iterations", std::to_string(counter));
 }
 
-std::vector<int3> CPathfinderHelper::getAllowedTeleportChannelExits(const TeleportChannelID & channelID) const
+TeleporterTilesVector CPathfinderHelper::getAllowedTeleportChannelExits(const TeleportChannelID & channelID) const
 {
-	std::vector<int3> allowedExits;
+	TeleporterTilesVector allowedExits;
 
 	for(const auto & objId : getTeleportChannelExits(channelID, hero->tempOwner))
 	{
@@ -262,9 +262,9 @@ std::vector<int3> CPathfinderHelper::getAllowedTeleportChannelExits(const Telepo
 	return allowedExits;
 }
 
-std::vector<int3> CPathfinderHelper::getCastleGates(const PathNodeInfo & source) const
+TeleporterTilesVector CPathfinderHelper::getCastleGates(const PathNodeInfo & source) const
 {
-	std::vector<int3> allowedExits;
+	TeleporterTilesVector allowedExits;
 
 	auto towns = getPlayerState(hero->tempOwner)->towns;
 	for(const auto & town : towns)
@@ -279,9 +279,9 @@ std::vector<int3> CPathfinderHelper::getCastleGates(const PathNodeInfo & source)
 	return allowedExits;
 }
 
-std::vector<int3> CPathfinderHelper::getTeleportExits(const PathNodeInfo & source) const
+TeleporterTilesVector CPathfinderHelper::getTeleportExits(const PathNodeInfo & source) const
 {
-	std::vector<int3> teleportationExits;
+	TeleporterTilesVector teleportationExits;
 
 	const auto * objTeleport = dynamic_cast<const CGTeleport *>(source.nodeObject);
 	if(isAllowedTeleportEntrance(objTeleport))
@@ -578,7 +578,7 @@ int CPathfinderHelper::getMaxMovePoints(const EPathfindingLayer & layer) const
 void CPathfinderHelper::getNeighbours(
 	const TerrainTile & srcTile,
 	const int3 & srcCoord,
-	std::vector<int3> & vec,
+	NeighbourTilesVector & vec,
 	const boost::logic::tribool & onLand,
 	const bool limitCoastSailing) const
 {
@@ -702,8 +702,8 @@ int CPathfinderHelper::getMovementCost(
 	constexpr auto maxCostOfOneStep = static_cast<int>(175 * M_SQRT2); // diagonal move on Swamp - 247 MP
 	if(checkLast && left > 0 && left <= maxCostOfOneStep) //it might be the last tile - if no further move possible we take all move points
 	{
-		std::vector<int3> vec;
-		vec.reserve(8); //optimization
+		NeighbourTilesVector vec;
+
 		getNeighbours(*dt, dst, vec, ct->terType->isLand(), true);
 		for(const auto & elem : vec)
 		{

+ 16 - 5
lib/pathfinder/CPathfinder.h

@@ -13,12 +13,23 @@
 #include "../IGameCallback.h"
 #include "../bonuses/BonusEnum.h"
 
+#include <boost/container/static_vector.hpp>
+#include <boost/container/small_vector.hpp>
+
 VCMI_LIB_NAMESPACE_BEGIN
 
 class CGWhirlpool;
 struct TurnInfo;
 struct PathfinderOptions;
 
+// Optimized storage - tile can have 0-8 neighbour tiles
+// static_vector uses fixed, preallocated storage (capacity) and dynamic size
+// this avoid dynamic allocations on huge number of neighbour list queries
+using NeighbourTilesVector = boost::container::static_vector<int3, 8>;
+
+// Optimized storage to minimize dynamic allocations - most of teleporters have only one exit, but some may have more (premade maps, castle gates)
+using TeleporterTilesVector = boost::container::small_vector<int3, 4>;
+
 class DLL_LINKAGE CPathfinder
 {
 public:
@@ -87,22 +98,22 @@ public:
 	bool hasBonusOfType(BonusType type) const;
 	int getMaxMovePoints(const EPathfindingLayer & layer) const;
 
-	std::vector<int3> getCastleGates(const PathNodeInfo & source) const;
+	TeleporterTilesVector getCastleGates(const PathNodeInfo & source) const;
 	bool isAllowedTeleportEntrance(const CGTeleport * obj) const;
-	std::vector<int3> getAllowedTeleportChannelExits(const TeleportChannelID & channelID) const;
+	TeleporterTilesVector getAllowedTeleportChannelExits(const TeleportChannelID & channelID) const;
 	bool addTeleportTwoWay(const CGTeleport * obj) const;
 	bool addTeleportOneWay(const CGTeleport * obj) const;
 	bool addTeleportOneWayRandom(const CGTeleport * obj) const;
 	bool addTeleportWhirlpool(const CGWhirlpool * obj) const;
 	bool canMoveBetween(const int3 & a, const int3 & b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility)
 
-	void calculateNeighbourTiles(std::vector<int3> & result, const PathNodeInfo & source) const;
-	std::vector<int3> getTeleportExits(const PathNodeInfo & source) const;
+	void calculateNeighbourTiles(NeighbourTilesVector & result, const PathNodeInfo & source) const;
+	TeleporterTilesVector getTeleportExits(const PathNodeInfo & source) const;
 
 	void getNeighbours(
 		const TerrainTile & srcTile,
 		const int3 & srcCoord,
-		std::vector<int3> & vec,
+		NeighbourTilesVector & vec,
 		const boost::logic::tribool & onLand,
 		const bool limitCoastSailing) const;
 

+ 2 - 3
lib/pathfinder/NodeStorage.cpp

@@ -40,7 +40,7 @@ void NodeStorage::initialize(const PathfinderOptions & options, const CGameState
 		{
 			for(pos.y=0; pos.y < sizes.y; ++pos.y)
 			{
-				const TerrainTile tile = gs->map->getTile(pos);
+				const TerrainTile & tile = gs->map->getTile(pos);
 				if(tile.terType->isWater())
 				{
 					resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs));
@@ -67,10 +67,9 @@ void NodeStorage::calculateNeighbours(
 	const PathfinderConfig * pathfinderConfig,
 	const CPathfinderHelper * pathfinderHelper)
 {
-	std::vector<int3> accessibleNeighbourTiles;
+	NeighbourTilesVector accessibleNeighbourTiles;
 	
 	result.clear();
-	accessibleNeighbourTiles.reserve(8);
 	
 	pathfinderHelper->calculateNeighbourTiles(accessibleNeighbourTiles, source);