diff --git a/AI/Nullkiller/AIGateway.cpp b/AI/Nullkiller/AIGateway.cpp index d0d2d998a..c803a9259 100644 --- a/AI/Nullkiller/AIGateway.cpp +++ b/AI/Nullkiller/AIGateway.cpp @@ -34,11 +34,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; thread_local AIGateway * ai = nullptr; @@ -553,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 < RETREAT_ABSOLUTE_THRESHOLD && fightRatio < RETREAT_THRESHOLD && battleState.canFlee) + if(cb->getTownsInfo().size() && ourStrength < nullkiller->settings->getRetreatThresholdAbsolute() && fightRatio < nullkiller->settings->getRetreatThresholdRelative() && battleState.canFlee) { return BattleAction::makeRetreat(battleState.ourSide); } @@ -670,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..4275bea9f 100644 --- a/AI/Nullkiller/AIUtility.h +++ b/AI/Nullkiller/AIUtility.h @@ -61,11 +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; - -//implementation-dependent -extern const float SAFE_ATTACK_CONSTANT; -extern const int GOLD_RESERVE; extern thread_local CCallback * cb; @@ -213,8 +208,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/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(); 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/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/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/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/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/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), diff --git a/AI/Nullkiller/Engine/Settings.cpp b/AI/Nullkiller/Engine/Settings.cpp index 2f88204a1..17f38ad64 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,11 +24,14 @@ namespace NKAI { - Settings::Settings() + Settings::Settings(int difficultyLevel) : maxRoamingHeroes(8), mainHeroTurnDistanceLimit(10), scoutHeroTurnDistanceLimit(5), - maxGoldPressure(0.3f), + maxGoldPressure(0.3f), + retreatThresholdRelative(0.3), + retreatThresholdAbsolute(10000), + safeAttackRatio(1.1), maxpass(10), pathfinderBucketsCount(1), pathfinderBucketSize(32), @@ -35,7 +40,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(); @@ -44,6 +51,9 @@ namespace NKAI pathfinderBucketsCount = node["pathfinderBucketsCount"].Integer(); pathfinderBucketSize = node["pathfinderBucketSize"].Integer(); maxGoldPressure = node["maxGoldPressure"].Float(); + retreatThresholdRelative = node["retreatThresholdRelative"].Float(); + retreatThresholdAbsolute = node["retreatThresholdAbsolute"].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 d35a00632..769c5ae91 100644 --- a/AI/Nullkiller/Engine/Settings.h +++ b/AI/Nullkiller/Engine/Settings.h @@ -28,16 +28,22 @@ namespace NKAI int pathfinderBucketsCount; int pathfinderBucketSize; float maxGoldPressure; + float retreatThresholdRelative; + float retreatThresholdAbsolute; + float safeAttackRatio; bool allowObjectGraph; bool useTroopsFromGarrisons; bool openMap; bool useFuzzy; public: - Settings(); + explicit Settings(int difficultyLevel); int getMaxPass() const { return maxpass; } float getMaxGoldPressure() const { return maxGoldPressure; } + float getRetreatThresholdRelative() const { return retreatThresholdRelative; } + float getRetreatThresholdAbsolute() const { return retreatThresholdAbsolute; } + 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/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()) diff --git a/config/ai/nkai/nkai-settings.json b/config/ai/nkai/nkai-settings.json index 9640d149e..7c29e2f27 100644 --- a/config/ai/nkai/nkai-settings.json +++ b/config/ai/nkai/nkai-settings.json @@ -1,13 +1,86 @@ { - "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, + "retreatThresholdRelative" : 0.3, + "retreatThresholdAbsolute" : 10000, + "safeAttackRatio" : 1.1, + "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, + "retreatThresholdRelative" : 0.3, + "retreatThresholdAbsolute" : 10000, + "safeAttackRatio" : 1.1, + "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, + "retreatThresholdRelative" : 0.3, + "retreatThresholdAbsolute" : 10000, + "safeAttackRatio" : 1.1, + "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, + "retreatThresholdRelative" : 0.3, + "retreatThresholdAbsolute" : 10000, + "safeAttackRatio" : 1.1, + "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, + "retreatThresholdRelative" : 0.3, + "retreatThresholdAbsolute" : 10000, + "safeAttackRatio" : 1.1, + "useFuzzy" : false + } } \ No newline at end of file