From 6ba74f02bc0581af07c80ebb16cc5d89e3a4dc56 Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sun, 11 Jun 2023 19:21:50 +0300 Subject: [PATCH] NKAI: playing around with defence --- AI/Nullkiller/Analyzers/ArmyManager.cpp | 43 ++++++++- AI/Nullkiller/Analyzers/ArmyManager.h | 9 +- .../Analyzers/DangerHitMapAnalyzer.cpp | 93 +++++++++++++++---- .../Analyzers/DangerHitMapAnalyzer.h | 13 ++- AI/Nullkiller/Behaviors/DefenceBehavior.cpp | 24 +++-- .../Behaviors/GatherArmyBehavior.cpp | 68 +++++++++++--- .../Behaviors/RecruitHeroBehavior.cpp | 21 +++++ AI/Nullkiller/Engine/PriorityEvaluator.cpp | 82 ++++++++-------- AI/Nullkiller/Engine/PriorityEvaluator.h | 1 + AI/Nullkiller/Markers/ArmyUpgrade.cpp | 7 ++ AI/Nullkiller/Markers/ArmyUpgrade.h | 1 + AI/Nullkiller/Markers/DefendTown.cpp | 4 +- AI/Nullkiller/Markers/DefendTown.h | 5 +- 13 files changed, 286 insertions(+), 85 deletions(-) diff --git a/AI/Nullkiller/Analyzers/ArmyManager.cpp b/AI/Nullkiller/Analyzers/ArmyManager.cpp index b8ce631b6..359286a02 100644 --- a/AI/Nullkiller/Analyzers/ArmyManager.cpp +++ b/AI/Nullkiller/Analyzers/ArmyManager.cpp @@ -33,6 +33,45 @@ public: } }; +void ArmyUpgradeInfo::addArmyToBuy(std::vector army) +{ + for(auto slot : army) + { + resultingArmy.push_back(slot); + + upgradeValue += slot.power; + upgradeCost += slot.creature->getFullRecruitCost() * slot.count; + } +} + +void ArmyUpgradeInfo::addArmyToGet(std::vector army) +{ + for(auto slot : army) + { + resultingArmy.push_back(slot); + + upgradeValue += slot.power; + } +} + +std::vector ArmyManager::toSlotInfo(std::vector army) const +{ + std::vector result; + + for(auto i : army) + { + SlotInfo slot; + + slot.creature = VLC->creh->objects[i.cre->getId()]; + slot.count = i.count; + slot.power = evaluateStackPower(i.cre, i.count); + + result.push_back(slot); + } + + return result; +} + uint64_t ArmyManager::howManyReinforcementsCanGet(const CGHeroInstance * hero, const CCreatureSet * source) const { return howManyReinforcementsCanGet(hero, hero, source); @@ -136,7 +175,7 @@ std::vector ArmyManager::getBestArmy(const IBonusBearer * armyCarrier, { if(vstd::contains(allowedFactions, slot.creature->getFaction())) { - auto slotID = newArmyInstance.getSlotFor(slot.creature); + auto slotID = newArmyInstance.getSlotFor(slot.creature->getId()); if(slotID.validSlot()) { @@ -319,7 +358,7 @@ ui64 ArmyManager::howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, return newArmy > oldArmy ? newArmy - oldArmy : 0; } -uint64_t ArmyManager::evaluateStackPower(const CCreature * creature, int count) const +uint64_t ArmyManager::evaluateStackPower(const Creature * creature, int count) const { return creature->getAIValue() * count; } diff --git a/AI/Nullkiller/Analyzers/ArmyManager.h b/AI/Nullkiller/Analyzers/ArmyManager.h index 10848e0d6..1617bd1bd 100644 --- a/AI/Nullkiller/Analyzers/ArmyManager.h +++ b/AI/Nullkiller/Analyzers/ArmyManager.h @@ -34,6 +34,9 @@ struct ArmyUpgradeInfo std::vector resultingArmy; uint64_t upgradeValue = 0; TResources upgradeCost; + + void addArmyToBuy(std::vector army); + void addArmyToGet(std::vector army); }; class DLL_EXPORT IArmyManager //: public: IAbstractManager @@ -57,6 +60,7 @@ public: virtual std::vector getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const = 0; virtual std::vector::iterator getWeakestCreature(std::vector & army) const = 0; virtual std::vector getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const = 0; + virtual std::vector toSlotInfo(std::vector creatures) const = 0; virtual std::vector getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const = 0; virtual std::vector getArmyAvailableToBuy( @@ -65,7 +69,7 @@ public: TResources availableRes, uint8_t turn = 0) const = 0; - virtual uint64_t evaluateStackPower(const CCreature * creature, int count) const = 0; + virtual uint64_t evaluateStackPower(const Creature * creature, int count) const = 0; virtual SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const = 0; virtual ArmyUpgradeInfo calculateCreaturesUpgrade( const CCreatureSet * army, @@ -99,6 +103,7 @@ public: std::vector getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const override; std::vector::iterator getWeakestCreature(std::vector & army) const override; std::vector getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const override; + std::vector toSlotInfo(std::vector creatures) const override; std::vector getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const override; std::vector getArmyAvailableToBuy( @@ -108,7 +113,7 @@ public: uint8_t turn = 0) const override; std::shared_ptr getArmyAvailableToBuyAsCCreatureSet(const CGDwelling * dwelling, TResources availableRes) const override; - uint64_t evaluateStackPower(const CCreature * creature, int count) const override; + uint64_t evaluateStackPower(const Creature * creature, int count) const override; SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const override; ArmyUpgradeInfo calculateCreaturesUpgrade( const CCreatureSet * army, diff --git a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp index 0124b0c27..7bd330137 100644 --- a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp +++ b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.cpp @@ -17,6 +17,11 @@ namespace NKAI HitMapInfo HitMapInfo::NoTreat; +double HitMapInfo::value() const +{ + return danger / std::sqrt(turn / 3.0f + 1); +} + void DangerHitMapAnalyzer::updateHitMap() { if(hitMapUpToDate) @@ -29,8 +34,12 @@ void DangerHitMapAnalyzer::updateHitMap() auto cb = ai->cb.get(); auto mapSize = ai->cb->getMapSize(); - hitMap.resize(boost::extents[mapSize.x][mapSize.y][mapSize.z]); + + if(hitMap.shape()[0] != mapSize.x || hitMap.shape()[1] != mapSize.y || hitMap.shape()[2] != mapSize.z) + hitMap.resize(boost::extents[mapSize.x][mapSize.y][mapSize.z]); + enemyHeroAccessibleObjects.clear(); + townTreats.clear(); std::map> heroes; @@ -67,29 +76,26 @@ void DangerHitMapAnalyzer::updateHitMap() if(path.getFirstBlockedAction()) continue; - auto tileDanger = path.getHeroStrength(); - auto turn = path.turn(); auto & node = hitMap[pos.x][pos.y][pos.z]; - auto newMaxDanger = tileDanger / std::sqrt(turn / 3.0f + 1); - auto currentMaxDanger = node.maximumDanger.danger / std::sqrt(node.maximumDanger.turn / 3.0f + 1); + HitMapInfo newTreat; - if(newMaxDanger > currentMaxDanger) + newTreat.hero = path.targetHero; + newTreat.turn = path.turn(); + newTreat.danger = path.getHeroStrength(); + + if(newTreat.value() > node.maximumDanger.value()) { - node.maximumDanger.danger = tileDanger; - node.maximumDanger.turn = turn; - node.maximumDanger.hero = path.targetHero; + node.maximumDanger = newTreat; } - if(turn < node.fastestDanger.turn - || (turn == node.fastestDanger.turn && node.fastestDanger.danger < tileDanger)) + if(newTreat.turn < node.fastestDanger.turn + || (newTreat.turn == node.fastestDanger.turn && node.fastestDanger.danger < newTreat.danger)) { - node.fastestDanger.danger = tileDanger; - node.fastestDanger.turn = turn; - node.fastestDanger.hero = path.targetHero; + node.fastestDanger = newTreat; } - if(turn == 0) + if(newTreat.turn == 0) { auto objects = cb->getVisitableObjs(pos, false); @@ -97,6 +103,26 @@ void DangerHitMapAnalyzer::updateHitMap() { if(cb->getPlayerRelations(obj->tempOwner, ai->playerID) != PlayerRelations::ENEMIES) enemyHeroAccessibleObjects[path.targetHero].insert(obj); + + if(obj->ID == Obj::TOWN && obj->getOwner() == ai->playerID) + { + auto & treats = townTreats[obj->id]; + auto treat = std::find_if(treats.begin(), treats.end(), [&](const HitMapInfo & i) -> bool + { + return i.hero.hid == path.targetHero->id; + }); + + if(treat == treats.end()) + { + treats.emplace_back(); + treat = std::prev(treats.end(), 1); + } + + if(newTreat.value() > treat->value()) + { + *treat = newTreat; + } + } } } } @@ -115,7 +141,8 @@ void DangerHitMapAnalyzer::calculateTileOwners() auto cb = ai->cb.get(); auto mapSize = ai->cb->getMapSize(); - tileOwners.resize(boost::extents[mapSize.x][mapSize.y][mapSize.z]); + if(hitMap.shape()[0] != mapSize.x || hitMap.shape()[1] != mapSize.y || hitMap.shape()[2] != mapSize.z) + hitMap.resize(boost::extents[mapSize.x][mapSize.y][mapSize.z]); std::map townHeroes; std::map heroTownMap; @@ -157,6 +184,7 @@ void DangerHitMapAnalyzer::calculateTileOwners() float ourDistance = std::numeric_limits::max(); float enemyDistance = std::numeric_limits::max(); const CGTownInstance * enemyTown = nullptr; + const CGTownInstance * ourTown = nullptr; for(AIPath & path : ai->pathfinder->getPathInfo(pos)) { @@ -167,7 +195,11 @@ void DangerHitMapAnalyzer::calculateTileOwners() if(town->getOwner() == ai->playerID) { - vstd::amin(ourDistance, path.movementCost()); + if(ourDistance > path.movementCost()) + { + ourDistance = path.movementCost(); + ourTown = town; + } } else { @@ -181,19 +213,40 @@ void DangerHitMapAnalyzer::calculateTileOwners() if(ourDistance == enemyDistance) { - tileOwners[pos.x][pos.y][pos.z] = PlayerColor::NEUTRAL; + hitMap[pos.x][pos.y][pos.z].closestTown = nullptr; } else if(!enemyTown || ourDistance < enemyDistance) { - tileOwners[pos.x][pos.y][pos.z] = ai->playerID; + hitMap[pos.x][pos.y][pos.z].closestTown = ourTown; } else { - tileOwners[pos.x][pos.y][pos.z] = enemyTown->getOwner(); + hitMap[pos.x][pos.y][pos.z].closestTown = enemyTown; } }); } +const std::vector & DangerHitMapAnalyzer::getTownTreats(const CGTownInstance * town) const +{ + static const std::vector empty = {}; + + auto result = townTreats.find(town->id); + + return result == townTreats.end() ? empty : result->second; +} + +PlayerColor DangerHitMapAnalyzer::getTileOwner(const int3 & tile) const +{ + auto town = hitMap[tile.x][tile.y][tile.z].closestTown; + + return town ? town->getOwner() : PlayerColor::NEUTRAL; +} + +const CGTownInstance * DangerHitMapAnalyzer::getClosestTown(const int3 & tile) const +{ + return hitMap[tile.x][tile.y][tile.z].closestTown; +} + uint64_t DangerHitMapAnalyzer::enemyCanKillOurHeroesAlongThePath(const AIPath & path) const { int3 tile = path.targetTile(); diff --git a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.h b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.h index e96987b33..4fed77412 100644 --- a/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.h +++ b/AI/Nullkiller/Analyzers/DangerHitMapAnalyzer.h @@ -35,6 +35,8 @@ struct HitMapInfo turn = 255; hero = HeroPtr(); } + + double value() const; }; struct HitMapNode @@ -42,6 +44,8 @@ struct HitMapNode HitMapInfo maximumDanger; HitMapInfo fastestDanger; + const CGTownInstance * closestTown = nullptr; + HitMapNode() = default; void reset() @@ -51,15 +55,19 @@ struct HitMapNode } }; +struct TileOwner +{ +}; + class DangerHitMapAnalyzer { private: boost::multi_array hitMap; - boost::multi_array tileOwners; std::map> enemyHeroAccessibleObjects; bool hitMapUpToDate = false; bool tileOwnersUpToDate = false; const Nullkiller * ai; + std::map> townTreats; public: DangerHitMapAnalyzer(const Nullkiller * ai) :ai(ai) {} @@ -72,6 +80,9 @@ public: const std::set & getOneTurnAccessibleObjects(const CGHeroInstance * enemy) const; void reset(); void resetTileOwners() { tileOwnersUpToDate = false; } + PlayerColor getTileOwner(const int3 & tile) const; + const CGTownInstance * getClosestTown(const int3 & tile) const; + const std::vector & getTownTreats(const CGTownInstance * town) const; }; } diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index 0f971d290..229cd9121 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -54,7 +54,9 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta logAi->trace("Evaluating defence for %s", town->getNameTranslated()); auto treatNode = ai->nullkiller->dangerHitMap->getObjectTreat(town); - auto treats = { treatNode.maximumDanger, treatNode.fastestDanger }; + std::vector treats = ai->nullkiller->dangerHitMap->getTownTreats(town); + + treats.push_back(treatNode.fastestDanger); // no guarantee that fastest danger will be there int dayOfWeek = cb->getDate(Date::DAY_OF_WEEK); @@ -131,14 +133,24 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta if(treat.hero.validAndSet() && treat.turn <= 1 - && (treat.danger == treatNode.maximumDanger.danger || treat.turn < treatNode.maximumDanger.turn) - && isSafeToVisit(path.targetHero, path.heroArmy, treat.danger)) + && (treat.danger == treatNode.maximumDanger.danger || treat.turn < treatNode.maximumDanger.turn)) { - Composition composition; + auto heroCapturingPaths = ai->nullkiller->pathfinder->getPathInfo(treat.hero->visitablePos()); + auto goals = CaptureObjectsBehavior::getVisitGoals(heroCapturingPaths, treat.hero.get()); - composition.addNext(DefendTown(town, treat, path)).addNext(CaptureObject(treat.hero.get())); + for(int i = 0; i < heroCapturingPaths.size(); i++) + { + AIPath & path = heroCapturingPaths[i]; + TSubgoal goal = goals[i]; - tasks.push_back(Goals::sptr(composition)); + if(!goal || goal->invalid() || !goal->isElementar()) continue; + + Composition composition; + + composition.addNext(DefendTown(town, treat, path, true)).addNext(goal); + + tasks.push_back(Goals::sptr(composition)); + } } bool treatIsWeak = path.getHeroStrength() / (float)treat.danger > TREAT_IGNORE_RATIO; diff --git a/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp b/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp index 66416df54..e88ab5928 100644 --- a/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp +++ b/AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp @@ -12,6 +12,7 @@ #include "../Engine/Nullkiller.h" #include "../Goals/ExecuteHeroChain.h" #include "../Goals/Composition.h" +#include "../Goals/RecruitHero.h" #include "../Markers/HeroExchange.h" #include "../Markers/ArmyUpgrade.h" #include "GatherArmyBehavior.h" @@ -240,12 +241,22 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader) logAi->trace("Found %d paths", paths.size()); #endif + bool hasMainAround = false; + + for(const AIPath & path : paths) + { + auto heroRole = ai->nullkiller->heroManager->getHeroRole(path.targetHero); + + if(heroRole == HeroRole::MAIN && path.turn() < SCOUT_TURN_DISTANCE_LIMIT) + hasMainAround = true; + } + for(const AIPath & path : paths) { #if NKAI_TRACE_LEVEL >= 2 logAi->trace("Path found %s, %s, %lld", path.toString(), path.targetHero->getObjectName(), path.heroArmy->getArmyStrength()); #endif - if(upgrader->visitingHero && upgrader->visitingHero.get() != path.targetHero) + if(upgrader->visitingHero && (upgrader->visitingHero.get() != path.targetHero || path.exchangeCount == 1)) { #if NKAI_TRACE_LEVEL >= 2 logAi->trace("Ignore path. Town has visiting hero."); @@ -283,25 +294,58 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader) auto upgrade = ai->nullkiller->armyManager->calculateCreaturesUpgrade(path.heroArmy, upgrader, availableResources); - if(!upgrader->garrisonHero && ai->nullkiller->heroManager->getHeroRole(path.targetHero) == HeroRole::MAIN) + if(!upgrader->garrisonHero + && ( + hasMainAround + || ai->nullkiller->heroManager->getHeroRole(path.targetHero) == HeroRole::MAIN)) { - upgrade.upgradeValue += - ai->nullkiller->armyManager->howManyReinforcementsCanGet( + ArmyUpgradeInfo armyToGetOrBuy; + + armyToGetOrBuy.addArmyToGet( + ai->nullkiller->armyManager->getBestArmy( path.targetHero, path.heroArmy, - upgrader->getUpperArmy()); + upgrader->getUpperArmy())); - upgrade.upgradeValue += - ai->nullkiller->armyManager->howManyReinforcementsCanBuy( - path.heroArmy, - upgrader, - ai->nullkiller->getFreeResources(), - path.turn()); + armyToGetOrBuy.addArmyToBuy( + ai->nullkiller->armyManager->toSlotInfo( + ai->nullkiller->armyManager->getArmyAvailableToBuy( + path.heroArmy, + upgrader, + ai->nullkiller->getFreeResources(), + path.turn()))); + + upgrade.upgradeValue += armyToGetOrBuy.upgradeValue; + upgrade.upgradeCost += armyToGetOrBuy.upgradeCost; + vstd::concatenate(upgrade.resultingArmy, armyToGetOrBuy.resultingArmy); + + auto getOrBuyArmyValue = (float)armyToGetOrBuy.upgradeValue / path.getHeroStrength(); + + if(!upgrade.upgradeValue + && armyToGetOrBuy.upgradeValue > 20000 + && ai->nullkiller->heroManager->canRecruitHero(town) + && path.turn() < SCOUT_TURN_DISTANCE_LIMIT) + { + for(auto hero : cb->getAvailableHeroes(town)) + { + auto scoutReinforcement = ai->nullkiller->armyManager->howManyReinforcementsCanBuy(hero, town) + + ai->nullkiller->armyManager->howManyReinforcementsCanGet(hero, town); + + if(scoutReinforcement >= armyToGetOrBuy.upgradeValue + && ai->nullkiller->getFreeGold() >20000 + && ai->nullkiller->buildAnalyzer->getGoldPreasure() < MAX_GOLD_PEASURE) + { + Composition recruitHero; + + recruitHero.addNext(ArmyUpgrade(path.targetHero, town, armyToGetOrBuy)).addNext(RecruitHero(town, hero)); + } + } + } } auto armyValue = (float)upgrade.upgradeValue / path.getHeroStrength(); - if((armyValue < 0.1f && armyValue < 20000) || upgrade.upgradeValue < 300) // avoid small upgrades + if((armyValue < 0.1f && upgrade.upgradeValue < 20000) || upgrade.upgradeValue < 300) // avoid small upgrades { #if NKAI_TRACE_LEVEL >= 2 logAi->trace("Ignore path. Army value is too small (%f)", armyValue); diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index bbc9dd737..fdf75ba3d 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -66,6 +66,27 @@ Goals::TGoalVec RecruitHeroBehavior::decompose() const } } + int treasureSourcesCount = 0; + + for(auto obj : ai->nullkiller->objectClusterizer->getNearbyObjects()) + { + if((obj->ID == Obj::RESOURCE && obj->subID == GameResID(EGameResID::GOLD)) + || obj->ID == Obj::TREASURE_CHEST + || obj->ID == Obj::CAMPFIRE + || obj->ID == Obj::WATER_WHEEL + || obj->ID ==Obj::ARTIFACT) + { + auto tile = obj->visitablePos(); + auto closestTown = ai->nullkiller->dangerHitMap->getClosestTown(tile); + + if(town == closestTown) + treasureSourcesCount++; + } + } + + if(treasureSourcesCount < 10) + continue; + if(cb->getHeroesInfo().size() < cb->getTownsInfo().size() + 1 || (ai->nullkiller->getFreeResources()[EGameResID::GOLD] > 10000 && ai->nullkiller->buildAnalyzer->getGoldPreasure() < MAX_GOLD_PEASURE)) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 66ef95c7c..135f1451a 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -282,18 +282,6 @@ uint64_t RewardEvaluator::getArmyReward( switch(target->ID) { - case Obj::TOWN: - { - auto town = dynamic_cast(target); - auto fortLevel = town->fortLevel(); - auto booster = isAnotherAi(town, *ai->cb) ? 1 : 2; - - if(fortLevel < CGTownInstance::CITADEL) - return town->hasFort() ? booster * 500 : 0; - else - return booster * (fortLevel == CGTownInstance::CASTLE ? 5000 : 2000); - } - case Obj::HILL_FORT: return ai->armyManager->calculateCreaturesUpgrade(army, target, ai->cb->getResourceAmount()).upgradeValue; case Obj::CREATURE_BANK: @@ -440,6 +428,22 @@ float RewardEvaluator::getTotalResourceRequirementStrength(int resType) const return std::min(ratio, 1.0f); } +uint64_t RewardEvaluator::townArmyGrowth(const CGTownInstance * town) const +{ + uint64_t result = 0; + + for(auto creatureInfo : town->creatures) + { + if(creatureInfo.second.empty()) + continue; + + auto creature = creatureInfo.second.back().toCreature(); + result += creature->getAIValue() * town->getGrowthInfo(creature->getLevel() - 1).totalGrowth(); + } + + return result; +} + float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) const { if(!target) @@ -475,13 +479,22 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons case Obj::TOWN: { if(ai->buildAnalyzer->getDevelopmentInfo().empty()) - return 1; + return 10.0f; auto town = dynamic_cast(target); - auto fortLevel = town->fortLevel(); - auto booster = isAnotherAi(town, *ai->cb) ? 0.3 : 1; - if(town->hasCapitol()) return 1; + if(town->getOwner() == ai->playerID) + { + auto armyIncome = townArmyGrowth(town); + auto dailyIncome = town->dailyIncome()[EGameResID::GOLD]; + + return std::min(1.0f, std::sqrt(armyIncome / 40000.0f)) + std::min(0.3f, dailyIncome / 10000.0f); + } + + auto fortLevel = town->fortLevel(); + auto booster = isAnotherAi(town, *ai->cb) ? 0.3f : 0.7f; + + if(town->hasCapitol()) return booster; if(fortLevel < CGTownInstance::CITADEL) return booster * (town->hasFort() ? 0.6 : 0.4); @@ -690,23 +703,6 @@ void addTileDanger(EvaluationContext & evaluationContext, const int3 & tile, uin class DefendTownEvaluator : public IEvaluationContextBuilder { -private: - uint64_t townArmyIncome(const CGTownInstance * town) const - { - uint64_t result = 0; - - for(auto creatureInfo : town->creatures) - { - if(creatureInfo.second.empty()) - continue; - - auto creature = creatureInfo.second.back().toCreature(); - result += creature->getAIValue() * town->getGrowthInfo(creature->getLevel() - 1).totalGrowth(); - } - - return result; - } - public: virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override { @@ -717,10 +713,7 @@ public: const CGTownInstance * town = defendTown.town; auto & treat = defendTown.getTreat(); - auto armyIncome = townArmyIncome(town); - auto dailyIncome = town->dailyIncome()[EGameResID::GOLD]; - - auto strategicalValue = std::sqrt(armyIncome / 60000.0f) + dailyIncome / 10000.0f; + auto strategicalValue = evaluationContext.evaluator.getStrategicalValue(town); if(evaluationContext.evaluator.ai->buildAnalyzer->getDevelopmentInfo().size() == 1) vstd::amax(evaluationContext.strategicalValue, 10.0); @@ -732,9 +725,20 @@ public: multiplier /= 1.0f + treat.turn / 5.0f; - evaluationContext.armyGrowth += armyIncome * multiplier; + if(defendTown.getTurn() > 0 && defendTown.isContrAttack()) + { + auto ourSpeed = defendTown.hero->maxMovePoints(true); + auto enemySpeed = treat.hero->maxMovePoints(true); + + if(enemySpeed > ourSpeed) multiplier *= 0.7f; + } + + auto dailyIncome = town->dailyIncome()[EGameResID::GOLD]; + auto armyGrowth = evaluationContext.evaluator.townArmyGrowth(town); + + evaluationContext.armyGrowth += armyGrowth * multiplier; evaluationContext.goldReward += dailyIncome * 5 * multiplier; - evaluationContext.addNonCriticalStrategicalValue(strategicalValue * multiplier); + evaluationContext.addNonCriticalStrategicalValue(1.7f * multiplier * strategicalValue); vstd::amax(evaluationContext.danger, defendTown.getTreat().danger); addTileDanger(evaluationContext, town->visitablePos(), defendTown.getTurn(), defendTown.getDefenceStrength()); } diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index 8f7f478f7..fb8085494 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -44,6 +44,7 @@ public: int32_t getGoldReward(const CGObjectInstance * target, const CGHeroInstance * hero) const; uint64_t getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const; const HitMapInfo & getEnemyHeroDanger(const int3 & tile, uint8_t turn) const; + uint64_t townArmyGrowth(const CGTownInstance * town) const; }; struct DLL_EXPORT EvaluationContext diff --git a/AI/Nullkiller/Markers/ArmyUpgrade.cpp b/AI/Nullkiller/Markers/ArmyUpgrade.cpp index bc475583d..0f6d41090 100644 --- a/AI/Nullkiller/Markers/ArmyUpgrade.cpp +++ b/AI/Nullkiller/Markers/ArmyUpgrade.cpp @@ -28,6 +28,13 @@ ArmyUpgrade::ArmyUpgrade(const AIPath & upgradePath, const CGObjectInstance * up sethero(upgradePath.targetHero); } +ArmyUpgrade::ArmyUpgrade(const CGHeroInstance * targetMain, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade) + : CGoal(Goals::ARMY_UPGRADE), upgrader(upgrader), upgradeValue(upgrade.upgradeValue), + initialValue(targetMain->getArmyStrength()), goldCost(upgrade.upgradeCost[EGameResID::GOLD]) +{ + sethero(targetMain); +} + bool ArmyUpgrade::operator==(const ArmyUpgrade & other) const { return false; diff --git a/AI/Nullkiller/Markers/ArmyUpgrade.h b/AI/Nullkiller/Markers/ArmyUpgrade.h index df30c3588..1af41a5bf 100644 --- a/AI/Nullkiller/Markers/ArmyUpgrade.h +++ b/AI/Nullkiller/Markers/ArmyUpgrade.h @@ -27,6 +27,7 @@ namespace Goals public: ArmyUpgrade(const AIPath & upgradePath, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade); + ArmyUpgrade(const CGHeroInstance * targetMain, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade); virtual bool operator==(const ArmyUpgrade & other) const override; virtual std::string toString() const override; diff --git a/AI/Nullkiller/Markers/DefendTown.cpp b/AI/Nullkiller/Markers/DefendTown.cpp index b09952f22..096def550 100644 --- a/AI/Nullkiller/Markers/DefendTown.cpp +++ b/AI/Nullkiller/Markers/DefendTown.cpp @@ -18,8 +18,8 @@ namespace NKAI using namespace Goals; -DefendTown::DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const AIPath & defencePath) - : CGoal(Goals::DEFEND_TOWN), treat(treat), defenceArmyStrength(defencePath.getHeroStrength()), turn(defencePath.turn()) +DefendTown::DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const AIPath & defencePath, bool isContrattack) + : CGoal(Goals::DEFEND_TOWN), treat(treat), defenceArmyStrength(defencePath.getHeroStrength()), turn(defencePath.turn()), contrattack(isContrattack) { settown(town); sethero(defencePath.targetHero); diff --git a/AI/Nullkiller/Markers/DefendTown.h b/AI/Nullkiller/Markers/DefendTown.h index 083f9b07f..bfe2314d9 100644 --- a/AI/Nullkiller/Markers/DefendTown.h +++ b/AI/Nullkiller/Markers/DefendTown.h @@ -24,9 +24,10 @@ namespace Goals uint64_t defenceArmyStrength; HitMapInfo treat; uint8_t turn; + bool contrattack; public: - DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const AIPath & defencePath); + DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const AIPath & defencePath, bool isContrattack = false); DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const CGHeroInstance * defender); virtual bool operator==(const DefendTown & other) const override; @@ -37,6 +38,8 @@ namespace Goals uint64_t getDefenceStrength() const { return defenceArmyStrength; } uint8_t getTurn() const { return turn; } + + bool isContrAttack() { return contrattack; } }; }