mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Merge remote-tracking branch 'remotes/nullkiller/ai-refactor-exploration-according-to-new-pathfinder' into develop
This commit is contained in:
		| @@ -403,46 +403,6 @@ bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater) | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| int3 whereToExplore(HeroPtr h) | ||||
| { | ||||
| 	TimeCheck tc("where to explore"); | ||||
| 	int radius = h->getSightRadius(); | ||||
| 	int3 hpos = h->visitablePos(); | ||||
|  | ||||
| 	//look for nearby objs -> visit them if they're close enouh | ||||
| 	const int DIST_LIMIT = 3; | ||||
| 	const int MP_LIMIT = DIST_LIMIT * 150; // aproximate cost of diagonal movement | ||||
|  | ||||
| 	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, MP_LIMIT)) | ||||
| 				{ | ||||
| 					nearbyVisitableObjs.push_back(obj); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	vstd::removeDuplicates(nearbyVisitableObjs); //one object may occupy multiple tiles | ||||
| 	boost::sort(nearbyVisitableObjs, CDistanceSorter(h.get())); | ||||
| 	if(nearbyVisitableObjs.size()) | ||||
| 		return nearbyVisitableObjs.back()->visitablePos(); | ||||
|  | ||||
| 	try //check if nearby tiles allow us to reveal anything - this is quick | ||||
| 	{ | ||||
| 		return ai->explorationBestNeighbour(hpos, radius, h); | ||||
| 	} | ||||
| 	catch(cannotFulfillGoalException & e) | ||||
| 	{ | ||||
| 		//perform exhaustive search | ||||
| 		return ai->explorationNewPoint(h); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool isBlockedBorderGate(int3 tileToHit) //TODO: is that function needed? should be handled by pathfinder | ||||
| { | ||||
| 	if(cb->getTile(tileToHit)->topVisitableId() != Obj::BORDER_GATE) | ||||
| @@ -461,51 +421,6 @@ bool isBlockVisitObj(const int3 & pos) | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| bool hasReachableNeighbor(const int3 &pos, HeroPtr hero, CCallback * cbp) | ||||
| { | ||||
| 	for(crint3 dir : int3::getDirs()) | ||||
| 	{ | ||||
| 		int3 tile = pos + dir; | ||||
| 		if(cbp->isInTheMap(tile) && cbp->getPathsInfo(hero.get())->getPathInfo(tile)->reachable()) | ||||
| 		{ | ||||
| 			return true; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| int howManyTilesWillBeDiscovered(const int3 & pos, int radious, CCallback * cbp, HeroPtr hero) | ||||
| { | ||||
| 	int ret = 0; | ||||
| 	for(int x = pos.x - radious; x <= pos.x + radious; x++) | ||||
| 	{ | ||||
| 		for(int y = pos.y - radious; y <= pos.y + radious; y++) | ||||
| 		{ | ||||
| 			int3 npos = int3(x, y, pos.z); | ||||
| 			if(cbp->isInTheMap(npos) && pos.dist2d(npos) - 0.5 < radious && !cbp->isVisible(npos)) | ||||
| 			{ | ||||
| 				if(hasReachableNeighbor(npos, hero, cbp)) | ||||
| 					ret++; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| void getVisibleNeighbours(const std::vector<int3> & tiles, std::vector<int3> & out) | ||||
| { | ||||
| 	for(const int3 & tile : tiles) | ||||
| 	{ | ||||
| 		foreach_neighbour(tile, [&](int3 neighbour) | ||||
| 		{ | ||||
| 			if(cb->isVisible(neighbour)) | ||||
| 				out.push_back(neighbour); | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| creInfo infoFromDC(const dwellingContent & dc) | ||||
| { | ||||
| 	creInfo ci; | ||||
|   | ||||
| @@ -159,9 +159,6 @@ void foreach_tile_pos(CCallback * cbp, std::function<void(CCallback * cbp, const | ||||
| 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, CCallback * cbp, HeroPtr hero); | ||||
| void getVisibleNeighbours(const std::vector<int3> & tiles, std::vector<int3> & out); | ||||
|  | ||||
| bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater); | ||||
| bool isBlockedBorderGate(int3 tileToHit); | ||||
| bool isBlockVisitObj(const int3 & pos); | ||||
| @@ -181,7 +178,6 @@ bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2); | ||||
| bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2); | ||||
| ui64 howManyReinforcementsCanBuy(const CArmedInstance * h, const CGDwelling * t); | ||||
| ui64 howManyReinforcementsCanGet(const CArmedInstance * h, const CGTownInstance * t); | ||||
| int3 whereToExplore(HeroPtr h); | ||||
| uint32_t distanceToTile(const CGHeroInstance * hero, int3 pos); | ||||
|  | ||||
| class CDistanceSorter | ||||
|   | ||||
| @@ -148,6 +148,11 @@ std::vector<AIPath> AIhelper::getPathsToTile(HeroPtr hero, int3 tile) | ||||
| 	return pathfindingManager->getPathsToTile(hero, tile); | ||||
| } | ||||
|  | ||||
| bool AIhelper::isTileAccessible(HeroPtr hero, int3 tile) | ||||
| { | ||||
| 	return pathfindingManager->isTileAccessible(hero, tile); | ||||
| } | ||||
|  | ||||
| void AIhelper::resetPaths() | ||||
| { | ||||
| 	pathfindingManager->resetPaths(); | ||||
|   | ||||
| @@ -60,6 +60,7 @@ public: | ||||
| 	Goals::TGoalVec howToVisitTile(int3 tile) override; | ||||
| 	Goals::TGoalVec howToVisitObj(ObjectIdRef obj) override; | ||||
| 	std::vector<AIPath> getPathsToTile(HeroPtr hero, int3 tile) override; | ||||
| 	bool isTileAccessible(HeroPtr hero, int3 tile) override; | ||||
| 	void resetPaths() override; | ||||
|  | ||||
| private: | ||||
|   | ||||
| @@ -318,8 +318,15 @@ void HeroMovementGoalEngineBase::setSharedFuzzyVariables(Goals::AbstractGoal & g | ||||
| { | ||||
| 	float turns = calculateTurnDistanceInputValue(goal); | ||||
| 	float missionImportanceData = 0; | ||||
|  | ||||
| 	if(vstd::contains(ai->lockedHeroes, goal.hero)) | ||||
| 	{ | ||||
| 		missionImportanceData = ai->lockedHeroes[goal.hero]->priority; | ||||
| 	} | ||||
| 	else if(goal.parent) | ||||
| 	{ | ||||
| 		missionImportanceData = goal.parent->priority; | ||||
| 	} | ||||
|  | ||||
| 	float strengthRatioData = 10.0f; //we are much stronger than enemy | ||||
| 	ui64 danger = evaluateDanger(goal.tile, goal.hero.h); | ||||
| @@ -410,6 +417,9 @@ VisitTileEngine::VisitTileEngine() //so far no VisitTile-specific variables that | ||||
|  | ||||
| float VisitTileEngine::evaluate(Goals::VisitTile & goal) | ||||
| { | ||||
| 	// for now any visit tile is usually much more in priority then visit obj so lets reduce it | ||||
| 	const int scale = 2; | ||||
|  | ||||
| 	//we assume that hero is already set and we want to choose most suitable one for the mission | ||||
| 	if(!goal.hero) | ||||
| 		return 0; | ||||
| @@ -421,7 +431,8 @@ float VisitTileEngine::evaluate(Goals::VisitTile & goal) | ||||
| 	try | ||||
| 	{ | ||||
| 		engine.process(); | ||||
| 		goal.priority = value->getValue(); | ||||
|  | ||||
| 		goal.priority = value->getValue() / scale; | ||||
| 	} | ||||
| 	catch(fl::Exception & fe) | ||||
| 	{ | ||||
|   | ||||
| @@ -78,6 +78,11 @@ ui64 FuzzyHelper::estimateBankDanger(const CBank * bank) | ||||
|  | ||||
| float FuzzyHelper::evaluate(Goals::VisitTile & g) | ||||
| { | ||||
| 	if(g.parent) | ||||
| 	{ | ||||
| 		g.parent->accept(this); | ||||
| 	} | ||||
|  | ||||
| 	return visitTileEngine.evaluate(g); | ||||
| } | ||||
|  | ||||
| @@ -108,6 +113,11 @@ float FuzzyHelper::evaluate(Goals::CompleteQuest & g) | ||||
|  | ||||
| float FuzzyHelper::evaluate(Goals::VisitObj & g) | ||||
| { | ||||
| 	if(g.parent) | ||||
| 	{ | ||||
| 		g.parent->accept(this); | ||||
| 	} | ||||
|  | ||||
| 	return visitObjEngine.evaluate(g); | ||||
| } | ||||
| float FuzzyHelper::evaluate(Goals::VisitHero & g) | ||||
|   | ||||
| @@ -18,7 +18,7 @@ | ||||
| #include "../../../lib/mapping/CMap.h" //for victory conditions | ||||
| #include "../../../lib/CPathfinder.h" | ||||
| #include "../../../lib/StringConstants.h" | ||||
|  | ||||
| #include "../../../lib/CPlayerState.h" | ||||
|  | ||||
| extern boost::thread_specific_ptr<CCallback> cb; | ||||
| extern boost::thread_specific_ptr<VCAI> ai; | ||||
| @@ -28,7 +28,7 @@ using namespace Goals; | ||||
|  | ||||
| bool Explore::operator==(const Explore & other) const | ||||
| { | ||||
| 	return other.hero.h == hero.h; | ||||
| 	return other.hero.h == hero.h && other.allowGatherArmy == allowGatherArmy; | ||||
| } | ||||
|  | ||||
| std::string Explore::completeMessage() const | ||||
| @@ -127,34 +127,36 @@ TGoalVec Explore::getAllPossibleSubgoals() | ||||
| 	{ | ||||
| 		for(auto obj : objs) //double loop, performance risk? | ||||
| 		{ | ||||
| 			auto waysToVisitObj = ai->ah->howToVisitObj(h, obj); | ||||
| 			auto waysToVisitObj = ai->ah->howToVisitObj(h, obj, allowGatherArmy); | ||||
|  | ||||
| 			vstd::concatenate(ret, waysToVisitObj); | ||||
| 		} | ||||
|  | ||||
| 		int3 t = whereToExplore(h); | ||||
| 		if(t.valid()) | ||||
| 		{ | ||||
| 			ret.push_back(sptr(VisitTile(t).sethero(h))); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			//FIXME: possible issues when gathering army to break | ||||
| 			if(hero.h == h || //exporation is assigned to this hero | ||||
| 				(!hero && h == primaryHero)) //not assigned to specific hero, let our main hero do the job | ||||
| 			{ | ||||
| 				t = ai->explorationDesperate(h);  //check this only ONCE, high cost | ||||
| 				if (t.valid()) //don't waste time if we are completely blocked | ||||
| 				{ | ||||
| 					auto waysToVisitTile = ai->ah->howToVisitTile(h, t); | ||||
| 		TSubgoal goal = exploreNearestNeighbour(h); | ||||
|  | ||||
| 					vstd::concatenate(ret, waysToVisitTile); | ||||
| 					continue; | ||||
| 				} | ||||
| 			} | ||||
| 			ai->markHeroUnableToExplore(h); //there is no freely accessible tile, do not poll this hero anymore | ||||
| 		if(!goal->invalid()) | ||||
| 		{ | ||||
| 			ret.push_back(goal); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if(ret.empty()) | ||||
| 	{ | ||||
| 		for(auto h : heroes) | ||||
| 		{ | ||||
| 			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())); | ||||
| @@ -163,7 +165,6 @@ TGoalVec Explore::getAllPossibleSubgoals() | ||||
| 	{ | ||||
| 		throw goalFulfilledException(sptr(Explore().sethero(hero))); | ||||
| 	} | ||||
| 	//throw cannotFulfillGoalException("Cannot explore - no possible ways found!"); | ||||
|  | ||||
| 	return ret; | ||||
| } | ||||
| @@ -179,3 +180,244 @@ bool Explore::fulfillsMe(TSubgoal goal) | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| bool Explore::hasReachableNeighbor(const int3 &pos, HeroPtr hero, CCallback * cbp, VCAI * vcai) const | ||||
| { | ||||
| 	for(crint3 dir : int3::getDirs()) | ||||
| 	{ | ||||
| 		int3 tile = pos + dir; | ||||
| 		if(cbp->isInTheMap(tile)) | ||||
| 		{ | ||||
| 			auto isAccessible = vcai->ah->isTileAccessible(hero, tile); | ||||
|  | ||||
| 			if(isAccessible) | ||||
| 				return true; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| int Explore::howManyTilesWillBeDiscovered( | ||||
| 	const int3 & pos,  | ||||
| 	int radious,  | ||||
| 	CCallback * cbp,  | ||||
| 	const TeamState * ts, | ||||
| 	VCAI * aip, | ||||
| 	HeroPtr h) const | ||||
| { | ||||
| 	int ret = 0; | ||||
| 	for(int x = pos.x - radious; x <= pos.x + radious; x++) | ||||
| 	{ | ||||
| 		for(int y = pos.y - radious; y <= pos.y + radious; y++) | ||||
| 		{ | ||||
| 			int3 npos = int3(x, y, pos.z); | ||||
| 			if(cbp->isInTheMap(npos)  | ||||
| 				&& pos.dist2d(npos) - 0.5 < radious  | ||||
| 				&& !ts->fogOfWarMap[npos.x][npos.y][npos.z] | ||||
| 				&& hasReachableNeighbor(npos, h, cbp, aip)) | ||||
| 			{ | ||||
| 				ret++; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| TSubgoal Explore::explorationBestNeighbour(int3 hpos, int radius, HeroPtr h) const | ||||
| { | ||||
| 	std::map<int3, int> dstToRevealedTiles; | ||||
| 	VCAI * aip = ai.get(); | ||||
| 	CCallback * cbp = cb.get(); | ||||
| 	const TeamState * ts = cbp->getPlayerTeam(ai->playerID); | ||||
|  | ||||
| 	for(crint3 dir : int3::getDirs()) | ||||
| 	{ | ||||
| 		int3 tile = hpos + dir; | ||||
| 		if(cb->isInTheMap(tile)) | ||||
| 		{ | ||||
| 			if(isBlockVisitObj(tile)) | ||||
| 				continue; | ||||
|  | ||||
| 			if(isSafeToVisit(h, tile) && ai->isAccessibleForHero(tile, h)) | ||||
| 			{ | ||||
| 				auto distance = hpos.dist2d(tile); // diagonal movement opens more tiles but spends more mp | ||||
| 				int tilesDiscovered = howManyTilesWillBeDiscovered(tile, radius, cbp, ts, aip, h); | ||||
|  | ||||
| 				dstToRevealedTiles[tile] = tilesDiscovered / distance; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if(dstToRevealedTiles.empty()) //yes, it DID happen! | ||||
| 		return sptr(Invalid()); | ||||
|  | ||||
| 	auto best = dstToRevealedTiles.begin(); | ||||
| 	for(auto i = dstToRevealedTiles.begin(); i != dstToRevealedTiles.end(); i++) | ||||
| 	{ | ||||
| 		const CGPathNode * pn = cb->getPathsInfo(h.get())->getPathInfo(i->first); | ||||
| 		//const TerrainTile *t = cb->getTile(i->first); | ||||
| 		if(best->second < i->second && pn->reachable() && pn->accessible == CGPathNode::ACCESSIBLE) | ||||
| 			best = i; | ||||
| 	} | ||||
|  | ||||
| 	if(best->second) | ||||
| 		return sptr(VisitTile(best->first).sethero(h)); | ||||
|  | ||||
| 	return sptr(Invalid()); | ||||
| } | ||||
|  | ||||
| TSubgoal Explore::explorationNewPoint(HeroPtr h) const | ||||
| { | ||||
| 	int radius = h->getSightRadius(); | ||||
| 	CCallback * cbp = cb.get(); | ||||
| 	VCAI *aip = ai.get(); | ||||
| 	const TeamState * ts = cbp->getPlayerTeam(aip->playerID); | ||||
|  | ||||
| 	int3 mapSize = cbp->getMapSize(); | ||||
| 	int perimiter = 2 * radius * (mapSize.x + mapSize.y); | ||||
| 	 | ||||
| 	std::vector<int3> from; | ||||
| 	std::vector<int3> to; | ||||
|  | ||||
| 	from.reserve(perimiter); | ||||
| 	to.reserve(perimiter); | ||||
|  | ||||
| 	foreach_tile_pos([&](const int3 & pos) | ||||
| 	{ | ||||
| 		if(ts->fogOfWarMap[pos.x][pos.y][pos.z]) | ||||
| 		{ | ||||
| 			bool hasInvisibleNeighbor = false; | ||||
|  | ||||
| 			foreach_neighbour(cbp, pos, [&](CCallback * cbp, int3 neighbour) | ||||
| 			{ | ||||
| 				if(!ts->fogOfWarMap[neighbour.x][neighbour.y][neighbour.z]) | ||||
| 				{ | ||||
| 					hasInvisibleNeighbor = true; | ||||
| 				} | ||||
| 			}); | ||||
|  | ||||
| 			if(hasInvisibleNeighbor) | ||||
| 				from.push_back(pos); | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	for(int i = 0; i < radius; i++) | ||||
| 	{ | ||||
| 		getVisibleNeighbours(from, to, cbp, ts); | ||||
| 		vstd::concatenate(from, to); | ||||
| 		vstd::removeDuplicates(from); | ||||
| 	} | ||||
|  | ||||
| 	return explorationScanRange(h, from); | ||||
| } | ||||
|  | ||||
| TSubgoal Explore::explorationScanRange(HeroPtr h, std::vector<int3> & range) const | ||||
| { | ||||
| 	int radius = h->getSightRadius(); | ||||
| 	CCallback * cbp = cb.get(); | ||||
| 	VCAI *aip = ai.get(); | ||||
| 	const TeamState * ts = cbp->getPlayerTeam(aip->playerID); | ||||
|  | ||||
| 	float bestValue = 0; //discovered tile to node distance ratio | ||||
| 	TSubgoal bestWay = sptr(Invalid()); | ||||
| 	int3 ourPos = h->convertPosition(h->pos, false); | ||||
|  | ||||
| 	for(const int3 & tile : range) | ||||
| 	{ | ||||
| 		if(tile == ourPos) //shouldn't happen, but it does | ||||
| 			continue; | ||||
|  | ||||
| 		int tilesDiscovered = howManyTilesWillBeDiscovered(tile, radius, cbp, ts, aip, h); | ||||
| 		if(!tilesDiscovered) | ||||
| 			continue; | ||||
|  | ||||
| 		auto waysToVisit = aip->ah->howToVisitTile(h, tile, allowGatherArmy); | ||||
| 		for(auto goal : waysToVisit) | ||||
| 		{ | ||||
| 			if(goal->evaluationContext.movementCost == 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); | ||||
| 				if(obj && obj->blockVisit && !isObjectRemovable(obj)) //we can't stand on that object | ||||
| 				{ | ||||
| 					continue; | ||||
| 				} | ||||
|  | ||||
| 				if(isSafeToVisit(h, tile)) | ||||
| 				{ | ||||
| 					bestWay = goal; | ||||
| 					bestValue = ourValue; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if(!bestWay->invalid()) | ||||
| 	{ | ||||
| 		return bestWay; | ||||
| 	} | ||||
|  | ||||
| 	return bestWay; | ||||
| } | ||||
|  | ||||
| TSubgoal Explore::exploreNearestNeighbour(HeroPtr h) const | ||||
| { | ||||
| 	TimeCheck tc("where to explore"); | ||||
| 	int radius = h->getSightRadius(); | ||||
| 	int3 hpos = h->visitablePos(); | ||||
|  | ||||
| 	//look for nearby objs -> visit them if they're close enouh | ||||
| 	const int DIST_LIMIT = 3; | ||||
| 	const int MP_LIMIT = DIST_LIMIT * 150; // aproximate cost of diagonal movement | ||||
|  | ||||
| 	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, MP_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, radius, h); | ||||
| } | ||||
|  | ||||
| void Explore::getVisibleNeighbours(const std::vector<int3> & tiles, std::vector<int3> & out, CCallback * cbp, const TeamState * ts) const | ||||
| { | ||||
| 	for(const int3 & tile : tiles) | ||||
| 	{ | ||||
| 		foreach_neighbour(cbp, tile, [&](CCallback * cbp, int3 neighbour) | ||||
| 		{ | ||||
| 			if(ts->fogOfWarMap[neighbour.x][neighbour.y][neighbour.z]) | ||||
| 			{ | ||||
| 				out.push_back(neighbour); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
| @@ -19,12 +19,21 @@ namespace Goals | ||||
| { | ||||
| 	class DLL_EXPORT Explore : public CGoal<Explore> | ||||
| 	{ | ||||
| 	private: | ||||
| 		bool allowGatherArmy; | ||||
|  | ||||
| 	public: | ||||
| 		Explore() | ||||
| 			: CGoal(Goals::EXPLORE) | ||||
| 		Explore(bool allowGatherArmy) | ||||
| 			: CGoal(Goals::EXPLORE), allowGatherArmy(allowGatherArmy) | ||||
| 		{ | ||||
| 			priority = 1; | ||||
| 		} | ||||
|  | ||||
| 		Explore() | ||||
| 			: Explore(true) | ||||
| 		{ | ||||
| 		} | ||||
|  | ||||
| 		Explore(HeroPtr h) | ||||
| 			: CGoal(Goals::EXPLORE) | ||||
| 		{ | ||||
| @@ -36,5 +45,26 @@ namespace Goals | ||||
| 		std::string completeMessage() const override; | ||||
| 		bool fulfillsMe(TSubgoal goal) override; | ||||
| 		virtual bool operator==(const Explore & other) const override; | ||||
|  | ||||
| 	private: | ||||
| 		TSubgoal exploreNearestNeighbour(HeroPtr h) const; | ||||
| 		TSubgoal explorationNewPoint(HeroPtr h) const; | ||||
| 		TSubgoal explorationBestNeighbour(int3 hpos, int radius, HeroPtr h) const; | ||||
| 		TSubgoal explorationScanRange(HeroPtr h, std::vector<int3> & range) const; | ||||
| 		bool hasReachableNeighbor(const int3 &pos, HeroPtr hero, CCallback * cbp, VCAI * vcai) const; | ||||
|  | ||||
| 		void getVisibleNeighbours( | ||||
| 			const std::vector<int3> & tiles,  | ||||
| 			std::vector<int3> & out,  | ||||
| 			CCallback * cbp,  | ||||
| 			const TeamState * ts) const; | ||||
|  | ||||
| 		int howManyTilesWillBeDiscovered( | ||||
| 			const int3 & pos, | ||||
| 			int radious, | ||||
| 			CCallback * cbp, | ||||
| 			const TeamState * ts, | ||||
| 			VCAI * aip, | ||||
| 			HeroPtr h) const; | ||||
| 	}; | ||||
| } | ||||
|   | ||||
| @@ -193,8 +193,10 @@ TGoalVec GatherArmy::getAllPossibleSubgoals() | ||||
|  | ||||
| 	if(ret.empty()) | ||||
| 	{ | ||||
| 		const bool allowGatherArmy = false; | ||||
|  | ||||
| 		if(hero == ai->primaryHero()) | ||||
| 			ret.push_back(sptr(Explore())); | ||||
| 			ret.push_back(sptr(Explore(allowGatherArmy))); | ||||
| 		else | ||||
| 			throw cannotFulfillGoalException("No ways to gather army"); | ||||
| 	} | ||||
|   | ||||
| @@ -189,6 +189,23 @@ bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNode | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| bool AINodeStorage::isTileAccessible(int3 pos, const EPathfindingLayer layer) const | ||||
| { | ||||
| 	std::vector<AIPath> paths; | ||||
| 	auto chains = nodes[pos.x][pos.y][pos.z][layer]; | ||||
| 	auto initialPos = hero->visitablePos(); | ||||
|  | ||||
| 	for(const AIPathNode & node : chains) | ||||
| 	{ | ||||
| 		if(node.action != CGPathNode::ENodeAction::UNKNOWN) | ||||
| 		{ | ||||
| 			return true; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| std::vector<AIPath> AINodeStorage::getChainInfo(int3 pos, bool isOnLand) const | ||||
| { | ||||
| 	std::vector<AIPath> paths; | ||||
|   | ||||
| @@ -111,6 +111,7 @@ public: | ||||
| 	bool hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const; | ||||
| 	boost::optional<AIPathNode *> getOrCreateNode(const int3 & coord, const EPathfindingLayer layer, int chainNumber); | ||||
| 	std::vector<AIPath> getChainInfo(int3 pos, bool isOnLand) const; | ||||
| 	bool isTileAccessible(int3 pos, const EPathfindingLayer layer) const; | ||||
|  | ||||
| 	void setHero(HeroPtr heroPtr) | ||||
| 	{ | ||||
|   | ||||
| @@ -35,9 +35,34 @@ void AIPathfinder::init() | ||||
| 	storageMap.clear(); | ||||
| } | ||||
|  | ||||
| bool AIPathfinder::isTileAccessible(HeroPtr hero, int3 tile) | ||||
| { | ||||
| 	boost::unique_lock<boost::mutex> storageLock(storageMutex); | ||||
|  | ||||
| 	std::shared_ptr<AINodeStorage> nodeStorage = getOrCreateStorage(hero); | ||||
|  | ||||
| 	return nodeStorage->isTileAccessible(tile, EPathfindingLayer::LAND) | ||||
| 		|| nodeStorage->isTileAccessible(tile, EPathfindingLayer::SAIL); | ||||
| } | ||||
|  | ||||
| std::vector<AIPath> AIPathfinder::getPathInfo(HeroPtr hero, int3 tile) | ||||
| { | ||||
| 	boost::unique_lock<boost::mutex> storageLock(storageMutex); | ||||
|  | ||||
| 	std::shared_ptr<AINodeStorage> nodeStorage = getOrCreateStorage(hero); | ||||
|  | ||||
| 	const TerrainTile * tileInfo = cb->getTile(tile, false); | ||||
|  | ||||
| 	if(!tileInfo) | ||||
| 	{ | ||||
| 		return std::vector<AIPath>(); | ||||
| 	} | ||||
|  | ||||
| 	return nodeStorage->getChainInfo(tile, !tileInfo->isWater()); | ||||
| } | ||||
|  | ||||
| std::shared_ptr<AINodeStorage> AIPathfinder::getOrCreateStorage(HeroPtr hero) | ||||
| { | ||||
| 	std::shared_ptr<AINodeStorage> nodeStorage; | ||||
|  | ||||
| 	if(!vstd::contains(storageMap, hero)) | ||||
| @@ -56,7 +81,7 @@ std::vector<AIPath> AIPathfinder::getPathInfo(HeroPtr hero, int3 tile) | ||||
|  | ||||
| 		storageMap[hero] = nodeStorage; | ||||
| 		nodeStorage->setHero(hero.get()); | ||||
| 		 | ||||
|  | ||||
| 		auto config = std::make_shared<AIPathfinding::AIPathfinderConfig>(cb, ai, nodeStorage); | ||||
|  | ||||
| 		cb->calculatePaths(config, hero.get()); | ||||
| @@ -66,12 +91,6 @@ std::vector<AIPath> AIPathfinder::getPathInfo(HeroPtr hero, int3 tile) | ||||
| 		nodeStorage = storageMap.at(hero); | ||||
| 	} | ||||
|  | ||||
| 	const TerrainTile * tileInfo = cb->getTile(tile, false); | ||||
|  | ||||
| 	if(!tileInfo) | ||||
| 	{ | ||||
| 		return std::vector<AIPath>(); | ||||
| 	} | ||||
|  | ||||
| 	return nodeStorage->getChainInfo(tile, !tileInfo->isWater()); | ||||
| 	return nodeStorage; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -26,6 +26,8 @@ private: | ||||
| public: | ||||
| 	AIPathfinder(CPlayerSpecificInfoCallback * cb, VCAI * ai); | ||||
| 	std::vector<AIPath> getPathInfo(HeroPtr hero, int3 tile); | ||||
| 	bool isTileAccessible(HeroPtr hero, int3 tile); | ||||
| 	std::shared_ptr<AINodeStorage> getOrCreateStorage(HeroPtr hero); | ||||
| 	void clear(); | ||||
| 	void init(); | ||||
| }; | ||||
|   | ||||
| @@ -105,6 +105,11 @@ std::vector<AIPath> PathfindingManager::getPathsToTile(HeroPtr hero, int3 tile) | ||||
| 	return pathfinder->getPathInfo(hero, tile); | ||||
| } | ||||
|  | ||||
| bool PathfindingManager::isTileAccessible(HeroPtr hero, int3 tile) | ||||
| { | ||||
| 	return pathfinder->isTileAccessible(hero, tile); | ||||
| } | ||||
|  | ||||
| Goals::TGoalVec PathfindingManager::findPath( | ||||
| 	HeroPtr hero, | ||||
| 	crint3 dest, | ||||
|   | ||||
| @@ -26,6 +26,7 @@ public: | ||||
| 	virtual Goals::TGoalVec howToVisitTile(int3 tile) = 0; | ||||
| 	virtual Goals::TGoalVec howToVisitObj(ObjectIdRef obj) = 0; | ||||
| 	virtual std::vector<AIPath> getPathsToTile(HeroPtr hero, int3 tile) = 0; | ||||
| 	virtual bool isTileAccessible(HeroPtr hero, int3 tile) = 0; | ||||
| }; | ||||
| 	 | ||||
| class PathfindingManager : public IPathfindingManager | ||||
| @@ -46,6 +47,7 @@ public: | ||||
| 	Goals::TGoalVec howToVisitTile(int3 tile) override; | ||||
| 	Goals::TGoalVec howToVisitObj(ObjectIdRef obj) override; | ||||
| 	std::vector<AIPath> getPathsToTile(HeroPtr hero, int3 tile) override; | ||||
| 	bool isTileAccessible(HeroPtr hero, int3 tile) override; | ||||
| 	void resetPaths() override; | ||||
|  | ||||
| private: | ||||
|   | ||||
							
								
								
									
										193
									
								
								AI/VCAI/VCAI.cpp
									
									
									
									
									
								
							
							
						
						
									
										193
									
								
								AI/VCAI/VCAI.cpp
									
									
									
									
									
								
							| @@ -1530,33 +1530,42 @@ void VCAI::wander(HeroPtr h) | ||||
| 				vstd::concatenate(targetObjectGoals, ah->howToVisitObj(h, destination, false)); | ||||
| 			} | ||||
|  | ||||
| 			auto bestObjectGoal = fh->chooseSolution(targetObjectGoals); | ||||
|  | ||||
| 			//wander should not cause heroes to be reserved - they are always considered free | ||||
| 			if(bestObjectGoal->goalType == Goals::VISIT_OBJ) | ||||
| 			if(targetObjectGoals.size()) | ||||
| 			{ | ||||
| 				auto chosenObject = cb->getObjInstance(ObjectInstanceID(bestObjectGoal->objid)); | ||||
| 				if(chosenObject != nullptr) | ||||
| 					logAi->debug("Of all %d destinations, object %s at pos=%s seems nice", dests.size(), chosenObject->getObjectName(), chosenObject->pos.toString()); | ||||
| 				auto bestObjectGoal = fh->chooseSolution(targetObjectGoals); | ||||
|  | ||||
| 				//wander should not cause heroes to be reserved - they are always considered free | ||||
| 				if(bestObjectGoal->goalType == Goals::VISIT_OBJ) | ||||
| 				{ | ||||
| 					auto chosenObject = cb->getObjInstance(ObjectInstanceID(bestObjectGoal->objid)); | ||||
| 					if(chosenObject != nullptr) | ||||
| 						logAi->debug("Of all %d destinations, object %s at pos=%s seems nice", dests.size(), chosenObject->getObjectName(), chosenObject->pos.toString()); | ||||
| 				} | ||||
| 				else | ||||
| 					logAi->debug("Trying to realize goal of type %s as part of wandering.", bestObjectGoal->name()); | ||||
|  | ||||
| 				try | ||||
| 				{ | ||||
| 					decomposeGoal(bestObjectGoal)->accept(this); | ||||
| 				} | ||||
| 				catch(const goalFulfilledException & e) | ||||
| 				{ | ||||
| 					if(e.goal->goalType == Goals::EGoals::VISIT_TILE || e.goal->goalType == Goals::EGoals::VISIT_OBJ) | ||||
| 						continue; | ||||
|  | ||||
| 					throw e; | ||||
| 				} | ||||
| 			} | ||||
| 			else | ||||
| 				logAi->debug("Trying to realize goal of type %s as part of wandering.", bestObjectGoal->name()); | ||||
|  | ||||
| 			try | ||||
| 			{ | ||||
| 				decomposeGoal(bestObjectGoal)->accept(this); | ||||
| 			} | ||||
| 			catch(const goalFulfilledException & e) | ||||
| 			{ | ||||
| 				if(e.goal->goalType == Goals::EGoals::VISIT_TILE || e.goal->goalType == Goals::EGoals::VISIT_OBJ) | ||||
| 					continue; | ||||
|  | ||||
| 				throw e; | ||||
| 				logAi->debug("Nowhere more to go..."); | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 			visitTownIfAny(h); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	visitTownIfAny(h); //in case hero is just sitting in town | ||||
| } | ||||
|  | ||||
| @@ -2471,154 +2480,6 @@ void VCAI::buildArmyIn(const CGTownInstance * t) | ||||
| 	moveCreaturesToHero(t); | ||||
| } | ||||
|  | ||||
| int3 VCAI::explorationBestNeighbour(int3 hpos, int radius, HeroPtr h) | ||||
| { | ||||
| 	std::map<int3, int> dstToRevealedTiles; | ||||
|  | ||||
| 	for(crint3 dir : int3::getDirs()) | ||||
| 	{ | ||||
| 		int3 tile = hpos + dir; | ||||
| 		if(cb->isInTheMap(tile)) | ||||
| 		{ | ||||
| 			if(isBlockVisitObj(tile)) | ||||
| 				continue; | ||||
|  | ||||
| 			if(isSafeToVisit(h, tile) && isAccessibleForHero(tile, h)) | ||||
| 			{ | ||||
| 				auto distance = hpos.dist2d(tile); // diagonal movement opens more tiles but spends more mp | ||||
| 				dstToRevealedTiles[tile] = howManyTilesWillBeDiscovered(tile, radius, cb.get(), h) / distance; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if(dstToRevealedTiles.empty()) //yes, it DID happen! | ||||
| 		throw cannotFulfillGoalException("No neighbour will bring new discoveries!"); | ||||
|  | ||||
| 	auto best = dstToRevealedTiles.begin(); | ||||
| 	for(auto i = dstToRevealedTiles.begin(); i != dstToRevealedTiles.end(); i++) | ||||
| 	{ | ||||
| 		const CGPathNode * pn = cb->getPathsInfo(h.get())->getPathInfo(i->first); | ||||
| 		//const TerrainTile *t = cb->getTile(i->first); | ||||
| 		if(best->second < i->second && pn->reachable() && pn->accessible == CGPathNode::ACCESSIBLE) | ||||
| 			best = i; | ||||
| 	} | ||||
|  | ||||
| 	if(best->second) | ||||
| 		return best->first; | ||||
|  | ||||
| 	throw cannotFulfillGoalException("No neighbour will bring new discoveries!"); | ||||
| } | ||||
|  | ||||
| int3 VCAI::explorationNewPoint(HeroPtr h) | ||||
| { | ||||
| 	int radius = h->getSightRadius(); | ||||
| 	CCallback * cbp = cb.get(); | ||||
| 	const CGHeroInstance * hero = h.get(); | ||||
|  | ||||
| 	std::vector<std::vector<int3>> tiles; //tiles[distance_to_fow] | ||||
| 	tiles.resize(radius); | ||||
|  | ||||
| 	foreach_tile_pos([&](const int3 & pos) | ||||
| 	{ | ||||
| 		if(!cbp->isVisible(pos)) | ||||
| 			tiles[0].push_back(pos); | ||||
| 	}); | ||||
|  | ||||
| 	float bestValue = 0; //discovered tile to node distance ratio | ||||
| 	int3 bestTile(-1, -1, -1); | ||||
| 	int3 ourPos = h->convertPosition(h->pos, false); | ||||
|  | ||||
| 	for(int i = 1; i < radius; i++) | ||||
| 	{ | ||||
| 		getVisibleNeighbours(tiles[i - 1], tiles[i]); | ||||
| 		vstd::removeDuplicates(tiles[i]); | ||||
|  | ||||
| 		for(const int3 & tile : tiles[i]) | ||||
| 		{ | ||||
| 			if(tile == ourPos) //shouldn't happen, but it does | ||||
| 				continue; | ||||
| 			if(!cb->getPathsInfo(hero)->getPathInfo(tile)->reachable()) //this will remove tiles that are guarded by monsters (or removable objects) | ||||
| 				continue; | ||||
|  | ||||
| 			CGPath path; | ||||
| 			cb->getPathsInfo(hero)->getPath(path, tile); | ||||
| 			float ourValue = (float)howManyTilesWillBeDiscovered(tile, radius, cbp, h) / (path.nodes.size() + 1); //+1 prevents erratic jumps | ||||
|  | ||||
| 			if(ourValue > bestValue) //avoid costly checks of tiles that don't reveal much | ||||
| 			{ | ||||
| 				auto obj = cb->getTopObj(tile); | ||||
| 				if (obj) | ||||
| 					if (obj->blockVisit && !isObjectRemovable(obj)) //we can't stand on that object | ||||
| 						continue; | ||||
| 				if(isSafeToVisit(h, tile)) | ||||
| 				{ | ||||
| 					bestTile = tile; | ||||
| 					bestValue = ourValue; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return bestTile; | ||||
| } | ||||
|  | ||||
| int3 VCAI::explorationDesperate(HeroPtr h) | ||||
| { | ||||
| 	int radius = h->getSightRadius(); | ||||
|  | ||||
| 	std::vector<std::vector<int3>> tiles; //tiles[distance_to_fow] | ||||
| 	tiles.resize(radius); | ||||
|  | ||||
| 	CCallback * cbp = cb.get(); | ||||
|  | ||||
| 	foreach_tile_pos([&](const int3 & pos) | ||||
| 	{ | ||||
| 		if(!cbp->isVisible(pos)) | ||||
| 			tiles[0].push_back(pos); | ||||
| 	}); | ||||
|  | ||||
| 	ui64 lowestDanger = -1; | ||||
| 	int3 bestTile(-1, -1, -1); | ||||
|  | ||||
| 	for(int i = 1; i < radius; i++) | ||||
| 	{ | ||||
| 		getVisibleNeighbours(tiles[i - 1], tiles[i]); | ||||
| 		vstd::removeDuplicates(tiles[i]); | ||||
|  | ||||
| 		for(const int3 & tile : tiles[i]) | ||||
| 		{ | ||||
| 			if(cbp->getTile(tile)->blocked) //does it shorten the time? | ||||
| 				continue; | ||||
| 			if(!howManyTilesWillBeDiscovered(tile, radius, cbp, h)) //avoid costly checks of tiles that don't reveal much | ||||
| 				continue; | ||||
|  | ||||
| 			auto paths = ah->getPathsToTile(h, tile); | ||||
| 			for(auto path : paths) | ||||
| 			{ | ||||
| 				auto t = path.firstTileToGet(); | ||||
|  | ||||
| 				if(t == bestTile) | ||||
| 					continue; | ||||
|  | ||||
| 				auto obj = cb->getTopObj(t); | ||||
| 				if (obj) | ||||
| 					if (obj->blockVisit && !isObjectRemovable(obj)) //we can't stand on object or remove it | ||||
| 						continue; | ||||
|  | ||||
| 				ui64 ourDanger = path.getTotalDanger(h); | ||||
| 				if(ourDanger < lowestDanger) | ||||
| 				{ | ||||
| 					if(!ourDanger) //at least one safe place found | ||||
| 						return t; | ||||
|  | ||||
| 					bestTile = t; | ||||
| 					lowestDanger = ourDanger; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return bestTile; | ||||
| } | ||||
|  | ||||
| void VCAI::checkHeroArmy(HeroPtr h) | ||||
| { | ||||
| 	auto it = lockedHeroes.find(h); | ||||
|   | ||||
| @@ -131,9 +131,6 @@ public: | ||||
| 	void tryRealize(Goals::Invalid & g); | ||||
| 	void tryRealize(Goals::AbstractGoal & g); | ||||
|  | ||||
| 	int3 explorationBestNeighbour(int3 hpos, int radius, HeroPtr h); | ||||
| 	int3 explorationNewPoint(HeroPtr h); | ||||
| 	int3 explorationDesperate(HeroPtr h); | ||||
| 	bool isTileNotReserved(const CGHeroInstance * h, int3 t) const; //the tile is not occupied by allied hero and the object is not reserved | ||||
|  | ||||
| 	std::string getBattleAIName() const override; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user