From 6245adb9a4ba5a688322d41e7e031378878c85ac Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Fri, 8 Mar 2024 14:39:16 +0200 Subject: [PATCH] NKAI: configurable object graph --- AI/Nullkiller/AIGateway.cpp | 6 ++- .../Behaviors/CaptureObjectsBehavior.cpp | 7 ++- AI/Nullkiller/Behaviors/ClusterBehavior.cpp | 2 +- AI/Nullkiller/Behaviors/DefenceBehavior.cpp | 4 ++ AI/Nullkiller/Engine/Nullkiller.cpp | 50 +++++++++++-------- AI/Nullkiller/Engine/Settings.cpp | 8 ++- AI/Nullkiller/Engine/Settings.h | 2 + AI/Nullkiller/Pathfinding/AINodeStorage.h | 6 +-- 8 files changed, 55 insertions(+), 30 deletions(-) diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index 947fb6c7e..703461cc1 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -373,7 +373,11 @@ void AIGateway::objectRemoved(const CGObjectInstance * obj, const PlayerColor & return; nullkiller->memory->removeFromMemory(obj); - nullkiller->baseGraph->removeObject(obj); + + if(nullkiller->baseGraph && nullkiller->settings->isObjectGraphAllowed()) + { + nullkiller->baseGraph->removeObject(obj); + } if(obj->ID == Obj::HERO && obj->tempOwner == playerID) { diff --git a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp index 951df981c..eac276ec5 100644 --- a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp +++ b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp @@ -178,8 +178,11 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const #endif const int3 pos = objToVisit->visitablePos(); + bool useObjectGraph = ai->nullkiller->settings->isObjectGraphAllowed() + && ai->nullkiller->getScanDepth() != ScanDepth::SMALL; + + auto paths = ai->nullkiller->pathfinder->getPathInfo(pos, useObjectGraph); - auto paths = ai->nullkiller->pathfinder->getPathInfo(pos, true); std::vector> waysToVisitObj; std::shared_ptr closestWay; @@ -210,7 +213,7 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const { captureObjects(ai->nullkiller->objectClusterizer->getNearbyObjects()); - if(tasks.empty()) + if(tasks.empty() || ai->nullkiller->getScanDepth() != ScanDepth::SMALL) captureObjects(ai->nullkiller->objectClusterizer->getFarObjects()); } diff --git a/AI/Nullkiller/Behaviors/ClusterBehavior.cpp b/AI/Nullkiller/Behaviors/ClusterBehavior.cpp index 4b185ed90..cf6134af3 100644 --- a/AI/Nullkiller/Behaviors/ClusterBehavior.cpp +++ b/AI/Nullkiller/Behaviors/ClusterBehavior.cpp @@ -42,7 +42,7 @@ Goals::TGoalVec ClusterBehavior::decompose() const Goals::TGoalVec ClusterBehavior::decomposeCluster(std::shared_ptr cluster) const { auto center = cluster->calculateCenter(); - auto paths = ai->nullkiller->pathfinder->getPathInfo(center->visitablePos(), true); + auto paths = ai->nullkiller->pathfinder->getPathInfo(center->visitablePos(), ai->nullkiller->settings->isObjectGraphAllowed()); auto blockerPos = cluster->blocker->visitablePos(); std::vector blockerPaths; diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index 7d2ab874e..ffa9098f9 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -443,6 +443,10 @@ void DefenceBehavior::evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitM heroToDismiss = town->garrisonHero.get(); } } + + // avoid dismissing one weak hero in order to recruit another. + if(heroToDismiss && heroToDismiss->getArmyStrength() + 500 > hero->getArmyStrength()) + continue; } else if(ai->nullkiller->heroManager->heroCapReached()) { diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index e71ed1abc..4099010c4 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -125,7 +125,7 @@ void Nullkiller::resetAiState() dangerHitMap->reset(); useHeroChain = true; - if(!baseGraph) + if(!baseGraph && ai->nullkiller->settings->isObjectGraphAllowed()) { baseGraph = std::make_unique(); baseGraph->updateGraph(this); @@ -172,20 +172,24 @@ void Nullkiller::updateAiState(int pass, bool fast) cfg.useHeroChain = useHeroChain; cfg.allowBypassObjects = true; - if(scanDepth == ScanDepth::SMALL) + if(scanDepth == ScanDepth::SMALL || settings->isObjectGraphAllowed()) { - cfg.mainTurnDistanceLimit = ai->nullkiller->settings->getMainHeroTurnDistanceLimit(); + cfg.mainTurnDistanceLimit = settings->getMainHeroTurnDistanceLimit(); } - if(scanDepth != ScanDepth::ALL_FULL) + if(scanDepth != ScanDepth::ALL_FULL || settings->isObjectGraphAllowed()) { - cfg.scoutTurnDistanceLimit = ai->nullkiller->settings->getScoutHeroTurnDistanceLimit(); + cfg.scoutTurnDistanceLimit =settings->getScoutHeroTurnDistanceLimit(); } boost::this_thread::interruption_point(); pathfinder->updatePaths(activeHeroes, cfg); - pathfinder->updateGraphs(activeHeroes); + + if(settings->isObjectGraphAllowed()) + { + pathfinder->updateGraphs(activeHeroes); + } boost::this_thread::interruption_point(); @@ -299,7 +303,8 @@ void Nullkiller::makeTurn() // TODO: better to check turn distance here instead of priority if((heroRole != HeroRole::MAIN || bestTask->priority < SMALL_SCAN_MIN_PRIORITY) - && scanDepth == ScanDepth::MAIN_FULL) + && scanDepth == ScanDepth::MAIN_FULL + && !settings->isObjectGraphAllowed()) { useHeroChain = false; scanDepth = ScanDepth::SMALL; @@ -312,22 +317,25 @@ void Nullkiller::makeTurn() if(bestTask->priority < MIN_PRIORITY) { - auto heroes = cb->getHeroesInfo(); - auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool - { - return h->movementPointsRemaining() > 100; - }); - - if(hasMp && scanDepth != ScanDepth::ALL_FULL) + if(!settings->isObjectGraphAllowed()) { - logAi->trace( - "Goal %s has too low priority %f so increasing scan depth to full.", - taskDescription, - bestTask->priority); + auto heroes = cb->getHeroesInfo(); + auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool + { + return h->movementPointsRemaining() > 100; + }); - scanDepth = ScanDepth::ALL_FULL; - useHeroChain = false; - continue; + if(hasMp && scanDepth != ScanDepth::ALL_FULL) + { + logAi->trace( + "Goal %s has too low priority %f so increasing scan depth to full.", + taskDescription, + bestTask->priority); + + scanDepth = ScanDepth::ALL_FULL; + useHeroChain = false; + continue; + } } logAi->trace("Goal %s has too low priority. It is not worth doing it. Ending turn.", taskDescription); diff --git a/AI/Nullkiller/Engine/Settings.cpp b/AI/Nullkiller/Engine/Settings.cpp index d004db24c..f0465e764 100644 --- a/AI/Nullkiller/Engine/Settings.cpp +++ b/AI/Nullkiller/Engine/Settings.cpp @@ -27,7 +27,8 @@ namespace NKAI mainHeroTurnDistanceLimit(10), scoutHeroTurnDistanceLimit(5), maxGoldPreasure(0.3f), - maxpass(30) + maxpass(30), + allowObjectGraph(false) { ResourcePath resource("config/ai/nkai/nkai-settings", EResType::JSON); @@ -74,5 +75,10 @@ namespace NKAI { maxGoldPreasure = node.Struct()["maxGoldPreasure"].Float(); } + + if(!node.Struct()["allowObjectGraph"].isNull()) + { + allowObjectGraph = node.Struct()["allowObjectGraph"].Bool(); + } } } \ No newline at end of file diff --git a/AI/Nullkiller/Engine/Settings.h b/AI/Nullkiller/Engine/Settings.h index 1be6aac19..c4b9510ec 100644 --- a/AI/Nullkiller/Engine/Settings.h +++ b/AI/Nullkiller/Engine/Settings.h @@ -26,6 +26,7 @@ namespace NKAI int scoutHeroTurnDistanceLimit; int maxpass; float maxGoldPreasure; + bool allowObjectGraph; public: Settings(); @@ -35,6 +36,7 @@ namespace NKAI int getMaxRoamingHeroes() const { return maxRoamingHeroes; } int getMainHeroTurnDistanceLimit() const { return mainHeroTurnDistanceLimit; } int getScoutHeroTurnDistanceLimit() const { return scoutHeroTurnDistanceLimit; } + bool isObjectGraphAllowed() const { return allowObjectGraph; } private: void loadFromMod(const std::string & modName, const ResourcePath & resource); diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index 556402d76..818ded9c8 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -12,7 +12,7 @@ #define NKAI_PATHFINDER_TRACE_LEVEL 0 #define NKAI_GRAPH_TRACE_LEVEL 0 -#define NKAI_TRACE_LEVEL 0 +#define NKAI_TRACE_LEVEL 1 #include "../../../lib/pathfinder/CGPathNode.h" #include "../../../lib/pathfinder/INodeStorage.h" @@ -27,11 +27,9 @@ namespace NKAI { namespace AIPathfinding { - - const int BUCKET_COUNT = 5; + const int BUCKET_COUNT = 5; const int BUCKET_SIZE = 3; const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE; - const int THREAD_COUNT = 8; const int CHAIN_MAX_DEPTH = 4; }