diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index 598be14ad..49e0f81d0 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -1058,27 +1058,6 @@ void AIGateway::recruitCreatures(const CGDwelling * d, const CArmedInstance * re } } -bool AIGateway::canRecruitAnyHero(const CGTownInstance * t) const -{ - //TODO: make gathering gold, building tavern or conquering town (?) possible subgoals - if(!t) - t = findTownWithTavern(); - - if(!t || !townHasFreeTavern(t)) - return false; - - if(cb->getResourceAmount(EGameResID::GOLD) < GameConstants::HERO_GOLD_COST) //TODO: use ResourceManager - return false; - if(cb->getHeroesInfo().size() >= ALLOWED_ROAMING_HEROES) - return false; - if(cb->getHeroesInfo().size() >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP)) - return false; - if(!cb->getAvailableHeroes(t).size()) - return false; - - return true; -} - void AIGateway::battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side) { NET_EVENT_HANDLER; @@ -1160,16 +1139,6 @@ void AIGateway::addVisitableObj(const CGObjectInstance * obj) } } -HeroPtr AIGateway::getHeroWithGrail() const -{ - for(const CGHeroInstance * h : cb->getHeroesInfo()) - { - if(h->hasArt(ArtifactID::GRAIL)) - return h; - } - return nullptr; -} - bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h) { if(h->inTownGarrison && h->visitedTown) @@ -1437,15 +1406,6 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade } } -const CGTownInstance * AIGateway::findTownWithTavern() const -{ - for(const CGTownInstance * t : cb->getTownsInfo()) - if(townHasFreeTavern(t)) - return t; - - return nullptr; -} - void AIGateway::endTurn() { logAi->info("Player %d (%s) ends turn", playerID, playerID.getStr()); diff --git a/AI/Nullkiller/AIGateway.h b/AI/Nullkiller/AIGateway.h index a3115e3e6..d42afd31f 100644 --- a/AI/Nullkiller/AIGateway.h +++ b/AI/Nullkiller/AIGateway.h @@ -198,11 +198,6 @@ public: void retrieveVisitableObjs(); virtual std::vector getFlaggedObjects() const; - HeroPtr getHeroWithGrail() const; - - const CGTownInstance * findTownWithTavern() const; - bool canRecruitAnyHero(const CGTownInstance * t = NULL) const; - void requestSent(const CPackForServer * pack, int requestID) override; void answerQuery(QueryID queryID, int selection); //special function that can be called ONLY from game events handling thread and will send request ASAP diff --git a/AI/Nullkiller/Analyzers/HeroManager.cpp b/AI/Nullkiller/Analyzers/HeroManager.cpp index 572a88694..116411633 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.cpp +++ b/AI/Nullkiller/Analyzers/HeroManager.cpp @@ -12,6 +12,8 @@ #include "../Engine/Nullkiller.h" #include "../../../lib/mapObjects/MapObjects.h" #include "../../../lib/CHeroHandler.h" +#include "../../../lib/GameSettings.h" +#include "../../../lib/CGameState.h" namespace NKAI { @@ -179,6 +181,51 @@ float HeroManager::evaluateHero(const CGHeroInstance * hero) const return evaluateFightingStrength(hero); } +bool HeroManager::canRecruitHero(const CGTownInstance * town) const +{ + if(!town) + town = findTownWithTavern(); + + if(!town || !townHasFreeTavern(town)) + return false; + + if(cb->getResourceAmount(EGameResID::GOLD) < GameConstants::HERO_GOLD_COST) + return false; + + const bool includeGarnisoned = true; + int heroCount = cb->getHeroCount(ai->playerID, includeGarnisoned); + + if(heroCount >= ALLOWED_ROAMING_HEROES) + return false; + + if(heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP)) + return false; + + if(!cb->getAvailableHeroes(town).size()) + return false; + + return true; +} + +const CGTownInstance * HeroManager::findTownWithTavern() const +{ + for(const CGTownInstance * t : cb->getTownsInfo()) + if(townHasFreeTavern(t)) + return t; + + return nullptr; +} + +const CGHeroInstance * HeroManager::findHeroWithGrail() const +{ + for(const CGHeroInstance * h : cb->getHeroesInfo()) + { + if(h->hasArt(ArtifactID::GRAIL)) + return h; + } + return nullptr; +} + SecondarySkillScoreMap::SecondarySkillScoreMap(std::map scoreMap) :scoreMap(scoreMap) { diff --git a/AI/Nullkiller/Analyzers/HeroManager.h b/AI/Nullkiller/Analyzers/HeroManager.h index 459604bef..9c98443f3 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.h +++ b/AI/Nullkiller/Analyzers/HeroManager.h @@ -30,6 +30,8 @@ public: virtual void update() = 0; virtual float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const = 0; virtual float evaluateHero(const CGHeroInstance * hero) const = 0; + virtual bool canRecruitHero(const CGTownInstance * t = nullptr) const = 0; + virtual const CGHeroInstance * findHeroWithGrail() const = 0; }; class DLL_EXPORT ISecondarySkillRule @@ -57,20 +59,24 @@ private: static SecondarySkillEvaluator scountSkillsScores; CCallback * cb; //this is enough, but we downcast from CCallback + const Nullkiller * ai; std::map heroRoles; public: - HeroManager(CCallback * CB, const Nullkiller * ai) : cb(CB) {} + HeroManager(CCallback * CB, const Nullkiller * ai) : cb(CB), ai(ai) {} const std::map & getHeroRoles() const override; HeroRole getHeroRole(const HeroPtr & hero) const override; int selectBestSkill(const HeroPtr & hero, const std::vector & skills) const override; void update() override; float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const override; float evaluateHero(const CGHeroInstance * hero) const override; + bool canRecruitHero(const CGTownInstance * t = nullptr) const override; + const CGHeroInstance * findHeroWithGrail() const override; private: float evaluateFightingStrength(const CGHeroInstance * hero) const; float evaluateSpeciality(const CGHeroInstance * hero) const; + const CGTownInstance * findTownWithTavern() const; }; // basic skill scores. missing skills will have score of 0 diff --git a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp index 8717900b6..e63e26c3a 100644 --- a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp +++ b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp @@ -209,7 +209,7 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const { captureObjects(ai->nullkiller->objectClusterizer->getNearbyObjects()); - if(tasks.empty()) + if(tasks.empty() || ai->nullkiller->getScanDepth() == ScanDepth::FULL) captureObjects(ai->nullkiller->objectClusterizer->getFarObjects()); } diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index 3db20a1aa..da5e5f34e 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -58,13 +58,6 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta auto treatNode = ai->nullkiller->dangerHitMap->getObjectTreat(town); auto treats = { treatNode.maximumDanger, treatNode.fastestDanger }; - if(!treatNode.fastestDanger.hero) - { - logAi->trace("No treat found for town %s", town->getNameTranslated()); - - return; - } - int dayOfWeek = cb->getDate(Date::DAY_OF_WEEK); if(town->garrisonHero) @@ -91,6 +84,13 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta return; } + + if(!treatNode.fastestDanger.hero) + { + logAi->trace("No treat found for town %s", town->getNameTranslated()); + + return; + } uint64_t reinforcement = ai->nullkiller->armyManager->howManyReinforcementsCanBuy(town->getUpperArmy(), town); diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index ab37ce028..950ce9cd0 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -53,7 +53,7 @@ Goals::TGoalVec RecruitHeroBehavior::decompose() const for(auto town : towns) { - if(ai->canRecruitAnyHero(town)) + if(ai->nullkiller->heroManager->canRecruitHero(town)) { auto availableHeroes = cb->getAvailableHeroes(town); diff --git a/AI/Nullkiller/Behaviors/StartupBehavior.cpp b/AI/Nullkiller/Behaviors/StartupBehavior.cpp index 91ef62344..448b2386e 100644 --- a/AI/Nullkiller/Behaviors/StartupBehavior.cpp +++ b/AI/Nullkiller/Behaviors/StartupBehavior.cpp @@ -66,7 +66,7 @@ const CGHeroInstance * getNearestHero(const CGTownInstance * town) bool needToRecruitHero(const CGTownInstance * startupTown) { - if(!ai->canRecruitAnyHero(startupTown)) + if(!ai->nullkiller->heroManager->canRecruitHero(startupTown)) return false; if(!startupTown->garrisonHero && !startupTown->visitingHero) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index b4e85fc97..c5aa3324f 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -117,7 +117,7 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior, int decompositi void Nullkiller::resetAiState() { lockedResources = TResources(); - scanDepth = ScanDepth::SMALL; + scanDepth = ScanDepth::FULL; playerID = ai->playerID; lockedHeroes.clear(); dangerHitMap->reset(); diff --git a/AI/Nullkiller/Engine/Nullkiller.h b/AI/Nullkiller/Engine/Nullkiller.h index 3a5b7bdc1..1b5e513e6 100644 --- a/AI/Nullkiller/Engine/Nullkiller.h +++ b/AI/Nullkiller/Engine/Nullkiller.h @@ -88,6 +88,7 @@ public: int32_t getFreeGold() const { return getFreeResources()[EGameResID::GOLD]; } void lockResources(const TResources & res); const TResources & getLockedResources() const { return lockedResources; } + ScanDepth getScanDepth() const { return scanDepth; } private: void resetAiState(); diff --git a/AI/Nullkiller/Goals/RecruitHero.cpp b/AI/Nullkiller/Goals/RecruitHero.cpp index d6cfe2908..ab596f801 100644 --- a/AI/Nullkiller/Goals/RecruitHero.cpp +++ b/AI/Nullkiller/Goals/RecruitHero.cpp @@ -33,8 +33,6 @@ void RecruitHero::accept(AIGateway * ai) { auto t = town; - if(!t) t = ai->findTownWithTavern(); - if(!t) { throw cannotFulfillGoalException("No town to recruit hero!"); diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 0f80c0949..1f03dbb0b 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -1093,7 +1093,7 @@ void AINodeStorage::calculateTownPortal( if(nodeOptional) { #if NKAI_PATHFINDER_TRACE_LEVEL >= 1 - logAi->trace("Adding town portal node at %s", targetTown->name); + logAi->trace("Adding town portal node at %s", targetTown->getObjectName()); #endif output.push_back(nodeOptional.value()); } diff --git a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp index 4871501aa..596dda5de 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AILayerTransitionRule.cpp @@ -53,15 +53,13 @@ namespace AIPathfinding for(const CGTownInstance * t : cb->getTownsInfo()) { - // do not allow ally shipyards because of bug - if(t->hasBuilt(BuildingID::SHIPYARD) && t->getOwner() == ai->playerID) + if(t->hasBuilt(BuildingID::SHIPYARD)) shipyards.push_back(t); } for(const CGObjectInstance * obj : ai->memory->visitableObjs) { - // do not allow ally shipyards because of bug - if(obj->ID != Obj::TOWN && obj->getOwner() == ai->playerID) //towns were handled in the previous loop + if(obj->ID != Obj::TOWN) //towns were handled in the previous loop { if(const IShipyard * shipyard = IShipyard::castFrom(obj)) shipyards.push_back(shipyard);