From 60c5f584a81e6f2efa10ebec627ca1b8b0416aad Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sun, 11 Nov 2018 23:02:05 +0200 Subject: [PATCH] AI: GATHER_TROOPS improvements and fixes --- AI/VCAI/Goals.cpp | 98 ++++++++++++---------- AI/VCAI/Goals.h | 8 +- AI/VCAI/Pathfinding/PathfindingManager.cpp | 4 + AI/VCAI/VCAI.cpp | 34 ++++++-- 4 files changed, 89 insertions(+), 55 deletions(-) diff --git a/AI/VCAI/Goals.cpp b/AI/VCAI/Goals.cpp index 5192de437..b8f996b52 100644 --- a/AI/VCAI/Goals.cpp +++ b/AI/VCAI/Goals.cpp @@ -137,7 +137,7 @@ bool Goals::GatherArmy::operator==(const GatherArmy & other) const bool Goals::BuyArmy::operator==(const BuyArmy & other) const { - return town == other.town; + return town == other.town && objid == other.objid; } bool Goals::BoostHero::operator==(const BoostHero & other) const @@ -1219,11 +1219,58 @@ bool CollectRes::fulfillsMe(TSubgoal goal) return false; } +int GatherTroops::getCreaturesCount(const CArmedInstance * army) +{ + int count = 0; + + for(auto stack : army->Slots()) + { + if(objid == stack.second->getCreatureID().num) + { + count += stack.second->count; + } + } + + return count; +} + TSubgoal GatherTroops::whatToDoToAchieve() { - std::vector dwellings; + auto heroes = cb->getHeroesInfo(true); + + for(auto hero : heroes) + { + if(getCreaturesCount(hero) >= this->value) + { + logAi->trace("Completing GATHER_TROOPS by hero %s", hero->name); + + throw goalFulfilledException(sptr(*this)); + } + } + + TGoalVec solutions = getAllPossibleSubgoals(); + + if(solutions.empty()) + return sptr(Goals::Explore()); + + return fh->chooseSolution(solutions); +} + + +TGoalVec GatherTroops::getAllPossibleSubgoals() +{ + TGoalVec solutions; + for(const CGTownInstance * t : cb->getTownsInfo()) { + int count = getCreaturesCount(t->getUpperArmy()); + + if(count >= this->value) + { + vstd::concatenate(solutions, ai->ah->howToVisitObj(t)); + continue; + } + auto creature = VLC->creh->creatures[objid]; if(t->subID == creature->faction) //TODO: how to force AI to build unupgraded creatures? :O { @@ -1238,7 +1285,7 @@ TSubgoal GatherTroops::whatToDoToAchieve() BuildingID bid(BuildingID::DWELL_FIRST + creature->level - 1 + upgradeNumber * GameConstants::CREATURES_PER_TOWN); if(t->hasBuilt(bid)) //this assumes only creatures with dwellings are assigned to faction { - dwellings.push_back(t); + solutions.push_back(sptr(Goals::BuyArmy(t, creature->AIValue * this->value).setobjid(objid))); } /*else //disable random building requests for now - this code needs to know a lot of town/resource context to do more good than harm { @@ -1248,10 +1295,11 @@ TSubgoal GatherTroops::whatToDoToAchieve() } for(auto obj : ai->visitableObjs) { - if(obj->ID != Obj::CREATURE_GENERATOR1) //TODO: what with other creature generators? + auto d = dynamic_cast(obj); + + if(!d || obj->ID == Obj::TOWN) continue; - auto d = dynamic_cast(obj); for(auto creature : d->creatures) { if(creature.first) //there are more than 0 creatures avaliabe @@ -1259,49 +1307,13 @@ TSubgoal GatherTroops::whatToDoToAchieve() for(auto type : creature.second) { if(type == objid && ai->ah->freeResources().canAfford(VLC->creh->creatures[type]->cost)) - dwellings.push_back(d); + vstd::concatenate(solutions, ai->ah->howToVisitObj(obj)); } } } } - if(dwellings.size()) - { - typedef std::map TDwellMap; - // sorted helper - auto comparator = [](const TDwellMap::value_type & a, const TDwellMap::value_type & b) -> bool - { - const CGPathNode * ln = ai->myCb->getPathsInfo(a.first)->getPathInfo(a.second->visitablePos()); - const CGPathNode * rn = ai->myCb->getPathsInfo(b.first)->getPathInfo(b.second->visitablePos()); - - if(ln->turns != rn->turns) - return ln->turns < rn->turns; - - return (ln->moveRemains > rn->moveRemains); - }; - - // for all owned heroes generate map nearest dwelling> - TDwellMap nearestDwellings; - for(const CGHeroInstance * hero : cb->getHeroesInfo(true)) - { - nearestDwellings[hero] = *boost::range::min_element(dwellings, CDistanceSorter(hero)); - } - if(nearestDwellings.size()) - { - // find hero who is nearest to a dwelling - const CGDwelling * nearest = boost::range::min_element(nearestDwellings, comparator)->second; - if(!nearest) - throw cannotFulfillGoalException("Cannot find nearest dwelling!"); - - return sptr(Goals::VisitObj(nearest->id.getNum())); - } - else - return sptr(Goals::Explore()); - } - else - { - return sptr(Goals::Explore()); - } + return solutions; //TODO: exchange troops between heroes } diff --git a/AI/VCAI/Goals.h b/AI/VCAI/Goals.h index 3adb933e2..2277e7066 100644 --- a/AI/VCAI/Goals.h +++ b/AI/VCAI/Goals.h @@ -535,13 +535,13 @@ public: value = val; priority = 2; } - TGoalVec getAllPossibleSubgoals() override - { - return TGoalVec(); - } + TGoalVec getAllPossibleSubgoals() override; TSubgoal whatToDoToAchieve() override; bool fulfillsMe(TSubgoal goal) override; virtual bool operator==(const GatherTroops & other) const override; + +private: + int getCreaturesCount(const CArmedInstance * army); }; class DLL_EXPORT VisitObj : public CGoal //this goal was previously known as GetObj diff --git a/AI/VCAI/Pathfinding/PathfindingManager.cpp b/AI/VCAI/Pathfinding/PathfindingManager.cpp index 72de89f29..825112b39 100644 --- a/AI/VCAI/Pathfinding/PathfindingManager.cpp +++ b/AI/VCAI/Pathfinding/PathfindingManager.cpp @@ -144,6 +144,9 @@ Goals::TGoalVec PathfindingManager::findPath( : clearWayTo(hero, firstTileToGet); } + if(solution->invalid()) + continue; + if(solution->evaluationContext.danger < danger) solution->evaluationContext.danger = danger; @@ -212,6 +215,7 @@ Goals::TSubgoal PathfindingManager::clearWayTo(HeroPtr hero, int3 firstTileToGet //ret.push_back(ai->questToGoal()); //however, visiting obj for firts time will give us quest //do not access quets guard if we can't complete the quest + logAi->trace("Can not visit this quest guard! Not ready!"); return sptr(Goals::Invalid()); } } diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index da1893d2f..54a6f6c0f 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -2193,16 +2193,18 @@ void VCAI::tryRealize(Goals::BuyArmy & g) { auto res = ah->allResources(); std::vector creaturesInDwellings; + for (int i = 0; i < t->creatures.size(); i++) { auto ci = infoFromDC(t->creatures[i]); + + if(!ci.count || ci.creID == -1 || (g.objid != -1 && ci.creID != g.objid)) + continue; + ci.level = i; //this is important for Dungeon Summoning Portal creaturesInDwellings.push_back(ci); } - vstd::erase_if(creaturesInDwellings, [](const creInfo & ci) -> bool - { - return !ci.count || ci.creID == -1; - }); + if (creaturesInDwellings.empty()) throw cannotFulfillGoalException("Can't buy any more creatures!"); @@ -2433,6 +2435,8 @@ Goals::TSubgoal VCAI::decomposeGoal(Goals::TSubgoal ultimateGoal) Goals::TSubgoal VCAI::questToGoal(const QuestInfo & q) { + Goals::TSubgoal result = sptr(Goals::Invalid()); + if (q.quest->missionType && q.quest->progress != CQuest::COMPLETE) { MetaString ms; @@ -2476,12 +2480,18 @@ Goals::TSubgoal VCAI::questToGoal(const QuestInfo & q) { if (q.quest->checkQuest(hero)) //very bad info - stacks can be split between multiple heroes :( { - return sptr(Goals::VisitObj(q.obj->id.getNum()).sethero(hero)); + result = sptr(Goals::VisitObj(q.obj->id.getNum()).sethero(hero)); + break; } } - for (auto creature : q.quest->m6creatures) + + if(result->invalid()) { - return sptr(Goals::GatherTroops(creature.type->idNumber, creature.count)); + for(auto creature : q.quest->m6creatures) + { + result = sptr(Goals::GatherTroops(creature.type->idNumber, creature.count)); + break; + } } //TODO: exchange armies... oh my //BNLOG ("Don't know how to recruit %d of %s\n", (int)(creature.count) % creature.type->namePl); @@ -2576,7 +2586,15 @@ Goals::TSubgoal VCAI::questToGoal(const QuestInfo & q) } } //end of switch } - return sptr(Goals::Invalid()); + + logAi->trace( + "Returning %s, tile: %s, objid: %d, hero: %s", + result->name(), + result->tile.toString(), + result->objid, + result->hero.validAndSet() ? result->hero->name : "not specified"); + + return result; } void VCAI::performTypicalActions()