diff --git a/AI/VCAI/Goals.cpp b/AI/VCAI/Goals.cpp index 207bd4ff0..02eb38657 100644 --- a/AI/VCAI/Goals.cpp +++ b/AI/VCAI/Goals.cpp @@ -486,6 +486,10 @@ TGoalVec Explore::getAllPossibleSubgoals() heroes = cb->getHeroesInfo(); erase_if (heroes, [](const HeroPtr h) { + if (vstd::contains(ai->lockedHeroes, h)) + if (ai->lockedHeroes[h]->goalType == Goals::EXPLORE) //do not reassign hero who is already explorer + return true; + return !h->movement; //saves time, immobile heroes are useless anyway }); } @@ -514,7 +518,6 @@ TGoalVec Explore::getAllPossibleSubgoals() auto t = sm.firstTileToGet(h, obj->visitablePos()); //we assume that no more than one tile on the way is guarded if (t.valid()) { - assert(cb->isInTheMap(t)); if (isSafeToVisit(h, t)) { ret.push_back (sptr (Goals::VisitTile(t).sethero(h))); @@ -530,14 +533,49 @@ TGoalVec Explore::getAllPossibleSubgoals() int3 t = whereToExplore(h); if (t.valid()) { - assert(cb->isInTheMap(t)); ret.push_back (sptr (Goals::VisitTile(t).sethero(h))); } + else if (hero.h == h || (!hero && h == ai->primaryHero().h)) //check this only ONCE, high cost + { + t = ai->explorationDesperate(h->getSightRadious(), h); + if (t.valid()) + { + if (isSafeToVisit(h, t)) + { + ret.push_back (sptr (Goals::VisitTile(t).sethero(h))); + } + else + { + ret.push_back (sptr (Goals::GatherArmy(evaluateDanger(t, h)*SAFE_ATTACK_CONSTANT). + sethero(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()) + //{ + // for (auto h : heroes) //this is costly function, use only when there is no other way + // { + // auto t = ai->explorationDesperate (h->getSightRadious(), h); //we assume that no more than one tile on the way is guarded + // if (t.valid()) + // { + // if (isSafeToVisit(h, t)) + // { + // ret.push_back (sptr (Goals::VisitTile(t).sethero(h))); + // } + // else + // { + // ret.push_back (sptr (Goals::GatherArmy(evaluateDanger(t, h)*SAFE_ATTACK_CONSTANT). + // sethero(h).setisAbstract(true))); + // } + // } + // } + //} + if (ret.empty()) { throw goalFulfilledException (sptr(Goals::Explore().sethero(hero))); @@ -784,11 +822,9 @@ TGoalVec Conquer::getAllPossibleSubgoals() { TGoalVec ret; - std::vector objs; - for (auto obj : ai->visitableObjs) + auto conquerable = [](const CGObjectInstance * obj) -> bool { - if (!vstd::contains (ai->reservedObjs, obj) && //no need to capture same object twice - cb->getPlayerRelations(ai->playerID, obj->tempOwner) == PlayerRelations::ENEMIES) //only enemy objects are interesting + if (cb->getPlayerRelations(ai->playerID, obj->tempOwner) == PlayerRelations::ENEMIES) { switch (obj->ID.num) { @@ -796,15 +832,30 @@ TGoalVec Conquer::getAllPossibleSubgoals() case Obj::HERO: case Obj::CREATURE_GENERATOR1: case Obj::MINE: //TODO: check ai->knownSubterraneanGates - objs.push_back (obj); + return true; } } + return false; + }; + + std::vector objs; + for (auto obj : ai->visitableObjs) + { + if (conquerable(obj)) + objs.push_back (obj); } for (auto h : cb->getHeroesInfo()) { SectorMap sm(h); - for (auto obj : objs) //double loop, performance risk? + std::vector ourObjs(objs); //copy common objects + + for (auto obj : ai->reservedHeroesMap[h]) //add objects reserved by this hero + { + if (conquerable(obj)) + ourObjs.push_back(obj); + } + for (auto obj : ourObjs) //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 (t.valid()) @@ -881,11 +932,11 @@ TGoalVec GatherArmy::getAllPossibleSubgoals() auto heroDummy = hero; erase_if(otherHeroes, [heroDummy](const CGHeroInstance * h) { - return (h == heroDummy.h || !ai->isAccessibleForHero(heroDummy->visitablePos(), h, true) || !ai->canGetArmy(heroDummy.h, h)); + return (h == heroDummy.h || !ai->isAccessibleForHero(heroDummy->visitablePos(), h, true) + || !ai->canGetArmy(heroDummy.h, h) || ai->getGoal(h)->goalType == Goals::GATHER_ARMY); }); for (auto h : otherHeroes) { - ret.push_back (sptr (Goals::VisitHero(h->id.getNum()).setisAbstract(true).sethero(hero))); //go to the other hero if we are faster ret.push_back (sptr (Goals::VisitHero(hero->id.getNum()).setisAbstract(true).sethero(h))); @@ -928,7 +979,12 @@ TGoalVec GatherArmy::getAllPossibleSubgoals() } } if (ret.empty()) - ret.push_back (sptr(Goals::Explore())); + { + if (hero == ai->primaryHero() || value >= 1.1f) + ret.push_back (sptr(Goals::Explore())); + else //workaround to break loop - seemingly there are no ways to explore left + throw goalFulfilledException (sptr(Goals::GatherArmy(0).sethero(hero))); + } return ret; } diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index caeebb5e3..e2fdef058 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -307,14 +307,30 @@ void VCAI::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, Q requestActionASAP([=]() { - if (firstHero->getFightingStrength() > secondHero->getFightingStrength() && canGetArmy (firstHero, secondHero)) - pickBestCreatures (firstHero, secondHero); - else if (canGetArmy (secondHero, firstHero)) - pickBestCreatures (secondHero, firstHero); + float goalpriority1 = 0, goalpriority2 = 0; - completeGoal(sptr(Goals::VisitHero(firstHero->id.getNum()))); //TODO: what if we were visited by other hero in the meantime? - completeGoal(sptr(Goals::VisitHero(secondHero->id.getNum()))); + auto firstGoal = getGoal(firstHero); + if (firstGoal->goalType == Goals::GATHER_ARMY) + goalpriority1 = firstGoal->priority; + auto secondGoal = getGoal(secondHero); + if (secondGoal->goalType == Goals::GATHER_ARMY) + goalpriority2 = secondGoal->priority; + + if (goalpriority1 > goalpriority2) + pickBestCreatures (firstHero, secondHero); + else if (goalpriority1 < goalpriority2) + pickBestCreatures (secondHero, firstHero); + else //regular criteria + { + if (firstHero->getFightingStrength() > secondHero->getFightingStrength() && canGetArmy (firstHero, secondHero)) + pickBestCreatures (firstHero, secondHero); + else if (canGetArmy (secondHero, firstHero)) + pickBestCreatures (secondHero, firstHero); + + completeGoal(sptr(Goals::VisitHero(firstHero->id.getNum()))); //TODO: what if we were visited by other hero in the meantime? + completeGoal(sptr(Goals::VisitHero(secondHero->id.getNum()))); //TODO: exchange artifacts + } answerQuery(query, 0); }); @@ -866,7 +882,7 @@ bool VCAI::canGetArmy (const CGHeroInstance * army, const CGHeroInstance * sourc for(auto armyPtr : armies) for (int j = 0; j < GameConstants::ARMY_SIZE; j++) { - if(armyPtr->getCreature(SlotID(j)) == bestArmy[i] && (i != j || armyPtr != army)) //it's a searched creature not in dst slot + if(armyPtr->getCreature(SlotID(j)) == bestArmy[i] && armyPtr != army) //it's a searched creature not in dst ARMY if (!(armyPtr->needsLastStack() && armyPtr->Slots().size() == 1 && armyPtr != army)) //can't take away last creature return true; //at least one exchange will be performed } @@ -910,7 +926,7 @@ void VCAI::pickBestCreatures(const CArmedInstance * army, const CArmedInstance * for(auto armyPtr : armies) for (int j = 0; j < GameConstants::ARMY_SIZE; j++) { - if(armyPtr->getCreature(SlotID(j)) == bestArmy[i] && (i != j || armyPtr != army)) //it's a searched creature not in dst slot + if(armyPtr->getCreature(SlotID(j)) == bestArmy[i] && (i != j || armyPtr != army)) //it's a searched creature not in dst SLOT if (!(armyPtr->needsLastStack() && armyPtr->Slots().size() == 1 && armyPtr != army)) cb->mergeOrSwapStacks(armyPtr, army, SlotID(j), SlotID(i)); } @@ -1833,6 +1849,16 @@ const CGTownInstance * VCAI::findTownWithTavern() const return nullptr; } +Goals::TSubgoal VCAI::getGoal (HeroPtr h) const +{ + auto it = lockedHeroes.find(h); + if (it != lockedHeroes.end()) + return it->second; + else + return sptr(Goals::Invalid()); +} + + std::vector VCAI::getUnblockedHeroes() const { std::vector ret; @@ -2179,7 +2205,7 @@ int3 VCAI::explorationBestNeighbour(int3 hpos, int radius, HeroPtr h) throw cannotFulfillGoalException("No neighbour will bring new discoveries!"); } -int3 VCAI::explorationNewPoint(int radius, HeroPtr h, bool breakUnsafe) +int3 VCAI::explorationNewPoint(int radius, HeroPtr h) { //logAi->debugStream() << "Looking for an another place for exploration..."; cb->setSelection(h.h); @@ -2205,7 +2231,7 @@ int3 VCAI::explorationNewPoint(int radius, HeroPtr h, bool breakUnsafe) { if (cb->getTile(tile)->blocked) //does it shorten the time? continue; - if (!cb->getPathInfo(tile)->reachable()) + if (!cb->getPathInfo(tile)->reachable()) //this will remove tiles that are guarded by monsters (or removable objects) continue; CGPath path; @@ -2214,14 +2240,101 @@ int3 VCAI::explorationNewPoint(int radius, HeroPtr h, bool breakUnsafe) if (ourValue > bestValue) //avoid costly checks of tiles that don't reveal much { - if((isSafeToVisit(h, tile) || breakUnsafe) && !isBlockedBorderGate(tile)) + if(isSafeToVisit(h, tile) && !isBlockedBorderGate(tile)) { - bestTile = tile; //return first tile that will discover anything + bestTile = tile; bestValue = ourValue; } } } } + //if (!bestValue) //no free spot, we need to fight + //{ + // SectorMap sm(h); + + // ui64 lowestDanger = -1; + + // for (int i = 1; i < radius; i++) + // { + // getVisibleNeighbours(tiles[i-1], tiles[i]); + // removeDuplicates(tiles[i]); + + // for(const int3 &tile : tiles[i]) + // { + // if (cb->getTile(tile)->blocked) //does it shorten the time? + // continue; + // if (!howManyTilesWillBeDiscovered(tile, radius)) //avoid costly checks of tiles that don't reveal much + // continue; + + // auto t = sm.firstTileToGet(h, tile); + // if (t.valid()) + // { + // ui64 ourDanger = evaluateDanger(tile, h.h); + // if (ourDanger < lowestDanger) + // { + // if(!isBlockedBorderGate(tile)) + // { + // if (!ourDanger) //at least one safe place found + // return tile; + + // bestTile = tile; + // lowestDanger = ourDanger; + // } + // } + // } + // } + // } + //} + return bestTile; +} + +int3 VCAI::explorationDesperate(int radius, HeroPtr h) +{ + //logAi->debugStream() << "Looking for an another place for exploration..."; + SectorMap sm(h); + + std::vector > tiles; //tiles[distance_to_fow] + tiles.resize(radius); + + foreach_tile_pos([&](const int3 &pos) + { + if(!cb->isVisible(pos)) + tiles[0].push_back(pos); + }); + + ui64 lowestDanger = -1; + int3 bestTile(-1,-1,-1); + + for (int i = 1; i < radius; i++) + { + getVisibleNeighbours(tiles[i-1], tiles[i]); + removeDuplicates(tiles[i]); + + for(const int3 &tile : tiles[i]) + { + if (cb->getTile(tile)->blocked) //does it shorten the time? + continue; + if (!howManyTilesWillBeDiscovered(tile, radius)) //avoid costly checks of tiles that don't reveal much + continue; + + auto t = sm.firstTileToGet(h, tile); + if (t.valid()) + { + ui64 ourDanger = evaluateDanger(t, h.h); + if (ourDanger < lowestDanger) + { + if(!isBlockedBorderGate(t)) + { + if (!ourDanger) //at least one safe place found + return t; + + bestTile = t; + lowestDanger = ourDanger; + } + } + } + } + } return bestTile; } diff --git a/AI/VCAI/VCAI.h b/AI/VCAI/VCAI.h index 48ff41b49..b15a69b0f 100644 --- a/AI/VCAI/VCAI.h +++ b/AI/VCAI/VCAI.h @@ -178,7 +178,8 @@ public: void tryRealize(Goals::AbstractGoal & g); int3 explorationBestNeighbour(int3 hpos, int radius, HeroPtr h); - int3 explorationNewPoint(int radius, HeroPtr h, bool breakUnsafe = false); + int3 explorationNewPoint(int radius, HeroPtr h); + int3 explorationDesperate(int radius, HeroPtr h); void recruitHero(); virtual std::string getBattleAIName() const override; @@ -296,6 +297,7 @@ public: const CGTownInstance *findTownWithTavern() const; bool canRecruitAnyHero(const CGTownInstance * t = NULL) const; + Goals::TSubgoal getGoal (HeroPtr h) const; bool canAct(HeroPtr h) const; std::vector getUnblockedHeroes() const; HeroPtr primaryHero() const; diff --git a/lib/HeroBonus.h b/lib/HeroBonus.h index 352cb607a..620313dbc 100644 --- a/lib/HeroBonus.h +++ b/lib/HeroBonus.h @@ -964,14 +964,14 @@ namespace Selector extern DLL_LINKAGE const std::map bonusNameMap; extern DLL_LINKAGE const std::map bonusValueMap; -extern DLL_LINKAGE const std::map bonusSourceMap; -extern DLL_LINKAGE const std::map bonusDurationMap; -extern DLL_LINKAGE const std::map bonusLimitEffect; -extern DLL_LINKAGE const std::map bonusLimiterMap; -extern DLL_LINKAGE const std::map bonusPropagatorMap; - - -// BonusList template that requires full interface of CBonusSystemNode +extern DLL_LINKAGE const std::map bonusSourceMap; +extern DLL_LINKAGE const std::map bonusDurationMap; +extern DLL_LINKAGE const std::map bonusLimitEffect; +extern DLL_LINKAGE const std::map bonusLimiterMap; +extern DLL_LINKAGE const std::map bonusPropagatorMap; + + +// BonusList template that requires full interface of CBonusSystemNode template void BonusList::insert(const int position, InputIterator first, InputIterator last) {