浏览代码

- Moved gameState::guardingCreaturePosition() to CMap so it doesn't need to be recalculated many times for every player.
- Some optimizations with local cb pinter in VCAI.

DjWarmonger 11 年之前
父节点
当前提交
30b79588db

+ 27 - 7
AI/VCAI/AIUtility.cpp

@@ -135,6 +135,16 @@ void foreach_tile_pos(std::function<void(const int3& pos)> foo)
 
 }
 
+void foreach_tile_pos(CCallback * cbp, std::function<void(CCallback * cbp, const int3& pos)> foo)
+{
+	int3 mapSize = cbp->getMapSize();
+
+	for(int i = 0; i < mapSize.x; i++)
+		for(int j = 0; j < mapSize.y; j++)
+			for(int k = 0; k < mapSize.z; k++)
+				foo(cbp, int3(i,j,k));
+}
+
 void foreach_neighbour(const int3 &pos, std::function<void(const int3& pos)> foo)
 {
 	CCallback * cbp = cb.get(); // avoid costly retrieval of thread-specific pointer
@@ -147,6 +157,16 @@ void foreach_neighbour(const int3 &pos, std::function<void(const int3& pos)> foo
 	}
 }
 
+void foreach_neighbour(CCallback * cbp, const int3 &pos, std::function<void(CCallback * cbp, const int3& pos)> foo)
+{
+	for(const int3 &dir : dirs)
+	{
+		const int3 n = pos + dir;
+		if(cbp->isInTheMap(n))
+			foo(cbp, pos+dir);
+	}
+}
+
 std::string strFromInt3(int3 pos)
 {
 	std::ostringstream oss;
@@ -180,7 +200,7 @@ ui64 evaluateDanger(crint3 tile)
 	if(visObjs.size())
 		objectDanger = evaluateDanger(visObjs.back());
 
-	int3 guardPos = cb->guardingCreaturePosition(tile);
+	int3 guardPos = cb->getGuardingCreaturePosition(tile);
 	if(guardPos.x >= 0 && guardPos != tile)
 		guardDanger = evaluateDanger(guardPos);
 
@@ -378,7 +398,7 @@ bool isBlockedBorderGate(int3 tileToHit)
 		&& cb->getPathInfo(tileToHit)->accessible != CGPathNode::ACCESSIBLE;
 }
 
-int howManyTilesWillBeDiscovered(const int3 &pos, int radious)
+int howManyTilesWillBeDiscovered(const int3 &pos, int radious, CCallback * cbp)
 { //TODO: do not explore dead-end boundaries
 	int ret = 0;
 	for(int x = pos.x - radious; x <= pos.x + radious; x++)
@@ -386,9 +406,9 @@ int howManyTilesWillBeDiscovered(const int3 &pos, int radious)
 		for(int y = pos.y - radious; y <= pos.y + radious; y++)
 		{
 			int3 npos = int3(x,y,pos.z);
-			if(cb->isInTheMap(npos) && pos.dist2d(npos) - 0.5 < radious  && !cb->isVisible(npos))
+			if(cbp->isInTheMap(npos) && pos.dist2d(npos) - 0.5 < radious  && !cbp->isVisible(npos))
 			{
-				if (!boundaryBetweenTwoPoints (pos, npos))
+				if (!boundaryBetweenTwoPoints (pos, npos, cbp))
 					ret++;
 			}
 		}
@@ -397,7 +417,7 @@ int howManyTilesWillBeDiscovered(const int3 &pos, int radious)
 	return ret;
 }
 
-bool boundaryBetweenTwoPoints (int3 pos1, int3 pos2) //determines if two points are separated by known barrier
+bool boundaryBetweenTwoPoints (int3 pos1, int3 pos2, CCallback * cbp) //determines if two points are separated by known barrier
 {
 	int xMin = std::min (pos1.x, pos2.x);
 	int xMax = std::max (pos1.x, pos2.x);
@@ -411,7 +431,7 @@ bool boundaryBetweenTwoPoints (int3 pos1, int3 pos2) //determines if two points
 			int3 tile = int3(x, y, pos1.z); //use only on same level, ofc
 			if (abs(pos1.dist2d(tile) - pos2.dist2d(tile)) < 1.5)
 			{
-				if (!(cb->isVisible(tile) && cb->getTile(tile)->blocked)) //if there's invisible or unblocked tile between, it's good
+				if (!(cbp->isVisible(tile) && cbp->getTile(tile)->blocked)) //if there's invisible or unblocked tile between, it's good
 					return false;
 			}
 		}
@@ -421,7 +441,7 @@ bool boundaryBetweenTwoPoints (int3 pos1, int3 pos2) //determines if two points
 
 int howManyTilesWillBeDiscovered(int radious, int3 pos, crint3 dir)
 {
-	return howManyTilesWillBeDiscovered(pos + dir, radious);
+	return howManyTilesWillBeDiscovered(pos + dir, radious, cb.get());
 }
 
 void getVisibleNeighbours(const std::vector<int3> &tiles, std::vector<int3> &out)

+ 4 - 2
AI/VCAI/AIUtility.h

@@ -175,9 +175,11 @@ void removeDuplicates(std::vector<T> &vec)
 
 std::string strFromInt3(int3 pos);
 void foreach_tile_pos(std::function<void(const int3& pos)> foo);
+void foreach_tile_pos(CCallback * cbp, std::function<void(CCallback * cbp, const int3& pos)> foo); // avoid costly retrieval of thread-specific pointer
 void foreach_neighbour(const int3 &pos, std::function<void(const int3& pos)> foo);
+void foreach_neighbour(CCallback * cbp, const int3 &pos, std::function<void(CCallback * cbp, const int3& pos)> foo); // avoid costly retrieval of thread-specific pointer
 
-int howManyTilesWillBeDiscovered(const int3 &pos, int radious);
+int howManyTilesWillBeDiscovered(const int3 &pos, int radious, CCallback * cbp);
 int howManyTilesWillBeDiscovered(int radious, int3 pos, crint3 dir);
 void getVisibleNeighbours(const std::vector<int3> &tiles, std::vector<int3> &out);
 
@@ -192,7 +194,7 @@ bool shouldVisit (HeroPtr h, const CGObjectInstance * obj);
 ui64 evaluateDanger(const CGObjectInstance *obj);
 ui64 evaluateDanger(crint3 tile, const CGHeroInstance *visitor);
 bool isSafeToVisit(HeroPtr h, crint3 tile);
-bool boundaryBetweenTwoPoints (int3 pos1, int3 pos2);
+bool boundaryBetweenTwoPoints (int3 pos1, int3 pos2, CCallback * cbp);
 
 bool compareMovement(HeroPtr lhs, HeroPtr rhs);
 bool compareHeroStrength(HeroPtr h1, HeroPtr h2);

+ 20 - 14
AI/VCAI/VCAI.cpp

@@ -2245,9 +2245,11 @@ int3 VCAI::explorationNewPoint(HeroPtr h)
 	std::vector<std::vector<int3> > tiles; //tiles[distance_to_fow]
 	tiles.resize(radius);
 
+	CCallback * cbp = cb.get();
+
 	foreach_tile_pos([&](const int3 &pos)
 	{
-		if(!cb->isVisible(pos))
+		if(!cbp->isVisible(pos))
 			tiles[0].push_back(pos);
 	});
 
@@ -2261,14 +2263,14 @@ int3 VCAI::explorationNewPoint(HeroPtr h)
 
 		for(const int3 &tile : tiles[i])
 		{
-			if (cb->getTile(tile)->blocked) //does it shorten the time?
+			if (cbp->getTile(tile)->blocked) //does it shorten the time?
 				continue;
-			if (!cb->getPathInfo(tile)->reachable()) //this will remove tiles that are guarded by monsters (or removable objects)
+			if (!cbp->getPathInfo(tile)->reachable()) //this will remove tiles that are guarded by monsters (or removable objects)
 				continue;
 
 			CGPath path;
-			cb->getPath2(tile, path);
-			float ourValue = (float)howManyTilesWillBeDiscovered(tile, radius) / (path.nodes.size() + 1); //+1 prevents erratic jumps
+			cbp->getPath2(tile, path);
+			float ourValue = (float)howManyTilesWillBeDiscovered(tile, radius, cbp) / (path.nodes.size() + 1); //+1 prevents erratic jumps
 
 			if (ourValue > bestValue) //avoid costly checks of tiles that don't reveal much
 			{
@@ -2292,9 +2294,11 @@ int3 VCAI::explorationDesperate(HeroPtr h)
 	std::vector<std::vector<int3> > tiles; //tiles[distance_to_fow]
 	tiles.resize(radius);
 
+	CCallback * cbp = cb.get();
+
 	foreach_tile_pos([&](const int3 &pos)
 	{
-		if(!cb->isVisible(pos))
+		if(!cbp->isVisible(pos))
 			tiles[0].push_back(pos);
 	});
 
@@ -2308,9 +2312,9 @@ int3 VCAI::explorationDesperate(HeroPtr h)
 
 		for(const int3 &tile : tiles[i])
 		{
-			if (cb->getTile(tile)->blocked) //does it shorten the time?
+			if (cbp->getTile(tile)->blocked) //does it shorten the time?
 				continue;
-			if (!howManyTilesWillBeDiscovered(tile, radius)) //avoid costly checks of tiles that don't reveal much
+			if (!howManyTilesWillBeDiscovered(tile, radius, cbp)) //avoid costly checks of tiles that don't reveal much
 				continue;
 
 			auto t = sm.firstTileToGet(h, tile);
@@ -2682,12 +2686,14 @@ void SectorMap::update()
 {
 	clear();
 	int curSector = 3; //0 is invisible, 1 is not explored
+
+	CCallback * cbp = cb.get(); //optimization
 	foreach_tile_pos([&](crint3 pos)
 	{
 		if(retreiveTile(pos) == NOT_CHECKED)
 		{
 			if(!markIfBlocked(retreiveTile(pos), pos))
-				exploreNewSector(pos, curSector++);
+				exploreNewSector(pos, curSector++, cbp);
 		}
 	});
 	valid = true;
@@ -2699,11 +2705,11 @@ void SectorMap::clear()
 	valid = false;
 }
 
-void SectorMap::exploreNewSector(crint3 pos, int num)
+void SectorMap::exploreNewSector(crint3 pos, int num, CCallback * cbp)
 {
 	Sector &s = infoOnSectors[num];
 	s.id = num;
-	s.water = cb->getTile(pos)->isWater();
+	s.water = cbp->getTile(pos)->isWater();
 
 	std::queue<int3> toVisit;
 	toVisit.push(pos);
@@ -2714,21 +2720,21 @@ void SectorMap::exploreNewSector(crint3 pos, int num)
 		ui8 &sec = retreiveTile(curPos);
 		if(sec == NOT_CHECKED)
 		{
-			const TerrainTile *t = cb->getTile(curPos);
+			const TerrainTile *t = cbp->getTile(curPos);
 			if(!markIfBlocked(sec, curPos, t))
 			{
 				if(t->isWater() == s.water) //sector is only-water or only-land
 				{
 					sec = num;
 					s.tiles.push_back(curPos);
-					foreach_neighbour(curPos, [&](crint3 neighPos)
+					foreach_neighbour(cbp, curPos, [&](CCallback * cbp, crint3 neighPos)
 					{
 						if(retreiveTile(neighPos) == NOT_CHECKED)
 						{
 							toVisit.push(neighPos);
 							//parent[neighPos] = curPos;
 						}
-						const TerrainTile *nt = cb->getTile(neighPos, false);
+						const TerrainTile *nt = cbp->getTile(neighPos, false);
 						if(nt && nt->isWater() != s.water && canBeEmbarkmentPoint(nt))
 						{
 							s.embarkmentPoints.push_back(neighPos);

+ 1 - 1
AI/VCAI/VCAI.h

@@ -102,7 +102,7 @@ struct SectorMap
 	SectorMap(HeroPtr h);
 	void update();
 	void clear();
-	void exploreNewSector(crint3 pos, int num);
+	void exploreNewSector(crint3 pos, int num, CCallback * cbp);
 	void write(crstring fname);
 
 	unsigned char &retreiveTile(crint3 pos);

+ 11 - 0
CCallback.cpp

@@ -356,6 +356,17 @@ int CCallback::getMovementCost(const CGHeroInstance * hero, int3 dest)
 	return gs->getMovementCost(hero, hero->visitablePos(), dest, hero->hasBonusOfType (Bonus::FLYING_MOVEMENT), hero->movement);
 }
 
+int3 CCallback::getGuardingCreaturePosition(int3 tile)
+{
+	if (!gs->map->isInTheMap(tile))
+		return int3(-1,-1,-1);
+
+	validatePaths();
+
+	boost::unique_lock<boost::mutex> pathLock(cl->pathMx);
+	return gs->map->guardingCreaturePositions[tile.x][tile.y][tile.z];
+}
+
 void CCallback::recalculatePaths()
 {
 	cl->calculatePaths(cl->IGameCallback::getSelectedHero(*player));

+ 1 - 0
CCallback.h

@@ -112,6 +112,7 @@ public:
 	virtual bool getPath2(int3 dest, CGPath &ret); //uses main, client pathfinder info
 	virtual bool canMoveBetween(const int3 &a, const int3 &b);
 	virtual int getMovementCost(const CGHeroInstance * hero, int3 dest);
+	virtual int3 getGuardingCreaturePosition(int3 tile); //uses main, client pathfinder info
 
 	virtual void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out, int3 src = int3(-1,-1,-1), int movement = -1);
 	virtual void recalculatePaths(); //updates main, client pathfinder info (should be called when moving hero is over)

+ 1 - 1
client/CAdvmapInterface.cpp

@@ -1279,7 +1279,7 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
 		}
 	}
 
-	const bool guardingCreature = CGI->mh->map->isInTheMap(LOCPLINT->cb->guardingCreaturePosition(mapPos));
+	const bool guardingCreature = CGI->mh->map->isInTheMap(LOCPLINT->cb->getGuardingCreaturePosition(mapPos));
 
 	if(selection->ID == Obj::TOWN)
 	{

+ 1 - 1
client/CPlayerInterface.cpp

@@ -1344,7 +1344,7 @@ bool CPlayerInterface::moveHero( const CGHeroInstance *h, CGPath path )
 				stillMoveHero.data = WAITING_MOVE;
 
 				int3 endpos(path.nodes[i-1].coord.x, path.nodes[i-1].coord.y, h->pos.z);
-				bool guarded = CGI->mh->map->isInTheMap(cb->guardingCreaturePosition(endpos - int3(1, 0, 0)));
+				bool guarded = CGI->mh->map->isInTheMap(cb->getGuardingCreaturePosition(endpos - int3(1, 0, 0)));
 
                 logGlobal->traceStream() << "Requesting hero movement to " << endpos;
 				cb->moveHero(h,endpos);

+ 7 - 67
lib/CGameState.cpp

@@ -2237,7 +2237,7 @@ std::vector<CGObjectInstance*> CGameState::guardingCreatures (int3 pos) const
 				{
 					for (CGObjectInstance* obj : tile.visitableObjects)
 					{
-						if (obj->ID == Obj::MONSTER  &&  checkForVisitableDir(pos, &map->getTile(originalPos), originalPos)) // Monster being able to attack investigated tile
+						if (obj->ID == Obj::MONSTER  &&  map->checkForVisitableDir(pos, &map->getTile(originalPos), originalPos)) // Monster being able to attack investigated tile
 						{
 							guards.push_back(obj);
 						}
@@ -2256,53 +2256,7 @@ std::vector<CGObjectInstance*> CGameState::guardingCreatures (int3 pos) const
 
 int3 CGameState::guardingCreaturePosition (int3 pos) const
 {
-	const int3 originalPos = pos;
-	// Give monster at position priority.
-	if (!map->isInTheMap(pos))
-		return int3(-1, -1, -1);
-	const TerrainTile &posTile = map->getTile(pos);
-	if (posTile.visitable)
-	{
-		for (CGObjectInstance* obj : posTile.visitableObjects)
-		{
-			if(obj->blockVisit)
-			{
-				if (obj->ID == Obj::MONSTER) // Monster
-					return pos;
-				else
-					return int3(-1, -1, -1); //blockvis objects are not guarded by neighbouring creatures
-			}
-		}
-	}
-
-	// See if there are any monsters adjacent.
-	pos -= int3(1, 1, 0); // Start with top left.
-	for (int dx = 0; dx < 3; dx++)
-	{
-		for (int dy = 0; dy < 3; dy++)
-		{
-			if (map->isInTheMap(pos))
-			{
-				const auto & tile = map->getTile(pos);
-                if (tile.visitable && (tile.isWater() == posTile.isWater()))
-				{
-					for (CGObjectInstance* obj : tile.visitableObjects)
-					{
-						if (obj->ID == Obj::MONSTER  &&  checkForVisitableDir(pos, &posTile, originalPos)) // Monster being able to attack investigated tile
-						{
-							return pos;
-						}
-					}
-				}
-			}
-
-			pos.y++;
-		}
-		pos.y -= 3;
-		pos.x++;
-	}
-
-	return int3(-1, -1, -1);
+	return gs->map->guardingCreaturePositions[pos.x][pos.y][pos.z];
 }
 
 bool CGameState::isVisible(int3 pos, PlayerColor player)
@@ -2338,22 +2292,7 @@ bool CGameState::isVisible( const CGObjectInstance *obj, boost::optional<PlayerC
 bool CGameState::checkForVisitableDir(const int3 & src, const int3 & dst) const
 {
 	const TerrainTile * pom = &map->getTile(dst);
-	return checkForVisitableDir(src, pom, dst);
-}
-
-bool CGameState::checkForVisitableDir( const int3 & src, const TerrainTile *pom, const int3 & dst ) const
-{
-	for(ui32 b=0; b<pom->visitableObjects.size(); ++b) //checking destination tile
-	{
-		if(!vstd::contains(pom->blockingObjects, pom->visitableObjects[b])) //this visitable object is not blocking, ignore
-			continue;
-
-		const CGObjectInstance * obj = pom->visitableObjects[b];
-
-		if (!obj->appearance.isVisitableFrom(src.x - dst.x, src.y - dst.y))
-			return false;
-	}
-	return true;
+	return map->checkForVisitableDir(src, pom, dst);
 }
 
 EVictoryLossCheckResult CGameState::checkForVictoryAndLoss(PlayerColor player) const
@@ -3350,6 +3289,7 @@ void CPathfinder::initializeGraph()
 				curPos = int3(i,j,k);
 				const TerrainTile *tinfo = &gs->map->getTile(int3(i, j, k));
 				CGPathNode &node = graph[i][j][k];
+
 				node.accessible = evaluateAccessibility(tinfo);
 				node.turns = 0xff;
 				node.moveRemains = 0;
@@ -3405,7 +3345,7 @@ void CPathfinder::calculatePaths(int3 src /*= int3(-1,-1,-1)*/, int movement /*=
 		cp = mq.front();
 		mq.pop_front();
 
-		const int3 sourceGuardPosition = guardingCreaturePosition(cp->coord);
+		const int3 sourceGuardPosition = gs->map->guardingCreaturePositions[cp->coord.x][cp->coord.y][cp->coord.z];
 		bool guardedSource = (sourceGuardPosition != int3(-1, -1, -1) && cp->coord != src);
 		ct = &gs->map->getTile(cp->coord);
 
@@ -3498,7 +3438,7 @@ void CPathfinder::calculatePaths(int3 src /*= int3(-1,-1,-1)*/, int movement /*=
 				dp->turns = turnAtNextTile;
 				dp->theNodeBefore = cp;
 
-				const bool guardedDst = guardingCreaturePosition(dp->coord) != int3(-1, -1, -1)
+				const bool guardedDst = gs->map->guardingCreaturePositions[dp->coord.x][dp->coord.y][dp->coord.z].valid()
 										&& dp->accessible == CGPathNode::BLOCKVIS;
 
 				if (dp->accessible == CGPathNode::ACCESSIBLE
@@ -3558,7 +3498,7 @@ CGPathNode::EAccessibility CPathfinder::evaluateAccessibility(const TerrainTile
 			}
 		}
 	}
-	else if (gs->map->isInTheMap(guardingCreaturePosition(curPos))
+	else if (gs->map->guardingCreaturePositions[curPos.x][curPos.y][curPos.z].valid()
 		&& !tinfo->blocked)
 	{
 		// Monster close by; blocked visit for battle.

+ 0 - 1
lib/CGameState.h

@@ -431,7 +431,6 @@ public:
 	UpgradeInfo getUpgradeInfo(const CStackInstance &stack);
 	PlayerRelations::PlayerRelations getPlayerRelations(PlayerColor color1, PlayerColor color2);
 	bool checkForVisitableDir(const int3 & src, const int3 & dst) const; //check if src tile is visitable from dst tile
-	bool checkForVisitableDir(const int3 & src, const TerrainTile *pom, const int3 & dst) const; //check if src tile is visitable from dst tile
 	void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out, int3 src = int3(-1,-1,-1), int movement = -1); //calculates possible paths for hero, by default uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists
 	int3 guardingCreaturePosition (int3 pos) const;
 	std::vector<CGObjectInstance*> guardingCreatures (int3 pos) const;

+ 1 - 1
lib/IGameCallback.cpp

@@ -406,7 +406,7 @@ bool CGameInfoCallback::getTownInfo( const CGObjectInstance *town, InfoAboutTown
 	return true;
 }
 
-int3 CGameInfoCallback::guardingCreaturePosition (int3 pos) const
+int3 CGameInfoCallback::guardingCreaturePosition (int3 pos) const //FIXME: redundant?
 {
 	ERROR_RET_VAL_IF(!isVisible(pos), "Tile is not visible!", int3(-1,-1,-1));
 	return gs->guardingCreaturePosition(pos);

+ 2 - 0
lib/NetPacksLib.cpp

@@ -380,6 +380,7 @@ DLL_LINKAGE void RemoveObject::applyGs( CGameState *gs )
 	}
 
 	gs->map->objects[id.getNum()].dellNull();
+	gs->map->calculateGuardingGreaturePositions();
 }
 
 static int getDir(int3 src, int3 dst)
@@ -613,6 +614,7 @@ DLL_LINKAGE void NewObject::applyGs( CGameState *gs )
 	gs->map->objects.push_back(o);
 	gs->map->addBlockVisTiles(o);
 	o->initObj();
+	gs->map->calculateGuardingGreaturePositions();
 
 	logGlobal->debugStream() << "added object id=" << id << "; address=" << (intptr_t)o << "; name=" << o->getHoverText();
 }

+ 98 - 7
lib/mapping/CMap.cpp

@@ -216,13 +216,18 @@ CMap::~CMap()
 {
 	if(terrain)
 	{
-		for(int ii=0;ii<width;ii++)
+		for (int i=0; i<width; i++)
 		{
-			for(int jj=0;jj<height;jj++)
-				delete [] terrain[ii][jj];
-			delete [] terrain[ii];
+			for(int j=0; j<height; j++)
+			{
+				delete [] terrain[i][j];
+				delete [] guardingCreaturePositions[i][j];
+			}
+			delete [] terrain[i];
+			delete [] guardingCreaturePositions[i];
 		}
 		delete [] terrain;
+		delete [] guardingCreaturePositions;
 	}
 }
 
@@ -280,6 +285,19 @@ void CMap::addBlockVisTiles(CGObjectInstance * obj)
 	}
 }
 
+void CMap::calculateGuardingGreaturePositions()
+{
+	int levels = twoLevel ? 2 : 1;
+	for (int i=0; i<width; i++)
+	{
+		for(int j=0; j<height; j++)
+		{
+			for (int k = 0; k < levels; k++)
+				guardingCreaturePositions[i][j][k] = guardingCreaturePosition(int3(i,j,k));
+		}
+	}
+}
+
 CGHeroInstance * CMap::getHero(int heroID)
 {
 	for(auto & elem : heroesOnMap)
@@ -318,6 +336,75 @@ bool CMap::isWaterTile(const int3 &pos) const
 	return isInTheMap(pos) && getTile(pos).terType == ETerrainType::WATER;
 }
 
+bool CMap::checkForVisitableDir(const int3 & src, const TerrainTile *pom, const int3 & dst ) const
+{
+	for(ui32 b=0; b<pom->visitableObjects.size(); ++b) //checking destination tile
+	{
+		if(!vstd::contains(pom->blockingObjects, pom->visitableObjects[b])) //this visitable object is not blocking, ignore
+			continue;
+
+		const CGObjectInstance * obj = pom->visitableObjects[b];
+
+		if (!obj->appearance.isVisitableFrom(src.x - dst.x, src.y - dst.y))
+			return false;
+	}
+	return true;
+}
+
+int3 CMap::guardingCreaturePosition (int3 pos) const
+{
+
+	const int3 originalPos = pos;
+	// Give monster at position priority.
+	if (!isInTheMap(pos))
+		return int3(-1, -1, -1);
+	const TerrainTile &posTile = getTile(pos);
+	if (posTile.visitable)
+	{
+		for (CGObjectInstance* obj : posTile.visitableObjects)
+		{
+			if(obj->blockVisit)
+			{
+				if (obj->ID == Obj::MONSTER) // Monster
+					return pos;
+				else
+					return int3(-1, -1, -1); //blockvis objects are not guarded by neighbouring creatures
+			}
+		}
+	}
+
+	// See if there are any monsters adjacent.
+	bool water = posTile.isWater();
+
+	pos -= int3(1, 1, 0); // Start with top left.
+	for (int dx = 0; dx < 3; dx++)
+	{
+		for (int dy = 0; dy < 3; dy++)
+		{
+			if (isInTheMap(pos))
+			{
+				const auto & tile = getTile(pos);
+                if (tile.visitable && (tile.isWater() == water))
+				{
+					for (CGObjectInstance* obj : tile.visitableObjects)
+					{
+						if (obj->ID == Obj::MONSTER  &&  checkForVisitableDir(pos, &posTile, originalPos)) // Monster being able to attack investigated tile
+						{
+							return pos;
+						}
+					}
+				}
+			}
+
+			pos.y++;
+		}
+		pos.y -= 3;
+		pos.x++;
+	}
+
+	return int3(-1, -1, -1);
+}
+
 const CGObjectInstance * CMap::getObjectiveObjectFrom(int3 pos, Obj::EObj type)
 {
 	for (CGObjectInstance * object : getTile(pos).visitableObjects)
@@ -432,13 +519,17 @@ void CMap::addQuest(CGObjectInstance * quest)
 
 void CMap::initTerrain()
 {
+	int level = twoLevel ? 2 : 1;
 	terrain = new TerrainTile**[width];
-	for(int i = 0; i < width; ++i)
+	guardingCreaturePositions = new int3**[width];
+	for (int i = 0; i < width; ++i)
 	{
 		terrain[i] = new TerrainTile*[height];
-		for(int j = 0; j < height; ++j)
+		guardingCreaturePositions[i] = new int3*[height];
+		for (int j = 0; j < height; ++j)
 		{
-			terrain[i][j] = new TerrainTile[twoLevel ? 2 : 1];
+			terrain[i][j] = new TerrainTile[level];
+			guardingCreaturePositions[i][j] = new int3[level];
 		}
 	}
 }

+ 17 - 6
lib/mapping/CMap.h

@@ -391,9 +391,12 @@ public:
 	const TerrainTile & getTile(const int3 & tile) const;
 	bool isInTheMap(const int3 & pos) const;
 	bool isWaterTile(const int3 & pos) const;
+	bool checkForVisitableDir( const int3 & src, const TerrainTile *pom, const int3 & dst ) const;
+	int3 guardingCreaturePosition (int3 pos) const;
 
 	void addBlockVisTiles(CGObjectInstance * obj);
 	void removeBlockVisTiles(CGObjectInstance * obj, bool total = false);
+	void calculateGuardingGreaturePositions();
 
 	void addNewArtifactInstance(CArtifactInstance * art);
 	void eraseArtifactInstance(CArtifactInstance * art);
@@ -433,6 +436,8 @@ public:
 
 	unique_ptr<CMapEditManager> editManager;
 
+	int3 ***guardingCreaturePositions;
+
 private:
 	/// a 3-dimensional array of terrain tiles, access is as follows: x, y, level. where level=1 is underground
 	TerrainTile*** terrain;
@@ -447,6 +452,7 @@ public:
 		h & questIdentifierToId;
 
 		//TODO: viccondetails
+		int level = twoLevel ? 2 : 1;
 		if(h.saving)
 		{
 			// Save terrain
@@ -454,9 +460,10 @@ public:
 			{
 				for(int j = 0; j < height ; ++j)
 				{
-					for(int k = 0; k < (twoLevel ? 2 : 1); ++k)
+					for(int k = 0; k < level; ++k)
 					{
 						h & terrain[i][j][k];
+						h & guardingCreaturePositions[i][j][k];
 					}
 				}
 			}
@@ -465,21 +472,25 @@ public:
 		{
 			// Load terrain
 			terrain = new TerrainTile**[width];
-			for(int ii = 0; ii < width; ++ii)
+			guardingCreaturePositions = new int3**[width];
+			for(int i = 0; i < width; ++i)
 			{
-				terrain[ii] = new TerrainTile*[height];
-				for(int jj = 0; jj < height; ++jj)
+				terrain[i] = new TerrainTile*[height];
+				guardingCreaturePositions[i] = new int3*[height];
+				for(int j = 0; j < height; ++j)
 				{
-					terrain[ii][jj] = new TerrainTile[twoLevel ? 2 : 1];
+					terrain[i][j] = new TerrainTile[level];
+					guardingCreaturePositions[i][j] = new int3[level];
 				}
 			}
 			for(int i = 0; i < width ; ++i)
 			{
 				for(int j = 0; j < height ; ++j)
 				{
-					for(int k = 0; k < (twoLevel ? 2 : 1); ++k)
+					for(int k = 0; k < level; ++k)
 					{
 						h & terrain[i][j][k];
+						h & guardingCreaturePositions[i][j][k];
 					}
 				}
 			}

+ 1 - 0
lib/mapping/MapFormatH3M.cpp

@@ -134,6 +134,7 @@ void CMapLoaderH3M::init()
             logGlobal->debugStream() << "\tReading " << mlt.name << " took " << mlt.time << " ms.";
 		}
 	}
+	map->calculateGuardingGreaturePositions();
 }
 
 void CMapLoaderH3M::readHeader()