123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 |
- /*
- * ExplorationHelper.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 "ExplorationHelper.h"
- #include "../../../lib/mapObjects/CGTownInstance.h"
- #include "../Engine/Nullkiller.h"
- #include "../Goals/Invalid.h"
- #include "../Goals/Composition.h"
- #include "../Goals/ExecuteHeroChain.h"
- #include "../Markers/ExplorationPoint.h"
- #include "../../../lib/CPlayerState.h"
- #include "../Behaviors/CaptureObjectsBehavior.h"
- #include "../Goals/ExploreNeighbourTile.h"
- namespace NKAI
- {
- using namespace Goals;
- ExplorationHelper::ExplorationHelper(const CGHeroInstance * hero, const Nullkiller * ai, bool useCPathfinderAccessibility)
- :ai(ai), cbp(ai->cb.get()), hero(hero), useCPathfinderAccessibility(useCPathfinderAccessibility)
- {
- ts = cbp->getPlayerTeam(ai->playerID);
- sightRadius = hero->getSightRadius();
- bestGoal = sptr(Goals::Invalid());
- bestValue = 0;
- bestTilesDiscovered = 0;
- ourPos = hero->visitablePos();
- allowDeadEndCancellation = true;
- }
- TSubgoal ExplorationHelper::makeComposition() const
- {
- Composition c;
- c.addNext(ExplorationPoint(bestTile, bestTilesDiscovered));
- c.addNextSequence({bestGoal, sptr(ExploreNeighbourTile(hero, 5))});
- return sptr(c);
- }
- bool ExplorationHelper::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);
- }
- }
- }
- return !bestGoal->invalid();
- }
- bool ExplorationHelper::scanMap()
- {
- int3 mapSize = cbp->getMapSize();
- int perimeter = 2 * sightRadius * (mapSize.x + mapSize.y);
- std::vector<int3> edgeTiles;
- edgeTiles.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)
- edgeTiles.push_back(pos);
- }
- });
- logAi->debug("Exploration scan visible area perimeter for hero %s", hero->getNameTranslated());
- for(const int3 & tile : edgeTiles)
- {
- scanTile(tile);
- }
- if(!bestGoal->invalid())
- {
- return true;
- }
- allowDeadEndCancellation = false;
- logAi->debug("Exploration scan all possible tiles for hero %s", hero->getNameTranslated());
- boost::multi_array<ui8, 3> potentialTiles = ts->fogOfWarMap;
- std::vector<int3> tilesToExploreFrom = edgeTiles;
- // WARNING: POTENTIAL BUG
- // AI attempts to move to any tile within sight radius to reveal some new tiles
- // however sight radius is circular, while this method assumes square radius
- // standing on the edge of a square will NOT reveal tile in opposite corner
- for(int i = 0; i < sightRadius; i++)
- {
- std::vector<int3> newTilesToExploreFrom;
- for(const int3 & tile : tilesToExploreFrom)
- {
- foreach_neighbour(cbp, tile, [&](CCallback * cbp, int3 neighbour)
- {
- if(potentialTiles[neighbour.z][neighbour.x][neighbour.y])
- {
- newTilesToExploreFrom.push_back(neighbour);
- potentialTiles[neighbour.z][neighbour.x][neighbour.y] = false;
- }
- });
- }
- for(const int3 & tile : newTilesToExploreFrom)
- {
- scanTile(tile);
- }
- std::swap(tilesToExploreFrom, newTilesToExploreFrom);
- }
- return !bestGoal->invalid();
- }
- void ExplorationHelper::scanTile(const int3 & tile)
- {
- if(tile == ourPos
- || !ai->cb->getTile(tile, false)
- || !ai->pathfinder->isTileAccessible(hero, tile)) //shouldn't happen, but it does
- return;
- int tilesDiscovered = howManyTilesWillBeDiscovered(tile);
- if(!tilesDiscovered)
- return;
-
- auto paths = ai->pathfinder->getPathInfo(tile);
- auto waysToVisit = CaptureObjectsBehavior::getVisitGoals(paths, ai, ai->cb->getTopObj(tile));
- for(int i = 0; i != paths.size(); i++)
- {
- auto & path = paths[i];
- auto goal = waysToVisit[i];
- if(path.exchangeCount > 1 || path.targetHero != hero || path.movementCost() <= 0.0 || goal->invalid())
- continue;
- float ourValue = (float)tilesDiscovered * tilesDiscovered / path.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, path.heroArmy, path.getTotalDanger(), ai->settings->getSafeAttackRatio()))
- {
- bestGoal = goal;
- bestValue = ourValue;
- bestTile = tile;
- bestTilesDiscovered = tilesDiscovered;
- }
- }
- }
- }
- int ExplorationHelper::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 ExplorationHelper::hasReachableNeighbor(const int3 & pos) const
- {
- for(const int3 & dir : int3::getDirs())
- {
- int3 tile = pos + dir;
- if(cbp->isInTheMap(tile))
- {
- auto isAccessible = useCPathfinderAccessibility
- ? ai->getPathsInfo(hero)->getPathInfo(tile)->reachable()
- : ai->pathfinder->isTileAccessible(hero, tile);
- if(isAccessible)
- return true;
- }
- }
- return false;
- }
- }
|