| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448 | 
							- /*
 
- * Explore.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 "Goals.h"
 
- #include "../VCAI.h"
 
- #include "../AIUtility.h"
 
- #include "../AIhelper.h"
 
- #include "../FuzzyHelper.h"
 
- #include "../ResourceManager.h"
 
- #include "../BuildingManager.h"
 
- #include "../../../lib/StringConstants.h"
 
- #include "../../../lib/CPlayerState.h"
 
- using namespace Goals;
 
- namespace Goals
 
- {
 
- 	struct ExplorationHelper
 
- 	{
 
- 		HeroPtr hero;
 
- 		int sightRadius;
 
- 		float bestValue;
 
- 		TSubgoal bestGoal;
 
- 		VCAI * aip;
 
- 		CCallback * cbp;
 
- 		const TeamState * ts;
 
- 		int3 ourPos;
 
- 		bool allowDeadEndCancellation;
 
- 		bool allowGatherArmy;
 
- 		ExplorationHelper(HeroPtr h, bool gatherArmy)
 
- 		{
 
- 			cbp = cb;
 
- 			aip = ai;
 
- 			hero = h;
 
- 			ts = cbp->getPlayerTeam(ai->playerID);
 
- 			sightRadius = hero->getSightRadius();
 
- 			bestGoal = sptr(Goals::Invalid());
 
- 			bestValue = 0;
 
- 			ourPos = h->visitablePos();
 
- 			allowDeadEndCancellation = true;
 
- 			allowGatherArmy = gatherArmy;
 
- 		}
 
- 		void scanSector(int scanRadius)
 
- 		{
 
- 			int3 tile = int3(0, 0, ourPos.z);
 
- 			const auto & slice = (*(ts->fogOfWarMap))[ourPos.z];
 
- 			for(tile.x = ourPos.x - scanRadius; tile.x <= ourPos.x + scanRadius; tile.x++)
 
- 			{
 
- 				for(tile.y = ourPos.y - scanRadius; tile.y <= ourPos.y + scanRadius; tile.y++)
 
- 				{
 
- 					if(cbp->isInTheMap(tile) && slice[tile.x][tile.y])
 
- 					{
 
- 						scanTile(tile);
 
- 					}
 
- 				}
 
- 			}
 
- 		}
 
- 		void 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);
 
- 			foreach_tile_pos([&](const int3 & pos)
 
- 			{
 
- 				if((*(ts->fogOfWarMap))[pos.z][pos.x][pos.y])
 
- 				{
 
- 					bool hasInvisibleNeighbor = false;
 
- 					foreach_neighbour(cbp, pos, [&](CCallback * cbp, int3 neighbour)
 
- 					{
 
- 						if(!(*(ts->fogOfWarMap))[neighbour.z][neighbour.x][neighbour.y])
 
- 						{
 
- 							hasInvisibleNeighbor = true;
 
- 						}
 
- 					});
 
- 					if(hasInvisibleNeighbor)
 
- 						from.push_back(pos);
 
- 				}
 
- 			});
 
- 			logAi->debug("Exploration scan visible area perimeter for hero %s", hero.name);
 
- 			for(const int3 & tile : from)
 
- 			{
 
- 				scanTile(tile);
 
- 			}
 
- 			if(!bestGoal->invalid())
 
- 			{
 
- 				return;
 
- 			}
 
- 			allowDeadEndCancellation = false;
 
- 			for(int i = 0; i < sightRadius; i++)
 
- 			{
 
- 				getVisibleNeighbours(from, to);
 
- 				vstd::concatenate(from, to);
 
- 				vstd::removeDuplicates(from);
 
- 			}
 
- 			logAi->debug("Exploration scan all possible tiles for hero %s", hero.name);
 
- 			for(const int3 & tile : from)
 
- 			{
 
- 				scanTile(tile);
 
- 			}
 
- 		}
 
- 		void scanTile(const int3 & tile)
 
- 		{
 
- 			if(tile == ourPos
 
- 				|| !aip->ah->isTileAccessible(hero, tile)) //shouldn't happen, but it does
 
- 				return;
 
- 			int tilesDiscovered = howManyTilesWillBeDiscovered(tile);
 
- 			if(!tilesDiscovered)
 
- 				return;
 
- 			auto waysToVisit = aip->ah->howToVisitTile(hero, tile, allowGatherArmy);
 
- 			for(auto goal : waysToVisit)
 
- 			{
 
- 				if(goal->evaluationContext.movementCost <= 0.0) // should not happen
 
- 					continue;
 
- 				float ourValue = (float)tilesDiscovered * tilesDiscovered / goal->evaluationContext.movementCost;
 
- 				if(ourValue > bestValue) //avoid costly checks of tiles that don't reveal much
 
- 				{
 
- 					auto obj = cb->getTopObj(tile);
 
- 					// picking up resources does not yield any exploration at all.
 
- 					// if it blocks the way to some explorable tile AIPathfinder will take care of it
 
- 					if(obj && obj->isBlockedVisitable())
 
- 					{
 
- 						continue;
 
- 					}
 
- 					if(isSafeToVisit(hero, tile))
 
- 					{
 
- 						bestGoal = goal;
 
- 						bestValue = ourValue;
 
- 					}
 
- 				}
 
- 			}
 
- 		}
 
- 		void 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 howManyTilesWillBeDiscovered(const int3 & pos) const
 
- 		{
 
- 			int ret = 0;
 
- 			int3 npos = int3(0, 0, pos.z);
 
- 			const auto & slice = (*(ts->fogOfWarMap))[pos.z];
 
- 			for(npos.x = pos.x - sightRadius; npos.x <= pos.x + sightRadius; npos.x++)
 
- 			{
 
- 				for(npos.y = pos.y - sightRadius; npos.y <= pos.y + sightRadius; npos.y++)
 
- 				{
 
- 					if(cbp->isInTheMap(npos)
 
- 						&& pos.dist2d(npos) - 0.5 < sightRadius
 
- 						&& !slice[npos.x][npos.y])
 
- 					{
 
- 						if(allowDeadEndCancellation
 
- 							&& !hasReachableNeighbor(npos))
 
- 						{
 
- 							continue;
 
- 						}
 
- 						ret++;
 
- 					}
 
- 				}
 
- 			}
 
- 			return ret;
 
- 		}
 
- 		bool hasReachableNeighbor(const int3 &pos) const
 
- 		{
 
- 			for(crint3 dir : int3::getDirs())
 
- 			{
 
- 				int3 tile = pos + dir;
 
- 				if(cbp->isInTheMap(tile))
 
- 				{
 
- 					auto isAccessible = aip->ah->isTileAccessible(hero, tile);
 
- 					if(isAccessible)
 
- 						return true;
 
- 				}
 
- 			}
 
- 			return false;
 
- 		}
 
- 	};
 
- }
 
- bool Explore::operator==(const Explore & other) const
 
- {
 
- 	return other.hero.h == hero.h && other.allowGatherArmy == allowGatherArmy;
 
- }
 
- std::string Explore::completeMessage() const
 
- {
 
- 	return "Hero " + hero.get()->getNameTranslated() + " completed exploration";
 
- }
 
- TSubgoal Explore::whatToDoToAchieve()
 
- {
 
- 	return fh->chooseSolution(getAllPossibleSubgoals());
 
- }
 
- TGoalVec Explore::getAllPossibleSubgoals()
 
- {
 
- 	TGoalVec ret;
 
- 	std::vector<const CGHeroInstance *> heroes;
 
- 	if(hero)
 
- 	{
 
- 		heroes.push_back(hero.h);
 
- 	}
 
- 	else
 
- 	{
 
- 		//heroes = ai->getUnblockedHeroes();
 
- 		heroes = cb->getHeroesInfo();
 
- 		vstd::erase_if(heroes, [](const HeroPtr h)
 
- 		{
 
- 			if(ai->getGoal(h)->goalType == EXPLORE) //do not reassign hero who is already explorer
 
- 				return true;
 
- 			if(!ai->isAbleToExplore(h))
 
- 				return true;
 
- 			return !h->movementPointsRemaining(); //saves time, immobile heroes are useless anyway
 
- 		});
 
- 	}
 
- 	//try to use buildings that uncover map
 
- 	std::vector<const CGObjectInstance *> objs;
 
- 	for(auto obj : ai->visitableObjs)
 
- 	{
 
- 		if(!vstd::contains(ai->alreadyVisited, obj))
 
- 		{
 
- 			switch(obj->ID.num)
 
- 			{
 
- 			case Obj::REDWOOD_OBSERVATORY:
 
- 			case Obj::PILLAR_OF_FIRE:
 
- 			case Obj::CARTOGRAPHER:
 
- 				objs.push_back(obj);
 
- 				break;
 
- 			case Obj::MONOLITH_ONE_WAY_ENTRANCE:
 
- 			case Obj::MONOLITH_TWO_WAY:
 
- 			case Obj::SUBTERRANEAN_GATE:
 
- 				auto tObj = dynamic_cast<const CGTeleport *>(obj);
 
- 				assert(ai->knownTeleportChannels.find(tObj->channel) != ai->knownTeleportChannels.end());
 
- 				if(TeleportChannel::IMPASSABLE != ai->knownTeleportChannels[tObj->channel]->passability)
 
- 					objs.push_back(obj);
 
- 				break;
 
- 			}
 
- 		}
 
- 		else
 
- 		{
 
- 			switch(obj->ID.num)
 
- 			{
 
- 			case Obj::MONOLITH_TWO_WAY:
 
- 			case Obj::SUBTERRANEAN_GATE:
 
- 				auto tObj = dynamic_cast<const CGTeleport *>(obj);
 
- 				if(TeleportChannel::IMPASSABLE == ai->knownTeleportChannels[tObj->channel]->passability)
 
- 					break;
 
- 				for(auto exit : ai->knownTeleportChannels[tObj->channel]->exits)
 
- 				{
 
- 					if(!cb->getObj(exit))
 
- 					{ // Always attempt to visit two-way teleports if one of channel exits is not visible
 
- 						objs.push_back(obj);
 
- 						break;
 
- 					}
 
- 				}
 
- 				break;
 
- 			}
 
- 		}
 
- 	}
 
- 	for(auto h : heroes)
 
- 	{
 
- 		for(auto obj : objs) //double loop, performance risk?
 
- 		{
 
- 			auto waysToVisitObj = ai->ah->howToVisitObj(h, obj, allowGatherArmy);
 
- 			vstd::concatenate(ret, waysToVisitObj);
 
- 		}
 
- 		TSubgoal goal = exploreNearestNeighbour(h);
 
- 		if(!goal->invalid())
 
- 		{
 
- 			ret.push_back(goal);
 
- 		}
 
- 	}
 
- 	if(ret.empty())
 
- 	{
 
- 		for(auto h : heroes)
 
- 		{
 
- 			logAi->trace("Exploration searching for a new point for hero %s", h->getNameTranslated());
 
- 			TSubgoal goal = explorationNewPoint(h);
 
- 			if(goal->invalid())
 
- 			{
 
- 				ai->markHeroUnableToExplore(h); //there is no freely accessible tile, do not poll this hero anymore
 
- 			}
 
- 			else
 
- 			{
 
- 				ret.push_back(goal);
 
- 			}
 
- 		}
 
- 	}
 
- 	//we either don't have hero yet or none of heroes can explore
 
- 	if((!hero || ret.empty()) && ai->canRecruitAnyHero())
 
- 		ret.push_back(sptr(RecruitHero()));
 
- 	if(ret.empty())
 
- 	{
 
- 		throw goalFulfilledException(sptr(Explore().sethero(hero)));
 
- 	}
 
- 	return ret;
 
- }
 
- bool Explore::fulfillsMe(TSubgoal goal)
 
- {
 
- 	if(goal->goalType == EXPLORE)
 
- 	{
 
- 		if(goal->hero)
 
- 			return hero == goal->hero;
 
- 		else
 
- 			return true; //cancel ALL exploration
 
- 	}
 
- 	return false;
 
- }
 
- TSubgoal Explore::explorationBestNeighbour(int3 hpos, HeroPtr h) const
 
- {
 
- 	ExplorationHelper scanResult(h, allowGatherArmy);
 
- 	for(crint3 dir : int3::getDirs())
 
- 	{
 
- 		int3 tile = hpos + dir;
 
- 		if(cb->isInTheMap(tile))
 
- 		{
 
- 			scanResult.scanTile(tile);
 
- 		}
 
- 	}
 
- 	return scanResult.bestGoal;
 
- }
 
- TSubgoal Explore::explorationNewPoint(HeroPtr h) const
 
- {
 
- 	ExplorationHelper scanResult(h, allowGatherArmy);
 
- 	scanResult.scanSector(10);
 
- 	if(!scanResult.bestGoal->invalid())
 
- 	{
 
- 		return scanResult.bestGoal;
 
- 	}
 
- 	scanResult.scanMap();
 
- 	return scanResult.bestGoal;
 
- }
 
- TSubgoal Explore::exploreNearestNeighbour(HeroPtr h) const
 
- {
 
- 	TimeCheck tc("where to explore");
 
- 	int3 hpos = h->visitablePos();
 
- 	//look for nearby objs -> visit them if they're close enough
 
- 	const int DIST_LIMIT = 3;
 
- 	const float COST_LIMIT = .2f; //todo: fine tune
 
- 	std::vector<const CGObjectInstance *> nearbyVisitableObjs;
 
- 	for(int x = hpos.x - DIST_LIMIT; x <= hpos.x + DIST_LIMIT; ++x) //get only local objects instead of all possible objects on the map
 
- 	{
 
- 		for(int y = hpos.y - DIST_LIMIT; y <= hpos.y + DIST_LIMIT; ++y)
 
- 		{
 
- 			for(auto obj : cb->getVisitableObjs(int3(x, y, hpos.z), false))
 
- 			{
 
- 				if(ai->isGoodForVisit(obj, h, COST_LIMIT))
 
- 				{
 
- 					nearbyVisitableObjs.push_back(obj);
 
- 				}
 
- 			}
 
- 		}
 
- 	}
 
- 	if(nearbyVisitableObjs.size())
 
- 	{
 
- 		vstd::removeDuplicates(nearbyVisitableObjs); //one object may occupy multiple tiles
 
- 		boost::sort(nearbyVisitableObjs, CDistanceSorter(h.get()));
 
- 		TSubgoal pickupNearestObj = fh->chooseSolution(ai->ah->howToVisitObj(h, nearbyVisitableObjs.back(), false));
 
- 		if(!pickupNearestObj->invalid())
 
- 		{
 
- 			return pickupNearestObj;
 
- 		}
 
- 	}
 
- 	//check if nearby tiles allow us to reveal anything - this is quick
 
- 	return explorationBestNeighbour(hpos, h);
 
- }
 
 
  |