bool VisitHero::fulfillsMe (TSubgoal goal) { if (goal->goalType == Goals::VISIT_TILE && cb->getObj(ObjectInstanceID(objid))->visitablePos() == goal->tile) return true; else return false; }
const CGHeroInstance * HeroPtr::get(bool doWeExpectNull /*= false*/) const { //TODO? check if these all assertions every time we get info about hero affect efficiency // //behave terribly when attempting unauthorized access to hero that is not ours (or was lost) assert(doWeExpectNull || h); if(h) { auto obj = cb->getObj(hid); const bool owned = obj && obj->tempOwner == ai->playerID; if(doWeExpectNull && !owned) { return nullptr; } else { assert(obj); assert(owned); } } return h; }
bool GetObj::fulfillsMe (TSubgoal goal) { if (goal->goalType == Goals::VISIT_TILE) { auto obj = cb->getObj(ObjectInstanceID(objid)); if (obj && obj->visitablePos() == goal->tile) //object could be removed return true; } return false; }
bool VisitHero::fulfillsMe (TSubgoal goal) { if (goal->goalType != Goals::VISIT_TILE) { return false; } auto obj = cb->getObj(ObjectInstanceID(objid)); if (!obj) { logAi->error("Hero %s: VisitHero::fulfillsMe at %s: object %d not found", hero.name, goal->tile, objid); return false; } return obj->visitablePos() == goal->tile; }
void SectorMap::exploreNewSector(crint3 pos, int num, CCallback * cbp) { Sector & s = infoOnSectors[num]; s.id = num; s.water = getTile(pos)->isWater(); std::queue<int3> toVisit; toVisit.push(pos); while (!toVisit.empty()) { int3 curPos = toVisit.front(); toVisit.pop(); TSectorID & sec = retrieveTile(curPos); if (sec == NOT_CHECKED) { const TerrainTile * t = getTile(curPos); if (!markIfBlocked(sec, curPos, t)) { if (t->isWater() == s.water) //sector is only-water or only-land { sec = num; s.tiles.push_back(curPos); foreach_neighbour(cbp, curPos, [&](CCallback * cbp, crint3 neighPos) { if (retrieveTile(neighPos) == NOT_CHECKED) { toVisit.push(neighPos); //parent[neighPos] = curPos; } const TerrainTile * nt = getTile(neighPos); if (nt && nt->isWater() != s.water && canBeEmbarkmentPoint(nt, s.water)) { s.embarkmentPoints.push_back(neighPos); } }); if (t->visitable) { auto obj = t->visitableObjects.front(); if (cb->getObj(obj->id, false)) // FIXME: we have to filter invisible objcts like events, but probably TerrainTile shouldn't be used in SectorMap at all s.visitableObjs.push_back(obj); } } } } } vstd::removeDuplicates(s.embarkmentPoints); }
TSubgoal GetObj::whatToDoToAchieve() { const CGObjectInstance * obj = cb->getObj(ObjectInstanceID(objid)); if(!obj) return sptr (Goals::Explore()); int3 pos = obj->visitablePos(); if (hero) { if (ai->isAccessibleForHero(pos, hero)) return sptr (Goals::VisitTile(pos).sethero(hero)); } else { if (isReachable(obj)) return sptr (Goals::VisitTile(pos).sethero(hero)); //we must visit object with same hero, if any } return sptr (Goals::ClearWayTo(pos).sethero(hero)); }
TSubgoal VisitHero::whatToDoToAchieve() { const CGObjectInstance * obj = cb->getObj(ObjectInstanceID(objid)); if(!obj) return sptr (Goals::Explore()); int3 pos = obj->visitablePos(); if (hero && ai->isAccessibleForHero(pos, hero, true) && isSafeToVisit(hero, pos)) //enemy heroes can get reinforcements { if (hero->pos == pos) logAi->errorStream() << "Hero " << hero.name << " tries to visit himself."; else { //can't use VISIT_TILE here as tile appears blocked by target hero //FIXME: elementar goal should not be abstract return sptr (Goals::VisitHero(objid).sethero(hero).settile(pos).setisElementar(true)); } } return sptr (Goals::Invalid()); }
TSubgoal GetObj::whatToDoToAchieve() { const CGObjectInstance * obj = cb->getObj(ObjectInstanceID(objid)); if(!obj) return sptr (Goals::Explore()); if (obj->tempOwner == ai->playerID) //we can't capture our own object -> move to Win codition throw cannotFulfillGoalException("Cannot capture my own object " + obj->getObjectName()); int3 pos = obj->visitablePos(); if (hero) { if (ai->isAccessibleForHero(pos, hero)) return sptr (Goals::VisitTile(pos).sethero(hero)); } else { for (auto h : cb->getHeroesInfo()) { if (ai->isAccessibleForHero(pos, h)) return sptr(Goals::VisitTile(pos).sethero(h)); //we must visit object with same hero, if any } } return sptr (Goals::ClearWayTo(pos).sethero(hero)); }
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 == Goals::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) { auto sm = ai->getCachedSectorMap(h); for (auto obj : objs) //double loop, performance risk? { auto t = sm->firstTileToGet(h, obj->visitablePos()); //we assume that no more than one tile on the way is guarded if (ai->isTileNotReserved(h, t)) ret.push_back (sptr(Goals::ClearWayTo(obj->visitablePos(), h).setisAbstract(true))); } int3 t = whereToExplore(h); if (t.valid()) { ret.push_back (sptr (Goals::VisitTile(t).sethero(h))); } else { ai->markHeroUnableToExplore (h); //there is no freely accessible tile, do not poll this hero anymore //possible issues when gathering army to break if (hero.h == h || (!hero && h == ai->primaryHero().h)) //check this only ONCE, high cost { t = ai->explorationDesperate(h); if (t.valid()) //don't waste time if we are completely blocked ret.push_back (sptr(Goals::ClearWayTo(t, h).setisAbstract(true))); } } } //we either don't have hero yet or none of heroes can explore if ((!hero || ret.empty()) && ai->canRecruitAnyHero()) ret.push_back (sptr(Goals::RecruitHero())); if (ret.empty()) { throw goalFulfilledException (sptr(Goals::Explore().sethero(hero))); } //throw cannotFulfillGoalException("Cannot explore - no possible ways found!"); return ret; }
TSubgoal Win::whatToDoToAchieve() { auto toBool = [=](const EventCondition &) { // TODO: proper implementation // Right now even already fulfilled goals will be included into generated list // Proper check should test if event condition is already fulfilled // Easiest way to do this is to call CGameState::checkForVictory but this function should not be // used on client side or in AI code return false; }; std::vector<EventCondition> goals; for (const TriggeredEvent & event : cb->getMapHeader()->triggeredEvents) { //TODO: try to eliminate human player(s) using loss conditions that have isHuman element if (event.effect.type == EventEffect::VICTORY) { boost::range::copy(event.trigger.getFulfillmentCandidates(toBool), std::back_inserter(goals)); } } //TODO: instead of returning first encountered goal AI should generate list of possible subgoals for (const EventCondition & goal : goals) { switch(goal.condition) { case EventCondition::HAVE_ARTIFACT: return sptr (Goals::GetArtOfType(goal.objectType)); case EventCondition::DESTROY: { if (goal.object) { auto obj = cb->getObj (goal.object->id); if (obj) if (obj->getOwner() == ai->playerID) //we can't capture our own object return sptr(Goals::Conquer()); return sptr (Goals::GetObj(goal.object->id.getNum())); } else { // TODO: destroy all objects of type goal.objectType // This situation represents "kill all creatures" condition from H3 break; } } case EventCondition::HAVE_BUILDING: { // TODO build other buildings apart from Grail // goal.objectType = buidingID to build // goal.object = optional, town in which building should be built // Represents "Improve town" condition from H3 (but unlike H3 it consists from 2 separate conditions) if (goal.objectType == BuildingID::GRAIL) { if(auto h = ai->getHeroWithGrail()) { //hero is in a town that can host Grail if(h->visitedTown && !vstd::contains(h->visitedTown->forbiddenBuildings, BuildingID::GRAIL)) { const CGTownInstance *t = h->visitedTown; return sptr (Goals::BuildThis(BuildingID::GRAIL, t)); } else { auto towns = cb->getTownsInfo(); towns.erase(boost::remove_if(towns, [](const CGTownInstance *t) -> bool { return vstd::contains(t->forbiddenBuildings, BuildingID::GRAIL); }), towns.end()); boost::sort(towns, CDistanceSorter(h.get())); if(towns.size()) { return sptr (Goals::VisitTile(towns.front()->visitablePos()).sethero(h)); } } } double ratio = 0; // maybe make this check a bit more complex? For example: // 0.75 -> dig randomly within 3 tiles radius // 0.85 -> radius now 2 tiles // 0.95 -> 1 tile radius, position is fully known // AFAIK H3 AI does something like this int3 grailPos = cb->getGrailPos(&ratio); if(ratio > 0.99) { return sptr (Goals::DigAtTile(grailPos)); } //TODO: use FIND_OBJ else if(const CGObjectInstance * obj = ai->getUnvisitedObj(objWithID<Obj::OBELISK>)) //there are unvisited Obelisks return sptr (Goals::GetObj(obj->id.getNum())); else return sptr (Goals::Explore()); } break; } case EventCondition::CONTROL: { if (goal.object) { return sptr (Goals::GetObj(goal.object->id.getNum())); } else { //TODO: control all objects of type "goal.objectType" // Represents H3 condition "Flag all mines" break; } } case EventCondition::HAVE_RESOURCES: //TODO mines? piles? marketplace? //save? return sptr (Goals::CollectRes(static_cast<Res::ERes>(goal.objectType), goal.value)); case EventCondition::HAVE_CREATURES: return sptr (Goals::GatherTroops(goal.objectType, goal.value)); case EventCondition::TRANSPORT: { //TODO. merge with bring Grail to town? So AI will first dig grail, then transport it using this goal and builds it // Represents "transport artifact" condition: // goal.objectType = type of artifact // goal.object = destination-town where artifact should be transported break; } case EventCondition::STANDARD_WIN: return sptr (Goals::Conquer()); // Conditions that likely don't need any implementation case EventCondition::DAYS_PASSED: break; // goal.value = number of days for condition to trigger case EventCondition::DAYS_WITHOUT_TOWN: break; // goal.value = number of days to trigger this case EventCondition::IS_HUMAN: break; // Should be only used in calculation of candidates (see toBool lambda) case EventCondition::CONST_VALUE: break; default: assert(0); } } return sptr (Goals::Invalid()); }
ObjectIdRef::operator const CGObjectInstance*() const { return cb->getObj(id, false); }