| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431 | 
							- /*
 
- * SectorMap.cpp, part of VCMI engine
 
- *
 
- * Authors: listed in file AUTHORS in main folder
 
- *
 
- * License: GNU General Public License v2.0 or later
 
- * Full text of license available in license.txt file, in main folder
 
- *
 
- */
 
- #include "StdInc.h"
 
- #include "SectorMap.h"
 
- #include "VCAI.h"
 
- #include "../../CCallback.h"
 
- #include "../../lib/mapping/CMap.h"
 
- #include "../../lib/mapObjects/MapObjects.h"
 
- #include "../../lib/CPathfinder.h"
 
- #include "../../lib/CGameState.h"
 
- extern boost::thread_specific_ptr<CCallback> cb;
 
- extern boost::thread_specific_ptr<VCAI> ai;
 
- SectorMap::SectorMap()
 
- {
 
- 	update();
 
- }
 
- SectorMap::SectorMap(HeroPtr h)
 
- {
 
- 	update();
 
- 	makeParentBFS(h->visitablePos());
 
- }
 
- bool SectorMap::markIfBlocked(TSectorID & sec, crint3 pos, const TerrainTile * t)
 
- {
 
- 	if (t->blocked && !t->visitable)
 
- 	{
 
- 		sec = NOT_AVAILABLE;
 
- 		return true;
 
- 	}
 
- 	return false;
 
- }
 
- bool SectorMap::markIfBlocked(TSectorID & sec, crint3 pos)
 
- {
 
- 	return markIfBlocked(sec, pos, getTile(pos));
 
- }
 
- void SectorMap::update()
 
- {
 
- 	visibleTiles = cb->getAllVisibleTiles();
 
- 	auto shape = visibleTiles->shape();
 
- 	sector.resize(boost::extents[shape[0]][shape[1]][shape[2]]);
 
- 	clear();
 
- 	int curSector = 3; //0 is invisible, 1 is not explored
 
- 	CCallback * cbp = cb.get(); //optimization
 
- 	foreach_tile_pos([&](crint3 pos)
 
- 	{
 
- 		if (retrieveTile(pos) == NOT_CHECKED)
 
- 		{
 
- 			if (!markIfBlocked(retrieveTile(pos), pos))
 
- 				exploreNewSector(pos, curSector++, cbp);
 
- 		}
 
- 	});
 
- 	valid = true;
 
- }
 
- SectorMap::TSectorID & SectorMap::retrieveTileN(SectorMap::TSectorArray & a, const int3 & pos)
 
- {
 
- 	return a[pos.x][pos.y][pos.z];
 
- }
 
- const SectorMap::TSectorID & SectorMap::retrieveTileN(const SectorMap::TSectorArray & a, const int3 & pos)
 
- {
 
- 	return a[pos.x][pos.y][pos.z];
 
- }
 
- void SectorMap::clear()
 
- {
 
- 	//TODO: rotate to [z][x][y]
 
- 	auto fow = cb->getVisibilityMap();
 
- 	//TODO: any magic to automate this? will need array->array conversion
 
- 	//std::transform(fow.begin(), fow.end(), sector.begin(), [](const ui8 &f) -> unsigned short
 
- 	//{
 
- 	//	return f; //type conversion
 
- 	//});
 
- 	auto width = fow.size();
 
- 	auto height = fow.front().size();
 
- 	auto depth = fow.front().front().size();
 
- 	for (size_t x = 0; x < width; x++)
 
- 	{
 
- 		for (size_t y = 0; y < height; y++)
 
- 		{
 
- 			for (size_t z = 0; z < depth; z++)
 
- 				sector[x][y][z] = fow[x][y][z];
 
- 		}
 
- 	}
 
- 	valid = false;
 
- }
 
- void SectorMap::exploreNewSector(crint3 pos, int num, CCallback * cbp)
 
- {
 
- 	Sector & s = infoOnSectors[num];
 
- 	s.id = num;
 
- 	s.water = getTile(pos)->isWater();
 
- 	std::queue<int3> toVisit;
 
- 	toVisit.push(pos);
 
- 	while (!toVisit.empty())
 
- 	{
 
- 		int3 curPos = toVisit.front();
 
- 		toVisit.pop();
 
- 		TSectorID & sec = retrieveTile(curPos);
 
- 		if (sec == NOT_CHECKED)
 
- 		{
 
- 			const TerrainTile * t = 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(cbp, curPos, [&](CCallback * cbp, crint3 neighPos)
 
- 					{
 
- 						if (retrieveTile(neighPos) == NOT_CHECKED)
 
- 						{
 
- 							toVisit.push(neighPos);
 
- 							//parent[neighPos] = curPos;
 
- 						}
 
- 						const TerrainTile * nt = getTile(neighPos);
 
- 						if (nt && nt->isWater() != s.water && canBeEmbarkmentPoint(nt, s.water))
 
- 						{
 
- 							s.embarkmentPoints.push_back(neighPos);
 
- 						}
 
- 					});
 
- 					if (t->visitable)
 
- 					{
 
- 						auto obj = t->visitableObjects.front();
 
- 						if (cb->getObj(obj->id, false)) // FIXME: we have to filter invisible objcts like events, but probably TerrainTile shouldn't be used in SectorMap at all
 
- 							s.visitableObjs.push_back(obj);
 
- 					}
 
- 				}
 
- 			}
 
- 		}
 
- 	}
 
- 	vstd::removeDuplicates(s.embarkmentPoints);
 
- }
 
- void SectorMap::write(crstring fname)
 
- {
 
- 	std::ofstream out(fname);
 
- 	for (int k = 0; k < cb->getMapSize().z; k++)
 
- 	{
 
- 		for (int j = 0; j < cb->getMapSize().y; j++)
 
- 		{
 
- 			for (int i = 0; i < cb->getMapSize().x; i++)
 
- 			{
 
- 				out << (int)sector[i][j][k] << '\t';
 
- 			}
 
- 			out << std::endl;
 
- 		}
 
- 		out << std::endl;
 
- 	}
 
- }
 
- /*
 
- this functions returns one target tile or invalid tile. We will use it to poll possible destinations
 
- For ship construction etc, another function (goal?) is needed
 
- */
 
- int3 SectorMap::firstTileToGet(HeroPtr h, crint3 dst)
 
- {
 
- 	int3 ret(-1, -1, -1);
 
- 	int sourceSector = retrieveTile(h->visitablePos());
 
- 	int destinationSector = retrieveTile(dst);
 
- 	const Sector * src = &infoOnSectors[sourceSector];
 
- 	const Sector * dest = &infoOnSectors[destinationSector];
 
- 	if (sourceSector != destinationSector) //use ships, shipyards etc..
 
- 	{
 
- 		if (ai->isAccessibleForHero(dst, h)) //pathfinder can find a way using ships and gates if tile is not blocked by objects
 
- 			return dst;
 
- 		std::map<const Sector *, const Sector *> preds;
 
- 		std::queue<const Sector *> sectorQueue;
 
- 		sectorQueue.push(src);
 
- 		while (!sectorQueue.empty())
 
- 		{
 
- 			const Sector * s = sectorQueue.front();
 
- 			sectorQueue.pop();
 
- 			for (int3 ep : s->embarkmentPoints)
 
- 			{
 
- 				Sector * neigh = &infoOnSectors[retrieveTile(ep)];
 
- 				//preds[s].push_back(neigh);
 
- 				if (!preds[neigh])
 
- 				{
 
- 					preds[neigh] = s;
 
- 					sectorQueue.push(neigh);
 
- 				}
 
- 			}
 
- 		}
 
- 		if (!preds[dest])
 
- 		{
 
- 			//write("test.txt");
 
- 			return ret;
 
- 			//throw cannotFulfillGoalException(boost::str(boost::format("Cannot find connection between sectors %d and %d") % src->id % dst->id));
 
- 		}
 
- 		std::vector<const Sector *> toTraverse;
 
- 		toTraverse.push_back(dest);
 
- 		while (toTraverse.back() != src)
 
- 		{
 
- 			toTraverse.push_back(preds[toTraverse.back()]);
 
- 		}
 
- 		if (preds[dest])
 
- 		{
 
- 			//TODO: would be nice to find sectors in loop
 
- 			const Sector * sectorToReach = toTraverse.at(toTraverse.size() - 2);
 
- 			if (!src->water && sectorToReach->water) //embark
 
- 			{
 
- 				//embark on ship -> look for an EP with a boat
 
- 				auto firstEP = boost::find_if(src->embarkmentPoints, [=](crint3 pos) -> bool
 
- 				{
 
- 					const TerrainTile * t = getTile(pos);
 
- 					if (t && t->visitableObjects.size() == 1 && t->topVisitableId() == Obj::BOAT)
 
- 					{
 
- 						if (retrieveTile(pos) == sectorToReach->id)
 
- 							return true;
 
- 					}
 
- 					return false;
 
- 				});
 
- 				if (firstEP != src->embarkmentPoints.end())
 
- 				{
 
- 					return *firstEP;
 
- 				}
 
- 				else
 
- 				{
 
- 					//we need to find a shipyard with an access to the desired sector's EP
 
- 					//TODO what about Summon Boat spell?
 
- 					std::vector<const IShipyard *> shipyards;
 
- 					for (const CGTownInstance * t : cb->getTownsInfo())
 
- 					{
 
- 						if (t->hasBuilt(BuildingID::SHIPYARD))
 
- 							shipyards.push_back(t);
 
- 					}
 
- 					for (const CGObjectInstance * obj : ai->getFlaggedObjects())
 
- 					{
 
- 						if (obj->ID != Obj::TOWN) //towns were handled in the previous loop
 
- 						{
 
- 							if (const IShipyard * shipyard = IShipyard::castFrom(obj))
 
- 								shipyards.push_back(shipyard);
 
- 						}
 
- 					}
 
- 					shipyards.erase(boost::remove_if(shipyards, [=](const IShipyard * shipyard) -> bool
 
- 					{
 
- 						return shipyard->shipyardStatus() != 0 || retrieveTile(shipyard->bestLocation()) != sectorToReach->id;
 
- 					}), shipyards.end());
 
- 					if (!shipyards.size())
 
- 					{
 
- 						//TODO consider possibility of building shipyard in a town
 
- 						return ret;
 
- 						//throw cannotFulfillGoalException("There is no known shipyard!");
 
- 					}
 
- 					//we have only shipyards that possibly can build ships onto the appropriate EP
 
- 					auto ownedGoodShipyard = boost::find_if(shipyards, [](const IShipyard * s) -> bool
 
- 					{
 
- 						return s->o->tempOwner == ai->playerID;
 
- 					});
 
- 					if (ownedGoodShipyard != shipyards.end())
 
- 					{
 
- 						const IShipyard * s = *ownedGoodShipyard;
 
- 						TResources shipCost;
 
- 						s->getBoatCost(shipCost);
 
- 						if (cb->getResourceAmount().canAfford(shipCost))
 
- 						{
 
- 							int3 ret = s->bestLocation();
 
- 							cb->buildBoat(s); //TODO: move actions elsewhere
 
- 							return ret;
 
- 						}
 
- 						else
 
- 						{
 
- 							//TODO gather res
 
- 							return ret;
 
- 							//throw cannotFulfillGoalException("Not enough resources to build a boat");
 
- 						}
 
- 					}
 
- 					else
 
- 					{
 
- 						//TODO pick best shipyard to take over
 
- 						return shipyards.front()->o->visitablePos();
 
- 					}
 
- 				}
 
- 			}
 
- 			else if (src->water && !sectorToReach->water)
 
- 			{
 
- 				//TODO
 
- 				//disembark
 
- 				return ret;
 
- 			}
 
- 			else //use subterranean gates - not needed since gates are now handled via Pathfinder
 
- 			{
 
- 				return ret;
 
- 				//throw cannotFulfillGoalException("Land-land and water-water inter-sector transitions are not implemented!");
 
- 			}
 
- 		}
 
- 		else
 
- 		{
 
- 			return ret;
 
- 			//throw cannotFulfillGoalException("Inter-sector route detection failed: not connected sectors?");
 
- 		}
 
- 	}
 
- 	else //tiles are in same sector
 
- 	{
 
- 		return findFirstVisitableTile(h, dst);
 
- 	}
 
- }
 
- int3 SectorMap::findFirstVisitableTile(HeroPtr h, crint3 dst)
 
- {
 
- 	int3 ret(-1, -1, -1);
 
- 	int3 curtile = dst;
 
- 	while (curtile != h->visitablePos())
 
- 	{
 
- 		auto topObj = cb->getTopObj(curtile);
 
- 		if (topObj && topObj->ID == Obj::HERO && topObj != h.h)
 
- 		{
 
- 			if (cb->getPlayerRelations(h->tempOwner, topObj->tempOwner) != PlayerRelations::ENEMIES)
 
- 			{
 
- 				logAi->warn("Another allied hero stands in our way");
 
- 				return ret;
 
- 			}
 
- 		}
 
- 		if (ai->myCb->getPathsInfo(h.get())->getPathInfo(curtile)->reachable())
 
- 		{
 
- 			return curtile;
 
- 		}
 
- 		else
 
- 		{
 
- 			auto i = parent.find(curtile);
 
- 			if (i != parent.end())
 
- 			{
 
- 				assert(curtile != i->second);
 
- 				curtile = i->second;
 
- 			}
 
- 			else
 
- 			{
 
- 				return ret;
 
- 				//throw cannotFulfillGoalException("Unreachable tile in sector? Should not happen!");
 
- 			}
 
- 		}
 
- 	}
 
- 	return ret;
 
- }
 
- void SectorMap::makeParentBFS(crint3 source)
 
- {
 
- 	parent.clear();
 
- 	int mySector = retrieveTile(source);
 
- 	std::queue<int3> toVisit;
 
- 	toVisit.push(source);
 
- 	while (!toVisit.empty())
 
- 	{
 
- 		int3 curPos = toVisit.front();
 
- 		toVisit.pop();
 
- 		TSectorID & sec = retrieveTile(curPos);
 
- 		assert(sec == mySector); //consider only tiles from the same sector
 
- 		UNUSED(sec);
 
- 		foreach_neighbour(curPos, [&](crint3 neighPos)
 
- 		{
 
- 			if (retrieveTile(neighPos) == mySector && !vstd::contains(parent, neighPos))
 
- 			{
 
- 				if (cb->canMoveBetween(curPos, neighPos))
 
- 				{
 
- 					toVisit.push(neighPos);
 
- 					parent[neighPos] = curPos;
 
- 				}
 
- 			}
 
- 		});
 
- 	}
 
- }
 
- SectorMap::TSectorID & SectorMap::retrieveTile(crint3 pos)
 
- {
 
- 	return retrieveTileN(sector, pos);
 
- }
 
- TerrainTile * SectorMap::getTile(crint3 pos) const
 
- {
 
- 	//out of bounds access should be handled by boost::multi_array
 
- 	//still we cached this array to avoid any checks
 
- 	return visibleTiles->operator[](pos.x)[pos.y][pos.z];
 
- }
 
- std::vector<const CGObjectInstance *> SectorMap::getNearbyObjs(HeroPtr h, bool sectorsAround)
 
- {
 
- 	const Sector * heroSector = &infoOnSectors[retrieveTile(h->visitablePos())];
 
- 	if (sectorsAround)
 
- 	{
 
- 		std::vector<const CGObjectInstance *> ret;
 
- 		for (auto embarkPoint : heroSector->embarkmentPoints)
 
- 		{
 
- 			const Sector * embarkSector = &infoOnSectors[retrieveTile(embarkPoint)];
 
- 			range::copy(embarkSector->visitableObjs, std::back_inserter(ret));
 
- 		}
 
- 		return ret;
 
- 	}
 
- 	return heroSector->visitableObjs;
 
- }
 
 
  |