From bcf8db3d05b676add8d4d07494dff0db32dff614 Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sat, 15 May 2021 22:02:52 +0300 Subject: [PATCH] Nullkiller: startup scripts --- AI/Nullkiller/AIhelper.cpp | 5 + AI/Nullkiller/AIhelper.h | 1 + .../Behaviors/RecruitHeroBehavior.cpp | 125 +++++++++++++++++- AI/Nullkiller/Behaviors/RecruitHeroBehavior.h | 11 ++ AI/Nullkiller/Engine/Nullkiller.cpp | 5 + AI/Nullkiller/Engine/Nullkiller.h | 1 + AI/Nullkiller/Engine/PriorityEvaluator.cpp | 10 +- AI/Nullkiller/Goals/AbstractGoal.h | 3 +- AI/Nullkiller/Goals/ExecuteHeroChain.cpp | 56 ++++++++ AI/Nullkiller/Goals/ExecuteHeroChain.h | 21 +++ AI/Nullkiller/HeroManager.cpp | 5 + AI/Nullkiller/HeroManager.h | 2 + AI/Nullkiller/VCAI.cpp | 2 +- 13 files changed, 238 insertions(+), 9 deletions(-) diff --git a/AI/Nullkiller/AIhelper.cpp b/AI/Nullkiller/AIhelper.cpp index 333e9a75e..72402dd59 100644 --- a/AI/Nullkiller/AIhelper.cpp +++ b/AI/Nullkiller/AIhelper.cpp @@ -215,4 +215,9 @@ void AIhelper::updateHeroRoles() float AIhelper::evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const { return heroManager->evaluateSecSkill(skill, hero); +} + +float AIhelper::evaluateHero(const CGHeroInstance * hero) const +{ + return heroManager->evaluateHero(hero); } \ No newline at end of file diff --git a/AI/Nullkiller/AIhelper.h b/AI/Nullkiller/AIhelper.h index 1b522a0fd..154536097 100644 --- a/AI/Nullkiller/AIhelper.h +++ b/AI/Nullkiller/AIhelper.h @@ -85,6 +85,7 @@ public: int selectBestSkill(const HeroPtr & hero, const std::vector & skills) const override; void updateHeroRoles() override; float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const override; + float evaluateHero(const CGHeroInstance * hero) const override; private: bool notifyGoalCompleted(Goals::TSubgoal goal) override; diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp index b5a63db72..5f448b3ac 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp @@ -13,6 +13,7 @@ #include "../AIhelper.h" #include "../AIUtility.h" #include "../Goals/RecruitHero.h" +#include "../Goals/ExecuteHeroChain.h" #include "lib/mapping/CMap.h" //for victory conditions #include "lib/CPathfinder.h" @@ -33,13 +34,133 @@ Goals::TGoalVec RecruitHeroBehavior::getTasks() if(ai->canRecruitAnyHero()) { - if(cb->getDate(Date::DAY) == 1 - || cb->getHeroesInfo().size() < cb->getTownsInfo().size() + 1 + if(cb->getHeroesInfo().size() < cb->getTownsInfo().size() + 1 || cb->getResourceAmount(Res::GOLD) > 10000) { tasks.push_back(Goals::sptr(Goals::RecruitHero())); } } + return tasks; +} + +std::string StartupBehavior::toString() const +{ + return "Startup"; +} + +const AIPath getShortestPath(const CGTownInstance * town, const std::vector & paths) +{ + auto shortestPath = *vstd::minElementByFun(paths, [town](const AIPath & path) -> float + { + if(town->garrisonHero && path.targetHero == town->garrisonHero.get()) + return 1; + + return path.movementCost(); + }); + + return shortestPath; +} + +const CGHeroInstance * getNearestHero(const CGTownInstance * town) +{ + auto paths = ai->ah->getPathsToTile(town->visitablePos()); + + if(paths.empty()) + return nullptr; + + auto shortestPath = getShortestPath(town, paths); + + if(shortestPath.nodes.size() > 1 + || shortestPath.targetHero->visitablePos().dist2dSQ(town->visitablePos()) > 4 + || town->garrisonHero && shortestPath.targetHero == town->garrisonHero.get()) + return nullptr; + + return shortestPath.targetHero; +} + +Goals::TGoalVec StartupBehavior::getTasks() +{ + Goals::TGoalVec tasks; + auto towns = cb->getTownsInfo(); + + if(!towns.size()) + return tasks; + + const CGTownInstance * startupTown = towns.front(); + bool canRecruitHero = ai->canRecruitAnyHero(startupTown); + + if(towns.size() > 1) + { + startupTown = *vstd::maxElementByFun(towns, [](const CGTownInstance * town) -> float + { + auto closestHero = getNearestHero(town); + + if(!closestHero) + return 0; + + return ai->ah->evaluateHero(closestHero); + }); + } + + auto closestHero = getNearestHero(startupTown); + + if(closestHero) + { + if(!startupTown->visitingHero) + { + if(ai->ah->howManyReinforcementsCanGet(startupTown->getUpperArmy(), closestHero) > 200) + { + auto paths = ai->ah->getPathsToTile(startupTown->visitablePos()); + + if(paths.size()) + { + auto path = getShortestPath(startupTown, paths); + + tasks.push_back(Goals::sptr(Goals::ExecuteHeroChain(path, startupTown).setpriority(100))); + } + } + } + else + { + auto visitingHero = startupTown->visitingHero.get(); + auto visitingHeroScore = ai->ah->evaluateHero(visitingHero); + + if(startupTown->garrisonHero) + { + auto garrisonHero = startupTown->garrisonHero.get(); + auto garrisonHeroScore = ai->ah->evaluateHero(garrisonHero); + + if(garrisonHeroScore > visitingHeroScore) + { + if(ai->ah->howManyReinforcementsCanGet(garrisonHero, visitingHero) > 200) + tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, garrisonHero).setpriority(100))); + } + else + { + tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, visitingHero).setpriority(100))); + } + } + else if(canRecruitHero) + { + tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, visitingHero).setpriority(100))); + } + } + } + + if(tasks.empty() && canRecruitHero && !startupTown->visitingHero) + { + tasks.push_back(Goals::sptr(Goals::RecruitHero())); + } + + if(tasks.empty() && towns.size()) + { + for(const CGTownInstance * town : towns) + { + if(town->garrisonHero) + tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(town, nullptr).setpriority(0.0001f))); + } + } + return tasks; } \ No newline at end of file diff --git a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.h b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.h index 62c5543ba..9fbedb0a7 100644 --- a/AI/Nullkiller/Behaviors/RecruitHeroBehavior.h +++ b/AI/Nullkiller/Behaviors/RecruitHeroBehavior.h @@ -24,3 +24,14 @@ public: virtual std::string toString() const override; }; +class StartupBehavior : public Behavior +{ +public: + StartupBehavior() + { + } + + virtual Goals::TGoalVec getTasks() override; + virtual std::string toString() const override; +}; + diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 4ac70c210..0ee370727 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -86,6 +86,11 @@ void Nullkiller::makeTurn() choseBestTask(std::make_shared()) }; + if(cb->getDate(Date::DAY) == 1) + { + bestTasks.push_back(choseBestTask(std::make_shared())); + } + Goals::TSubgoal bestTask = choseBestTask(bestTasks); if(bestTask->invalid()) diff --git a/AI/Nullkiller/Engine/Nullkiller.h b/AI/Nullkiller/Engine/Nullkiller.h index 253ea8e98..0e8bd486e 100644 --- a/AI/Nullkiller/Engine/Nullkiller.h +++ b/AI/Nullkiller/Engine/Nullkiller.h @@ -20,6 +20,7 @@ public: bool isActive(const CGHeroInstance * hero) const { return activeHero.h == hero; } void setActive(const HeroPtr & hero) { activeHero = hero; } void lockHero(const HeroPtr & hero) { lockedHeroes.insert(hero); } + void unlockHero(const HeroPtr & hero) { lockedHeroes.erase(hero); } private: void resetAiState(); diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index f3d386818..447343be8 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -176,9 +176,9 @@ float getSkillReward(const CGObjectInstance * target, const CGHeroInstance * her case Obj::MARLETTO_TOWER: case Obj::MERCENARY_CAMP: case Obj::SHRINE_OF_MAGIC_GESTURE: + case Obj::SHRINE_OF_MAGIC_INCANTATION: return 1; case Obj::ARENA: - case Obj::SHRINE_OF_MAGIC_INCANTATION: case Obj::SHRINE_OF_MAGIC_THOUGHT: return 2; case Obj::LIBRARY_OF_ENLIGHTENMENT: @@ -240,6 +240,9 @@ int32_t getGoldReward(const CGObjectInstance * target, const CGHeroInstance * he /// importance float PriorityEvaluator::evaluate(Goals::TSubgoal task) { + if(task->priority > 0) + return task->priority; + auto heroPtr = task->hero; if(!heroPtr.validAndSet()) @@ -263,10 +266,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task) uint64_t danger = task->evaluationContext.danger; double result = 0; int rewardType = (goldReward > 0 ? 1 : 0) + (armyReward > 0 ? 1 : 0) + (skillReward > 0 ? 1 : 0); - - if(day == 1) - goldReward *= 2; - + try { armyLossPersentageVariable->setValue(armyLossPersentage); diff --git a/AI/Nullkiller/Goals/AbstractGoal.h b/AI/Nullkiller/Goals/AbstractGoal.h index adb928393..551dc3bc4 100644 --- a/AI/Nullkiller/Goals/AbstractGoal.h +++ b/AI/Nullkiller/Goals/AbstractGoal.h @@ -65,7 +65,8 @@ namespace Goals BUILD_BOAT, COMPLETE_QUEST, ADVENTURE_SPELL_CAST, - EXECUTE_HERO_CHAIN + EXECUTE_HERO_CHAIN, + EXCHANGE_SWAP_TOWN_HEROES }; class DLL_EXPORT TSubgoal : public std::shared_ptr diff --git a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp index 915b1995e..b11b2efdd 100644 --- a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp +++ b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp @@ -110,3 +110,59 @@ std::string ExecuteHeroChain::completeMessage() const { return "Hero chain completed"; } + +ExchangeSwapTownHeroes::ExchangeSwapTownHeroes(const CGTownInstance * town, const CGHeroInstance * garrisonHero) + :CGoal(Goals::EXCHANGE_SWAP_TOWN_HEROES), town(town), garrisonHero(garrisonHero) +{ +} + +std::string ExchangeSwapTownHeroes::name() const +{ + return "Exchange and swap heroes of " + town->name; +} + +std::string ExchangeSwapTownHeroes::completeMessage() const +{ + return "Exchange and swap heroes of " + town->name + " compelete"; +} + +bool ExchangeSwapTownHeroes::operator==(const ExchangeSwapTownHeroes & other) const +{ + return town == other.town; +} + +TSubgoal ExchangeSwapTownHeroes::whatToDoToAchieve() +{ + return iAmElementar(); +} + +void ExchangeSwapTownHeroes::accept(VCAI * ai) +{ + if(!garrisonHero && town->garrisonHero && town->visitingHero) + throw cannotFulfillGoalException("Invalid configuration. Garrison hero is null."); + + if(!garrisonHero) + { + if(!town->garrisonHero) + throw cannotFulfillGoalException("Invalid configuration. There is no hero in town garrison."); + + cb->swapGarrisonHero(town); + ai->nullkiller->unlockHero(town->visitingHero.get()); + logAi->debug("Extracted hero %s from garrison of %s", town->visitingHero->name, town->name); + + return; + } + + if(town->visitingHero && town->visitingHero.get() != garrisonHero) + cb->swapGarrisonHero(town); + + ai->moveHeroToTile(town->visitablePos(), garrisonHero); + + cb->swapGarrisonHero(town); // selected hero left in garrison with strongest army + ai->nullkiller->lockHero(town->garrisonHero.get()); + + if(town->visitingHero) + ai->nullkiller->unlockHero(town->visitingHero.get()); + + logAi->debug("Put hero %s to garrison of %s", town->garrisonHero->name, town->name); +} \ No newline at end of file diff --git a/AI/Nullkiller/Goals/ExecuteHeroChain.h b/AI/Nullkiller/Goals/ExecuteHeroChain.h index 410c7254e..f6b1be6a4 100644 --- a/AI/Nullkiller/Goals/ExecuteHeroChain.h +++ b/AI/Nullkiller/Goals/ExecuteHeroChain.h @@ -33,4 +33,25 @@ namespace Goals std::string completeMessage() const override; virtual bool operator==(const ExecuteHeroChain & other) const override; }; + + class DLL_EXPORT ExchangeSwapTownHeroes : public CGoal + { + private: + const CGTownInstance * town; + const CGHeroInstance * garrisonHero; + + public: + ExchangeSwapTownHeroes(const CGTownInstance * town, const CGHeroInstance * garrisonHero = nullptr); + + TGoalVec getAllPossibleSubgoals() override + { + return TGoalVec(); + } + + TSubgoal whatToDoToAchieve() override; + void accept(VCAI * ai) override; + std::string name() const override; + std::string completeMessage() const override; + virtual bool operator==(const ExchangeSwapTownHeroes & other) const override; + }; } diff --git a/AI/Nullkiller/HeroManager.cpp b/AI/Nullkiller/HeroManager.cpp index d0f5531ee..8a574222c 100644 --- a/AI/Nullkiller/HeroManager.cpp +++ b/AI/Nullkiller/HeroManager.cpp @@ -163,6 +163,11 @@ int HeroManager::selectBestSkill(const HeroPtr & hero, const std::vector scoreMap) :scoreMap(scoreMap) { diff --git a/AI/Nullkiller/HeroManager.h b/AI/Nullkiller/HeroManager.h index d4700a30d..4433e5776 100644 --- a/AI/Nullkiller/HeroManager.h +++ b/AI/Nullkiller/HeroManager.h @@ -35,6 +35,7 @@ public: virtual HeroRole getHeroRole(const HeroPtr & hero) const = 0; virtual void updateHeroRoles() = 0; virtual float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const = 0; + virtual float evaluateHero(const CGHeroInstance * hero) const = 0; }; class DLL_EXPORT ISecondarySkillRule @@ -72,6 +73,7 @@ public: int selectBestSkill(const HeroPtr & hero, const std::vector & skills) const override; void updateHeroRoles() override; float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const override; + float evaluateHero(const CGHeroInstance * hero) const override; private: float evaluateFightingStrength(const CGHeroInstance * hero) const; diff --git a/AI/Nullkiller/VCAI.cpp b/AI/Nullkiller/VCAI.cpp index b83b398f6..bd867b2ba 100644 --- a/AI/Nullkiller/VCAI.cpp +++ b/AI/Nullkiller/VCAI.cpp @@ -1077,7 +1077,7 @@ void VCAI::moveCreaturesToHero(const CGTownInstance * t) { if(t->visitingHero && t->armedGarrison() && t->visitingHero->tempOwner == t->tempOwner) { - pickBestCreatures(t->visitingHero, t); + pickBestCreatures(t->visitingHero, t->getUpperArmy()); } }