int3 whereToExplore(HeroPtr h) { //TODO it's stupid and ineffective, write sth better cb->setSelection(*h); int radius = h->getSightRadious(); int3 hpos = h->visitablePos(); //look for nearby objs -> visit them if they're close enouh const int DIST_LIMIT = 3; std::vector<const CGObjectInstance *> nearbyVisitableObjs; for(const CGObjectInstance *obj : ai->getPossibleDestinations(h)) { int3 op = obj->visitablePos(); CGPath p; cb->getPath2(op, p); if(p.nodes.size() && p.endPos() == op && p.nodes.size() <= DIST_LIMIT) nearbyVisitableObjs.push_back(obj); } boost::sort(nearbyVisitableObjs, isCloser); if(nearbyVisitableObjs.size()) return nearbyVisitableObjs.back()->visitablePos(); try { return ai->explorationBestNeighbour(hpos, radius, h); } catch(cannotFulfillGoalException &e) { std::vector<std::vector<int3> > tiles; //tiles[distance_to_fow] try { return ai->explorationNewPoint(radius, h, tiles); } catch(cannotFulfillGoalException &e) { std::map<int, std::vector<int3> > profits; { TimeCheck tc("Evaluating exploration possibilities"); tiles[0].clear(); //we can't reach FoW anyway for(auto &vt : tiles) for(auto &tile : vt) profits[howManyTilesWillBeDiscovered(tile, radius)].push_back(tile); } if(profits.empty()) return int3 (-1,-1,-1); auto bestDest = profits.end(); bestDest--; return bestDest->second.front(); //TODO which is the real best tile? } } }
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; }
int3 whereToExplore(HeroPtr h) { TimeCheck tc ("where to explore"); int radius = h->getSightRadious(); int3 hpos = h->visitablePos(); SectorMap sm(h); //look for nearby objs -> visit them if they're close enouh const int DIST_LIMIT = 3; 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)) { int3 op = obj->visitablePos(); CGPath p; ai->myCb->getPathsInfo(h.get())->getPath(op, p); if (p.nodes.size() && p.endPos() == op && p.nodes.size() <= DIST_LIMIT) if (ai->isGoodForVisit(obj, h, sm)) 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); } }
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; }
SectorMap::SectorMap(HeroPtr h) { update(); makeParentBFS(h->visitablePos()); }
/* 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); } }