|
@@ -1,432 +0,0 @@
|
|
|
-/*
|
|
|
-* 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/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;
|
|
|
-extern FuzzyHelper * fh;
|
|
|
-
|
|
|
-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.get();
|
|
|
- aip = ai.get();
|
|
|
- hero = h;
|
|
|
- ts = cbp->getPlayerTeam(ai->playerID);
|
|
|
- sightRadius = hero->getSightRadius();
|
|
|
- bestGoal = sptr(Goals::Invalid());
|
|
|
- bestValue = 0;
|
|
|
- ourPos = h->convertPosition(h->pos, false);
|
|
|
- allowDeadEndCancellation = true;
|
|
|
- allowGatherArmy = gatherArmy;
|
|
|
- }
|
|
|
-
|
|
|
- void scanSector(int scanRadius)
|
|
|
- {
|
|
|
- for(int x = ourPos.x - scanRadius; x <= ourPos.x + scanRadius; x++)
|
|
|
- {
|
|
|
- for(int y = ourPos.y - scanRadius; y <= ourPos.y + scanRadius; y++)
|
|
|
- {
|
|
|
- int3 tile = int3(x, y, ourPos.z);
|
|
|
-
|
|
|
- if(cbp->isInTheMap(tile) && ts->fogOfWarMap[tile.x][tile.y][tile.z])
|
|
|
- {
|
|
|
- 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.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);
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- 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->blockVisit)
|
|
|
- {
|
|
|
- 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.x][neighbour.y][neighbour.z])
|
|
|
- {
|
|
|
- out.push_back(neighbour);
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- int howManyTilesWillBeDiscovered(
|
|
|
- const int3 & pos) const
|
|
|
- {
|
|
|
- int ret = 0;
|
|
|
- for(int x = pos.x - sightRadius; x <= pos.x + sightRadius; x++)
|
|
|
- {
|
|
|
- for(int y = pos.y - sightRadius; y <= pos.y + sightRadius; y++)
|
|
|
- {
|
|
|
- int3 npos = int3(x, y, pos.z);
|
|
|
- if(cbp->isInTheMap(npos)
|
|
|
- && pos.dist2d(npos) - 0.5 < sightRadius
|
|
|
- && !ts->fogOfWarMap[npos.x][npos.y][npos.z])
|
|
|
- {
|
|
|
- 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;
|
|
|
-}
|
|
|
-
|
|
|
-TSubgoal Explore::whatToDoToAchieve()
|
|
|
-{
|
|
|
- return sptr(Goals::Invalid());
|
|
|
-}
|
|
|
-
|
|
|
-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->movement; //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->name);
|
|
|
-
|
|
|
- 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;
|
|
|
-}
|
|
|
-
|
|
|
-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 = .2; //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);
|
|
|
-}
|