From 71d9664295a026a5cfc97340b37822ab898eda2d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 27 Nov 2024 21:26:06 +0000 Subject: [PATCH 1/7] Allow per-difficulty parameters for NKAI --- AI/Nullkiller/Engine/Nullkiller.cpp | 23 +++++---- AI/Nullkiller/Engine/Settings.cpp | 8 ++- AI/Nullkiller/Engine/Settings.h | 2 +- config/ai/nkai/nkai-settings.json | 80 +++++++++++++++++++++++++---- 4 files changed, 90 insertions(+), 23 deletions(-) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index e4db13bbc..c21f1c57d 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -34,13 +34,12 @@ using namespace Goals; std::unique_ptr Nullkiller::baseGraph; Nullkiller::Nullkiller() - :activeHero(nullptr), scanDepth(ScanDepth::MAIN_FULL), useHeroChain(true) + : activeHero(nullptr) + , scanDepth(ScanDepth::MAIN_FULL) + , useHeroChain(true) + , memory(std::make_unique()) { - memory = std::make_unique(); - settings = std::make_unique(); - useObjectGraph = settings->isObjectGraphAllowed(); - openMap = settings->isOpenMap() || useObjectGraph; } bool canUseOpenMap(std::shared_ptr cb, PlayerColor playerID) @@ -62,17 +61,23 @@ bool canUseOpenMap(std::shared_ptr cb, PlayerColor playerID) return false; } - return cb->getStartInfo()->difficulty >= 3; + return true; } void Nullkiller::init(std::shared_ptr cb, AIGateway * gateway) { this->cb = cb; this->gateway = gateway; - - playerID = gateway->playerID; + this->playerID = gateway->playerID; - if(openMap && !canUseOpenMap(cb, playerID)) + settings = std::make_unique(cb->getStartInfo()->difficulty); + + if(canUseOpenMap(cb, playerID)) + { + useObjectGraph = settings->isObjectGraphAllowed(); + openMap = settings->isOpenMap() || useObjectGraph; + } + else { useObjectGraph = false; openMap = false; diff --git a/AI/Nullkiller/Engine/Settings.cpp b/AI/Nullkiller/Engine/Settings.cpp index 2f88204a1..7271a361f 100644 --- a/AI/Nullkiller/Engine/Settings.cpp +++ b/AI/Nullkiller/Engine/Settings.cpp @@ -11,6 +11,8 @@ #include #include "Settings.h" + +#include "../../../lib/constants/StringConstants.h" #include "../../../lib/mapObjectConstructors/AObjectTypeHandler.h" #include "../../../lib/mapObjectConstructors/CObjectClassesHandler.h" #include "../../../lib/mapObjectConstructors/CBankInstanceConstructor.h" @@ -22,7 +24,7 @@ namespace NKAI { - Settings::Settings() + Settings::Settings(int difficultyLevel) : maxRoamingHeroes(8), mainHeroTurnDistanceLimit(10), scoutHeroTurnDistanceLimit(5), @@ -35,7 +37,9 @@ namespace NKAI openMap(true), useFuzzy(false) { - JsonNode node = JsonUtils::assembleFromFiles("config/ai/nkai/nkai-settings"); + const std::string & difficultyName = GameConstants::DIFFICULTY_NAMES[difficultyLevel]; + const JsonNode & rootNode = JsonUtils::assembleFromFiles("config/ai/nkai/nkai-settings"); + const JsonNode & node = rootNode[difficultyName]; maxRoamingHeroes = node["maxRoamingHeroes"].Integer(); mainHeroTurnDistanceLimit = node["mainHeroTurnDistanceLimit"].Integer(); diff --git a/AI/Nullkiller/Engine/Settings.h b/AI/Nullkiller/Engine/Settings.h index d35a00632..2dad59c69 100644 --- a/AI/Nullkiller/Engine/Settings.h +++ b/AI/Nullkiller/Engine/Settings.h @@ -34,7 +34,7 @@ namespace NKAI bool useFuzzy; public: - Settings(); + explicit Settings(int difficultyLevel); int getMaxPass() const { return maxpass; } float getMaxGoldPressure() const { return maxGoldPressure; } diff --git a/config/ai/nkai/nkai-settings.json b/config/ai/nkai/nkai-settings.json index 9640d149e..345e1a007 100644 --- a/config/ai/nkai/nkai-settings.json +++ b/config/ai/nkai/nkai-settings.json @@ -1,13 +1,71 @@ { - "maxRoamingHeroes" : 8, - "maxpass" : 30, - "mainHeroTurnDistanceLimit" : 10, - "scoutHeroTurnDistanceLimit" : 5, - "maxGoldPressure" : 0.3, - "useTroopsFromGarrisons" : true, - "openMap": true, - "allowObjectGraph": false, - "pathfinderBucketsCount" : 1, // old value: 3, - "pathfinderBucketSize" : 32, // old value: 7, - "useFuzzy" : false + "pawn" : { + "maxRoamingHeroes" : 8, + "maxpass" : 30, + "mainHeroTurnDistanceLimit" : 10, + "scoutHeroTurnDistanceLimit" : 5, + "maxGoldPressure" : 0.3, + "useTroopsFromGarrisons" : true, + "openMap": false, + "allowObjectGraph": false, + "pathfinderBucketsCount" : 1, // old value: 3, + "pathfinderBucketSize" : 32, // old value: 7, + "useFuzzy" : false + }, + + "knight" : { + "maxRoamingHeroes" : 8, + "maxpass" : 30, + "mainHeroTurnDistanceLimit" : 10, + "scoutHeroTurnDistanceLimit" : 5, + "maxGoldPressure" : 0.3, + "useTroopsFromGarrisons" : true, + "openMap": false, + "allowObjectGraph": false, + "pathfinderBucketsCount" : 1, // old value: 3, + "pathfinderBucketSize" : 32, // old value: 7, + "useFuzzy" : false + }, + + "rook" : { + "maxRoamingHeroes" : 8, + "maxpass" : 30, + "mainHeroTurnDistanceLimit" : 10, + "scoutHeroTurnDistanceLimit" : 5, + "maxGoldPressure" : 0.3, + "useTroopsFromGarrisons" : true, + "openMap": false, + "allowObjectGraph": false, + "pathfinderBucketsCount" : 1, // old value: 3, + "pathfinderBucketSize" : 32, // old value: 7, + "useFuzzy" : false + }, + + "queen" : { + "maxRoamingHeroes" : 8, + "maxpass" : 30, + "mainHeroTurnDistanceLimit" : 10, + "scoutHeroTurnDistanceLimit" : 5, + "maxGoldPressure" : 0.3, + "useTroopsFromGarrisons" : true, + "openMap": true, + "allowObjectGraph": false, + "pathfinderBucketsCount" : 1, // old value: 3, + "pathfinderBucketSize" : 32, // old value: 7, + "useFuzzy" : false + }, + + "king" : { + "maxRoamingHeroes" : 8, + "maxpass" : 30, + "mainHeroTurnDistanceLimit" : 10, + "scoutHeroTurnDistanceLimit" : 5, + "maxGoldPressure" : 0.3, + "useTroopsFromGarrisons" : true, + "openMap": true, + "allowObjectGraph": false, + "pathfinderBucketsCount" : 1, // old value: 3, + "pathfinderBucketSize" : 32, // old value: 7, + "useFuzzy" : false + } } \ No newline at end of file From 00492a60a7369c04d999005a05cebe660bdfda22 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 28 Nov 2024 11:39:36 +0000 Subject: [PATCH 2/7] Moved retreat decision-making constants to config --- AI/Nullkiller/AIGateway.cpp | 4 +--- AI/Nullkiller/Engine/Settings.cpp | 6 +++++- AI/Nullkiller/Engine/Settings.h | 4 ++++ config/ai/nkai/nkai-settings.json | 10 ++++++++++ 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index d0d2d998a..861ee237a 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -36,8 +36,6 @@ namespace NKAI // our to enemy strength ratio constants const float SAFE_ATTACK_CONSTANT = 1.1f; -const float RETREAT_THRESHOLD = 0.3f; -const double RETREAT_ABSOLUTE_THRESHOLD = 10000.; //one thread may be turn of AI and another will be handling a side effect for AI2 thread_local CCallback * cb = nullptr; @@ -553,7 +551,7 @@ std::optional AIGateway::makeSurrenderRetreatDecision(const Battle double fightRatio = ourStrength / (double)battleState.getEnemyStrength(); // if we have no towns - things are already bad, so retreat is not an option. - if(cb->getTownsInfo().size() && ourStrength < RETREAT_ABSOLUTE_THRESHOLD && fightRatio < RETREAT_THRESHOLD && battleState.canFlee) + if(cb->getTownsInfo().size() && ourStrength < nullkiller->settings->getRetreatThresholdAbsoolute() && fightRatio < nullkiller->settings->getRetreatThresholdRelative() && battleState.canFlee) { return BattleAction::makeRetreat(battleState.ourSide); } diff --git a/AI/Nullkiller/Engine/Settings.cpp b/AI/Nullkiller/Engine/Settings.cpp index 7271a361f..eaf44d1b7 100644 --- a/AI/Nullkiller/Engine/Settings.cpp +++ b/AI/Nullkiller/Engine/Settings.cpp @@ -28,7 +28,9 @@ namespace NKAI : maxRoamingHeroes(8), mainHeroTurnDistanceLimit(10), scoutHeroTurnDistanceLimit(5), - maxGoldPressure(0.3f), + maxGoldPressure(0.3f), + retreatThresholdRelative(0.3), + retreatThresholdAbsoolute(10000), maxpass(10), pathfinderBucketsCount(1), pathfinderBucketSize(32), @@ -48,6 +50,8 @@ namespace NKAI pathfinderBucketsCount = node["pathfinderBucketsCount"].Integer(); pathfinderBucketSize = node["pathfinderBucketSize"].Integer(); maxGoldPressure = node["maxGoldPressure"].Float(); + retreatThresholdRelative = node["retreatThresholdRelative"].Float(); + retreatThresholdAbsoolute = node["retreatThresholdAbsoolute"].Float(); allowObjectGraph = node["allowObjectGraph"].Bool(); openMap = node["openMap"].Bool(); useFuzzy = node["useFuzzy"].Bool(); diff --git a/AI/Nullkiller/Engine/Settings.h b/AI/Nullkiller/Engine/Settings.h index 2dad59c69..dfecb6530 100644 --- a/AI/Nullkiller/Engine/Settings.h +++ b/AI/Nullkiller/Engine/Settings.h @@ -28,6 +28,8 @@ namespace NKAI int pathfinderBucketsCount; int pathfinderBucketSize; float maxGoldPressure; + float retreatThresholdRelative; + float retreatThresholdAbsoolute; bool allowObjectGraph; bool useTroopsFromGarrisons; bool openMap; @@ -38,6 +40,8 @@ namespace NKAI int getMaxPass() const { return maxpass; } float getMaxGoldPressure() const { return maxGoldPressure; } + float getRetreatThresholdRelative() const { return retreatThresholdRelative; } + float getRetreatThresholdAbsoolute() const { return retreatThresholdAbsoolute; } int getMaxRoamingHeroes() const { return maxRoamingHeroes; } int getMainHeroTurnDistanceLimit() const { return mainHeroTurnDistanceLimit; } int getScoutHeroTurnDistanceLimit() const { return scoutHeroTurnDistanceLimit; } diff --git a/config/ai/nkai/nkai-settings.json b/config/ai/nkai/nkai-settings.json index 345e1a007..6ef63c453 100644 --- a/config/ai/nkai/nkai-settings.json +++ b/config/ai/nkai/nkai-settings.json @@ -10,6 +10,8 @@ "allowObjectGraph": false, "pathfinderBucketsCount" : 1, // old value: 3, "pathfinderBucketSize" : 32, // old value: 7, + "retreatThresholdRelative" : 0.3, + "retreatThresholdAbsoolute" : 10000, "useFuzzy" : false }, @@ -24,6 +26,8 @@ "allowObjectGraph": false, "pathfinderBucketsCount" : 1, // old value: 3, "pathfinderBucketSize" : 32, // old value: 7, + "retreatThresholdRelative" : 0.3, + "retreatThresholdAbsoolute" : 10000, "useFuzzy" : false }, @@ -38,6 +42,8 @@ "allowObjectGraph": false, "pathfinderBucketsCount" : 1, // old value: 3, "pathfinderBucketSize" : 32, // old value: 7, + "retreatThresholdRelative" : 0.3, + "retreatThresholdAbsoolute" : 10000, "useFuzzy" : false }, @@ -52,6 +58,8 @@ "allowObjectGraph": false, "pathfinderBucketsCount" : 1, // old value: 3, "pathfinderBucketSize" : 32, // old value: 7, + "retreatThresholdRelative" : 0.3, + "retreatThresholdAbsoolute" : 10000, "useFuzzy" : false }, @@ -66,6 +74,8 @@ "allowObjectGraph": false, "pathfinderBucketsCount" : 1, // old value: 3, "pathfinderBucketSize" : 32, // old value: 7, + "retreatThresholdRelative" : 0.3, + "retreatThresholdAbsoolute" : 10000, "useFuzzy" : false } } \ No newline at end of file From b25702f15ed347a66dc3e48899be2f26eca7bc7e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 28 Nov 2024 11:53:51 +0000 Subject: [PATCH 3/7] expose safeAttackRatio to AI config --- AI/Nullkiller/AIGateway.cpp | 5 +---- AI/Nullkiller/AIUtility.cpp | 8 ++++---- AI/Nullkiller/AIUtility.h | 8 ++------ AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp | 4 ++-- AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp | 2 +- AI/Nullkiller/Behaviors/DefenceBehavior.cpp | 2 +- AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp | 4 ++-- AI/Nullkiller/Engine/Settings.cpp | 2 ++ AI/Nullkiller/Engine/Settings.h | 2 ++ AI/Nullkiller/Helpers/ExplorationHelper.cpp | 2 +- config/ai/nkai/nkai-settings.json | 5 +++++ 11 files changed, 23 insertions(+), 21 deletions(-) diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index 861ee237a..c73a12dbe 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -34,9 +34,6 @@ namespace NKAI { -// our to enemy strength ratio constants -const float SAFE_ATTACK_CONSTANT = 1.1f; - //one thread may be turn of AI and another will be handling a side effect for AI2 thread_local CCallback * cb = nullptr; thread_local AIGateway * ai = nullptr; @@ -668,7 +665,7 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector (1 / SAFE_ATTACK_CONSTANT); + bool dangerTooHigh = ratio * nullkiller->settings->getSafeAttackRatio() > 1; answer = !dangerUnknown && !dangerTooHigh; } diff --git a/AI/Nullkiller/AIUtility.cpp b/AI/Nullkiller/AIUtility.cpp index 2eef0aa0d..4ee0e960e 100644 --- a/AI/Nullkiller/AIUtility.cpp +++ b/AI/Nullkiller/AIUtility.cpp @@ -146,21 +146,21 @@ bool HeroPtr::operator==(const HeroPtr & rhs) const return h == rhs.get(true); } -bool isSafeToVisit(const CGHeroInstance * h, const CCreatureSet * heroArmy, uint64_t dangerStrength) +bool isSafeToVisit(const CGHeroInstance * h, const CCreatureSet * heroArmy, uint64_t dangerStrength, float safeAttackRatio) { const ui64 heroStrength = h->getHeroStrength() * heroArmy->getArmyStrength(); if(dangerStrength) { - return heroStrength / SAFE_ATTACK_CONSTANT > dangerStrength; + return heroStrength > dangerStrength * safeAttackRatio; } return true; //there's no danger } -bool isSafeToVisit(const CGHeroInstance * h, uint64_t dangerStrength) +bool isSafeToVisit(const CGHeroInstance * h, uint64_t dangerStrength, float safeAttackRatio) { - return isSafeToVisit(h, h, dangerStrength); + return isSafeToVisit(h, h, dangerStrength, safeAttackRatio); } bool isObjectRemovable(const CGObjectInstance * obj) diff --git a/AI/Nullkiller/AIUtility.h b/AI/Nullkiller/AIUtility.h index 728130ff6..5ddd8b3e4 100644 --- a/AI/Nullkiller/AIUtility.h +++ b/AI/Nullkiller/AIUtility.h @@ -63,10 +63,6 @@ const int RESOURCE_MINE_PRODUCTION = 1; const int ACTUAL_RESOURCE_COUNT = 7; const int ALLOWED_ROAMING_HEROES = 8; -//implementation-dependent -extern const float SAFE_ATTACK_CONSTANT; -extern const int GOLD_RESERVE; - extern thread_local CCallback * cb; enum HeroRole @@ -213,8 +209,8 @@ bool isBlockVisitObj(const int3 & pos); bool isWeeklyRevisitable(const Nullkiller * ai, const CGObjectInstance * obj); bool isObjectRemovable(const CGObjectInstance * obj); //FIXME FIXME: move logic to object property! -bool isSafeToVisit(const CGHeroInstance * h, uint64_t dangerStrength); -bool isSafeToVisit(const CGHeroInstance * h, const CCreatureSet *, uint64_t dangerStrength); +bool isSafeToVisit(const CGHeroInstance * h, uint64_t dangerStrength, float safeAttackRatio); +bool isSafeToVisit(const CGHeroInstance * h, const CCreatureSet *, uint64_t dangerStrength, float safeAttackRatio); bool compareHeroStrength(const CGHeroInstance * h1, const CGHeroInstance * h2); bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2); diff --git a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp index c17cb9c3b..792225b7d 100644 --- a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp @@ -316,8 +316,8 @@ uint64_t DangerHitMapAnalyzer::enemyCanKillOurHeroesAlongThePath(const AIPath & const auto& info = getTileThreat(tile); - return (info.fastestDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.fastestDanger.danger)) - || (info.maximumDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.maximumDanger.danger)); + return (info.fastestDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.fastestDanger.danger, ai->settings->getSafeAttackRatio())) + || (info.maximumDanger.turn <= turn && !isSafeToVisit(path.targetHero, path.heroArmy, info.maximumDanger.danger, ai->settings->getSafeAttackRatio())); } const HitMapNode & DangerHitMapAnalyzer::getObjectThreat(const CGObjectInstance * obj) const diff --git a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp index 1e7f39569..38e71b675 100644 --- a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp +++ b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp @@ -114,7 +114,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals( continue; } - auto isSafe = isSafeToVisit(hero, path.heroArmy, danger); + auto isSafe = isSafeToVisit(hero, path.heroArmy, danger, nullkiller->settings->getSafeAttackRatio()); #if NKAI_TRACE_LEVEL >= 2 logAi->trace( diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index 710492fbc..86bd7e77c 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -305,7 +305,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta continue; } - if(threat.turn == 0 || (path.turn() <= threat.turn && path.getHeroStrength() * SAFE_ATTACK_CONSTANT >= threat.danger)) + if(threat.turn == 0 || (path.turn() <= threat.turn && path.getHeroStrength() * ai->settings->getSafeAttackRatio() >= threat.danger)) { if(ai->arePathHeroesLocked(path)) { diff --git a/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp b/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp index ceabfd0cf..b6b0811cb 100644 --- a/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp +++ b/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp @@ -145,7 +145,7 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const Nullkiller * ai, con } auto danger = path.getTotalDanger(); - auto isSafe = isSafeToVisit(hero, path.heroArmy, danger); + auto isSafe = isSafeToVisit(hero, path.heroArmy, danger, ai->settings->getSafeAttackRatio()); #if NKAI_TRACE_LEVEL >= 2 logAi->trace( @@ -341,7 +341,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * ai, const CGT auto danger = path.getTotalDanger(); - auto isSafe = isSafeToVisit(path.targetHero, path.heroArmy, danger); + auto isSafe = isSafeToVisit(path.targetHero, path.heroArmy, danger, ai->settings->getSafeAttackRatio()); #if NKAI_TRACE_LEVEL >= 2 logAi->trace( diff --git a/AI/Nullkiller/Engine/Settings.cpp b/AI/Nullkiller/Engine/Settings.cpp index eaf44d1b7..c61318bd1 100644 --- a/AI/Nullkiller/Engine/Settings.cpp +++ b/AI/Nullkiller/Engine/Settings.cpp @@ -31,6 +31,7 @@ namespace NKAI maxGoldPressure(0.3f), retreatThresholdRelative(0.3), retreatThresholdAbsoolute(10000), + safeAttackRatio(1.1), maxpass(10), pathfinderBucketsCount(1), pathfinderBucketSize(32), @@ -52,6 +53,7 @@ namespace NKAI maxGoldPressure = node["maxGoldPressure"].Float(); retreatThresholdRelative = node["retreatThresholdRelative"].Float(); retreatThresholdAbsoolute = node["retreatThresholdAbsoolute"].Float(); + safeAttackRatio = node["safeAttackRatio"].Float(); allowObjectGraph = node["allowObjectGraph"].Bool(); openMap = node["openMap"].Bool(); useFuzzy = node["useFuzzy"].Bool(); diff --git a/AI/Nullkiller/Engine/Settings.h b/AI/Nullkiller/Engine/Settings.h index dfecb6530..e67b938ad 100644 --- a/AI/Nullkiller/Engine/Settings.h +++ b/AI/Nullkiller/Engine/Settings.h @@ -30,6 +30,7 @@ namespace NKAI float maxGoldPressure; float retreatThresholdRelative; float retreatThresholdAbsoolute; + float safeAttackRatio; bool allowObjectGraph; bool useTroopsFromGarrisons; bool openMap; @@ -42,6 +43,7 @@ namespace NKAI float getMaxGoldPressure() const { return maxGoldPressure; } float getRetreatThresholdRelative() const { return retreatThresholdRelative; } float getRetreatThresholdAbsoolute() const { return retreatThresholdAbsoolute; } + float getSafeAttackRatio() const { return safeAttackRatio; } int getMaxRoamingHeroes() const { return maxRoamingHeroes; } int getMainHeroTurnDistanceLimit() const { return mainHeroTurnDistanceLimit; } int getScoutHeroTurnDistanceLimit() const { return scoutHeroTurnDistanceLimit; } diff --git a/AI/Nullkiller/Helpers/ExplorationHelper.cpp b/AI/Nullkiller/Helpers/ExplorationHelper.cpp index 75f40e9cb..0c17e0cc2 100644 --- a/AI/Nullkiller/Helpers/ExplorationHelper.cpp +++ b/AI/Nullkiller/Helpers/ExplorationHelper.cpp @@ -175,7 +175,7 @@ void ExplorationHelper::scanTile(const int3 & tile) continue; } - if(isSafeToVisit(hero, path.heroArmy, path.getTotalDanger())) + if(isSafeToVisit(hero, path.heroArmy, path.getTotalDanger(), ai->settings->getSafeAttackRatio())) { bestGoal = goal; bestValue = ourValue; diff --git a/config/ai/nkai/nkai-settings.json b/config/ai/nkai/nkai-settings.json index 6ef63c453..62b517abd 100644 --- a/config/ai/nkai/nkai-settings.json +++ b/config/ai/nkai/nkai-settings.json @@ -12,6 +12,7 @@ "pathfinderBucketSize" : 32, // old value: 7, "retreatThresholdRelative" : 0.3, "retreatThresholdAbsoolute" : 10000, + "safeAttackRatio" : 1.1, "useFuzzy" : false }, @@ -28,6 +29,7 @@ "pathfinderBucketSize" : 32, // old value: 7, "retreatThresholdRelative" : 0.3, "retreatThresholdAbsoolute" : 10000, + "safeAttackRatio" : 1.1, "useFuzzy" : false }, @@ -44,6 +46,7 @@ "pathfinderBucketSize" : 32, // old value: 7, "retreatThresholdRelative" : 0.3, "retreatThresholdAbsoolute" : 10000, + "safeAttackRatio" : 1.1, "useFuzzy" : false }, @@ -60,6 +63,7 @@ "pathfinderBucketSize" : 32, // old value: 7, "retreatThresholdRelative" : 0.3, "retreatThresholdAbsoolute" : 10000, + "safeAttackRatio" : 1.1, "useFuzzy" : false }, @@ -76,6 +80,7 @@ "pathfinderBucketSize" : 32, // old value: 7, "retreatThresholdRelative" : 0.3, "retreatThresholdAbsoolute" : 10000, + "safeAttackRatio" : 1.1, "useFuzzy" : false } } \ No newline at end of file From be2327d0004868fc0641fa18d794e8f94ccf00fc Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 28 Nov 2024 12:11:13 +0000 Subject: [PATCH 4/7] Removed no longer used or redundant constants --- AI/Nullkiller/AIUtility.h | 1 - AI/Nullkiller/Analyzers/HeroManager.cpp | 3 +-- AI/VCAI/AIUtility.h | 2 -- AI/VCAI/ResourceManager.cpp | 2 -- AI/VCAI/VCAI.cpp | 2 -- 5 files changed, 1 insertion(+), 9 deletions(-) diff --git a/AI/Nullkiller/AIUtility.h b/AI/Nullkiller/AIUtility.h index 5ddd8b3e4..4275bea9f 100644 --- a/AI/Nullkiller/AIUtility.h +++ b/AI/Nullkiller/AIUtility.h @@ -61,7 +61,6 @@ const int GOLD_MINE_PRODUCTION = 1000; const int WOOD_ORE_MINE_PRODUCTION = 2; const int RESOURCE_MINE_PRODUCTION = 1; const int ACTUAL_RESOURCE_COUNT = 7; -const int ALLOWED_ROAMING_HEROES = 8; extern thread_local CCallback * cb; diff --git a/AI/Nullkiller/Analyzers/HeroManager.cpp b/AI/Nullkiller/Analyzers/HeroManager.cpp index 2d5166bc3..be3526fda 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.cpp +++ b/AI/Nullkiller/Analyzers/HeroManager.cpp @@ -195,8 +195,7 @@ bool HeroManager::heroCapReached(bool includeGarrisoned) const { int heroCount = cb->getHeroCount(ai->playerID, includeGarrisoned); - return heroCount >= ALLOWED_ROAMING_HEROES - || heroCount >= ai->settings->getMaxRoamingHeroes() + return heroCount >= ai->settings->getMaxRoamingHeroes() || heroCount >= cb->getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP) || heroCount >= cb->getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP); } diff --git a/AI/VCAI/AIUtility.h b/AI/VCAI/AIUtility.h index 18b6133b4..c1a19d858 100644 --- a/AI/VCAI/AIUtility.h +++ b/AI/VCAI/AIUtility.h @@ -25,11 +25,9 @@ using crstring = const std::string &; using dwellingContent = std::pair>; const int ACTUAL_RESOURCE_COUNT = 7; -const int ALLOWED_ROAMING_HEROES = 8; //implementation-dependent extern const double SAFE_ATTACK_CONSTANT; -extern const int GOLD_RESERVE; extern thread_local CCallback * cb; extern thread_local VCAI * ai; diff --git a/AI/VCAI/ResourceManager.cpp b/AI/VCAI/ResourceManager.cpp index e8f9b75ce..44c0e53d8 100644 --- a/AI/VCAI/ResourceManager.cpp +++ b/AI/VCAI/ResourceManager.cpp @@ -14,8 +14,6 @@ #include "../../CCallback.h" #include "../../lib/mapObjects/MapObjects.h" -#define GOLD_RESERVE (10000); //at least we'll be able to reach capitol - ResourceObjective::ResourceObjective(const TResources & Res, Goals::TSubgoal Goal) : resources(Res), goal(Goal) { diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 4e5f41ea2..8af203906 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -1314,8 +1314,6 @@ bool VCAI::canRecruitAnyHero(const CGTownInstance * t) const 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() >= cb->getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP)) return false; if(!cb->getAvailableHeroes(t).size()) From 90536a5fbe8f3a6fff11fa4ffd5ceb9904591078 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 28 Nov 2024 12:11:32 +0000 Subject: [PATCH 5/7] Remove hardcoded morale chance constant --- AI/Nullkiller/Analyzers/ArmyManager.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/AI/Nullkiller/Analyzers/ArmyManager.cpp b/AI/Nullkiller/Analyzers/ArmyManager.cpp index a65ce2bbb..b366fa176 100644 --- a/AI/Nullkiller/Analyzers/ArmyManager.cpp +++ b/AI/Nullkiller/Analyzers/ArmyManager.cpp @@ -13,6 +13,7 @@ #include "../Engine/Nullkiller.h" #include "../../../CCallback.h" #include "../../../lib/mapObjects/MapObjects.h" +#include "../../../lib/IGameSettings.h" #include "../../../lib/GameConstants.h" namespace NKAI @@ -187,16 +188,18 @@ std::vector ArmyManager::getBestArmy(const IBonusBearer * armyCarrier, auto morale = slot.second->moraleVal(); auto multiplier = 1.0f; - const float BadMoraleChance = 0.083f; - const float HighMoraleChance = 0.04f; + const auto & badMoraleDice = cb->getSettings().getVector(EGameSettings::COMBAT_BAD_MORALE_DICE); + const auto & highMoraleDice = cb->getSettings().getVector(EGameSettings::COMBAT_GOOD_MORALE_DICE); - if(morale < 0) + if(morale < 0 && !badMoraleDice.empty()) { - multiplier += morale * BadMoraleChance; + size_t diceIndex = std::min(badMoraleDice.size(), -morale) - 1; + multiplier -= 1.0 / badMoraleDice.at(diceIndex); } - else if(morale > 0) + else if(morale > 0 && !highMoraleDice.empty()) { - multiplier += morale * HighMoraleChance; + size_t diceIndex = std::min(highMoraleDice.size(), morale) - 1; + multiplier += 1.0 / highMoraleDice.at(diceIndex); } newValue += multiplier * slot.second->getPower(); From 40aa49acff165c9ece4d04c99128f7ec043e5d63 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 28 Nov 2024 14:44:12 +0000 Subject: [PATCH 6/7] Fix typo: Absoolute -> Absolute --- AI/Nullkiller/AIGateway.cpp | 2 +- AI/Nullkiller/Engine/Settings.cpp | 4 ++-- AI/Nullkiller/Engine/Settings.h | 4 ++-- config/ai/nkai/nkai-settings.json | 10 +++++----- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index c73a12dbe..c803a9259 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -548,7 +548,7 @@ std::optional AIGateway::makeSurrenderRetreatDecision(const Battle double fightRatio = ourStrength / (double)battleState.getEnemyStrength(); // if we have no towns - things are already bad, so retreat is not an option. - if(cb->getTownsInfo().size() && ourStrength < nullkiller->settings->getRetreatThresholdAbsoolute() && fightRatio < nullkiller->settings->getRetreatThresholdRelative() && battleState.canFlee) + if(cb->getTownsInfo().size() && ourStrength < nullkiller->settings->getRetreatThresholdAbsolute() && fightRatio < nullkiller->settings->getRetreatThresholdRelative() && battleState.canFlee) { return BattleAction::makeRetreat(battleState.ourSide); } diff --git a/AI/Nullkiller/Engine/Settings.cpp b/AI/Nullkiller/Engine/Settings.cpp index c61318bd1..17f38ad64 100644 --- a/AI/Nullkiller/Engine/Settings.cpp +++ b/AI/Nullkiller/Engine/Settings.cpp @@ -30,7 +30,7 @@ namespace NKAI scoutHeroTurnDistanceLimit(5), maxGoldPressure(0.3f), retreatThresholdRelative(0.3), - retreatThresholdAbsoolute(10000), + retreatThresholdAbsolute(10000), safeAttackRatio(1.1), maxpass(10), pathfinderBucketsCount(1), @@ -52,7 +52,7 @@ namespace NKAI pathfinderBucketSize = node["pathfinderBucketSize"].Integer(); maxGoldPressure = node["maxGoldPressure"].Float(); retreatThresholdRelative = node["retreatThresholdRelative"].Float(); - retreatThresholdAbsoolute = node["retreatThresholdAbsoolute"].Float(); + retreatThresholdAbsolute = node["retreatThresholdAbsolute"].Float(); safeAttackRatio = node["safeAttackRatio"].Float(); allowObjectGraph = node["allowObjectGraph"].Bool(); openMap = node["openMap"].Bool(); diff --git a/AI/Nullkiller/Engine/Settings.h b/AI/Nullkiller/Engine/Settings.h index e67b938ad..769c5ae91 100644 --- a/AI/Nullkiller/Engine/Settings.h +++ b/AI/Nullkiller/Engine/Settings.h @@ -29,7 +29,7 @@ namespace NKAI int pathfinderBucketSize; float maxGoldPressure; float retreatThresholdRelative; - float retreatThresholdAbsoolute; + float retreatThresholdAbsolute; float safeAttackRatio; bool allowObjectGraph; bool useTroopsFromGarrisons; @@ -42,7 +42,7 @@ namespace NKAI int getMaxPass() const { return maxpass; } float getMaxGoldPressure() const { return maxGoldPressure; } float getRetreatThresholdRelative() const { return retreatThresholdRelative; } - float getRetreatThresholdAbsoolute() const { return retreatThresholdAbsoolute; } + float getRetreatThresholdAbsolute() const { return retreatThresholdAbsolute; } float getSafeAttackRatio() const { return safeAttackRatio; } int getMaxRoamingHeroes() const { return maxRoamingHeroes; } int getMainHeroTurnDistanceLimit() const { return mainHeroTurnDistanceLimit; } diff --git a/config/ai/nkai/nkai-settings.json b/config/ai/nkai/nkai-settings.json index 62b517abd..7c29e2f27 100644 --- a/config/ai/nkai/nkai-settings.json +++ b/config/ai/nkai/nkai-settings.json @@ -11,7 +11,7 @@ "pathfinderBucketsCount" : 1, // old value: 3, "pathfinderBucketSize" : 32, // old value: 7, "retreatThresholdRelative" : 0.3, - "retreatThresholdAbsoolute" : 10000, + "retreatThresholdAbsolute" : 10000, "safeAttackRatio" : 1.1, "useFuzzy" : false }, @@ -28,7 +28,7 @@ "pathfinderBucketsCount" : 1, // old value: 3, "pathfinderBucketSize" : 32, // old value: 7, "retreatThresholdRelative" : 0.3, - "retreatThresholdAbsoolute" : 10000, + "retreatThresholdAbsolute" : 10000, "safeAttackRatio" : 1.1, "useFuzzy" : false }, @@ -45,7 +45,7 @@ "pathfinderBucketsCount" : 1, // old value: 3, "pathfinderBucketSize" : 32, // old value: 7, "retreatThresholdRelative" : 0.3, - "retreatThresholdAbsoolute" : 10000, + "retreatThresholdAbsolute" : 10000, "safeAttackRatio" : 1.1, "useFuzzy" : false }, @@ -62,7 +62,7 @@ "pathfinderBucketsCount" : 1, // old value: 3, "pathfinderBucketSize" : 32, // old value: 7, "retreatThresholdRelative" : 0.3, - "retreatThresholdAbsoolute" : 10000, + "retreatThresholdAbsolute" : 10000, "safeAttackRatio" : 1.1, "useFuzzy" : false }, @@ -79,7 +79,7 @@ "pathfinderBucketsCount" : 1, // old value: 3, "pathfinderBucketSize" : 32, // old value: 7, "retreatThresholdRelative" : 0.3, - "retreatThresholdAbsoolute" : 10000, + "retreatThresholdAbsolute" : 10000, "safeAttackRatio" : 1.1, "useFuzzy" : false } From 7574842e936994f30edc784ad4cf259aa409afb9 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 28 Nov 2024 15:08:15 +0000 Subject: [PATCH 7/7] Replace defines with constexpr --- AI/Nullkiller/Engine/FuzzyEngines.cpp | 3 +-- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/AI/Nullkiller/Engine/FuzzyEngines.cpp b/AI/Nullkiller/Engine/FuzzyEngines.cpp index 7659f1352..a05119d90 100644 --- a/AI/Nullkiller/Engine/FuzzyEngines.cpp +++ b/AI/Nullkiller/Engine/FuzzyEngines.cpp @@ -17,8 +17,7 @@ namespace NKAI { -#define MIN_AI_STRENGTH (0.5f) //lower when combat AI gets smarter -#define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us +constexpr float MIN_AI_STRENGTH = 0.5f; //lower when combat AI gets smarter engineBase::engineBase() { diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index eb42a0c1f..92faf4bd8 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -35,9 +35,7 @@ namespace NKAI { -#define MIN_AI_STRENGTH (0.5f) //lower when combat AI gets smarter -#define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us -const float MIN_CRITICAL_VALUE = 2.0f; +constexpr float MIN_CRITICAL_VALUE = 2.0f; EvaluationContext::EvaluationContext(const Nullkiller* ai) : movementCost(0.0),