From e6eb9ccc0375fe2a93298654bce9304021d3cc8e Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sun, 16 May 2021 14:11:35 +0300 Subject: [PATCH] Nullkiller: dismising and hiring hero in order to defend town --- .../Behaviors/CaptureObjectsBehavior.cpp | 5 +- AI/Nullkiller/Behaviors/DefenceBehavior.cpp | 53 ++++++++++++++---- AI/Nullkiller/CMakeLists.txt | 2 + AI/Nullkiller/Engine/Nullkiller.h | 1 + AI/Nullkiller/Goals/AbstractGoal.h | 3 +- AI/Nullkiller/Goals/DismissHero.cpp | 55 +++++++++++++++++++ AI/Nullkiller/Goals/DismissHero.h | 36 ++++++++++++ AI/Nullkiller/Goals/ExecuteHeroChain.cpp | 4 ++ AI/Nullkiller/VCAI.cpp | 6 +- 9 files changed, 151 insertions(+), 14 deletions(-) create mode 100644 AI/Nullkiller/Goals/DismissHero.cpp create mode 100644 AI/Nullkiller/Goals/DismissHero.h diff --git a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp index 9f8d7407a..445ffdeb5 100644 --- a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp +++ b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp @@ -134,7 +134,10 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks() way->evaluationContext.closestWayRatio = way->evaluationContext.movementCost / closestWay->evaluationContext.movementCost; - tasks.push_back(sptr(*way)); + if(way->hero && ai->nullkiller->canMove(way->hero.h)) + { + tasks.push_back(sptr(*way)); + } } } }; diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index 74e640369..0ee250ed9 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -16,6 +16,7 @@ #include "../Goals/BuyArmy.h" #include "../Goals/VisitTile.h" #include "../Goals/ExecuteHeroChain.h" +#include "../Goals/DismissHero.h" #include "../Goals/ExchangeSwapTownHeroes.h" #include "lib/mapping/CMap.h" //for victory conditions #include "lib/CPathfinder.h" @@ -109,13 +110,6 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta auto paths = ai->ah->getPathsToTile(town->visitablePos()); - if(paths.empty()) - { - logAi->debug("No ways to defend town %s", town->name); - - return; - } - for(auto & treat : treats) { logAi->debug( @@ -131,7 +125,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta { if(path.getHeroStrength() > treat.danger) { - if(dayOfWeek + treat.turn < 6 && isSafeToVisit(path.targetHero, path.heroArmy, treat.danger) + if(path.turn() <= treat.turn && dayOfWeek + treat.turn < 6 && isSafeToVisit(path.targetHero, path.heroArmy, treat.danger) || path.exchangeCount == 1 && path.turn() < treat.turn || path.turn() < treat.turn - 1) { @@ -150,7 +144,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta if(treatIsUnderControl) continue; - if(ai->canRecruitAnyHero(town)) + if(cb->getResourceAmount(Res::GOLD) > GameConstants::HERO_GOLD_COST) { auto heroesInTavern = cb->getAvailableHeroes(town); @@ -158,12 +152,49 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta { if(hero->getTotalStrength() > treat.danger) { - tasks.push_back(Goals::sptr(Goals::RecruitHero().settown(town).setobjid(hero->id.getNum()).setpriority(1))); - continue; + auto myHeroes = cb->getHeroesInfo(); + + if(cb->getHeroesInfo().size() < ALLOWED_ROAMING_HEROES) + { + logAi->debug("Hero %s can be recruited to defend %s", hero->name, town->name); + tasks.push_back(Goals::sptr(Goals::RecruitHero().settown(town).setobjid(hero->id.getNum()).setpriority(1))); + continue; + } + else + { + const CGHeroInstance * weakestHero = nullptr; + + for(auto existingHero : myHeroes) + { + if(ai->nullkiller->isHeroLocked(existingHero) + || existingHero->getArmyStrength() > hero->getArmyStrength() + || ai->ah->getHeroRole(existingHero) == HeroRole::MAIN + || existingHero->movement + || existingHero->artifactsWorn.size() > (existingHero->hasSpellbook() ? 2 : 1)) + continue; + + if(!weakestHero || weakestHero->getFightingStrength() > existingHero->getFightingStrength()) + { + weakestHero = existingHero; + } + + if(weakestHero) + { + tasks.push_back(Goals::sptr(Goals::DismissHero(weakestHero))); + } + } + } } } } + if(paths.empty()) + { + logAi->debug("No ways to defend town %s", town->name); + + continue; + } + for(AIPath & path : paths) { #if AI_TRACE_LEVEL >= 1 diff --git a/AI/Nullkiller/CMakeLists.txt b/AI/Nullkiller/CMakeLists.txt index d165a708d..5b228f196 100644 --- a/AI/Nullkiller/CMakeLists.txt +++ b/AI/Nullkiller/CMakeLists.txt @@ -30,6 +30,7 @@ set(VCAI_SRCS Goals/BuildThis.cpp Goals/Explore.cpp Goals/GatherArmy.cpp + Goals/DismissHero.cpp Goals/GatherTroops.cpp Goals/BuyArmy.cpp Goals/AdventureSpellCast.cpp @@ -96,6 +97,7 @@ set(VCAI_HEADERS Goals/BuildThis.h Goals/Explore.h Goals/GatherArmy.h + Goals/DismissHero.h Goals/GatherTroops.h Goals/BuyArmy.h Goals/AdventureSpellCast.h diff --git a/AI/Nullkiller/Engine/Nullkiller.h b/AI/Nullkiller/Engine/Nullkiller.h index 89784f1ee..404916a9e 100644 --- a/AI/Nullkiller/Engine/Nullkiller.h +++ b/AI/Nullkiller/Engine/Nullkiller.h @@ -31,6 +31,7 @@ public: void setActive(const CGHeroInstance * hero) { activeHero = hero; } void lockHero(const CGHeroInstance * hero) { lockedHeroes.insert(hero); } void unlockHero(const CGHeroInstance * hero) { lockedHeroes.erase(hero); } + bool canMove(const CGHeroInstance * hero) { return hero->movement; } private: void resetAiState(); diff --git a/AI/Nullkiller/Goals/AbstractGoal.h b/AI/Nullkiller/Goals/AbstractGoal.h index 551dc3bc4..b0ae11b07 100644 --- a/AI/Nullkiller/Goals/AbstractGoal.h +++ b/AI/Nullkiller/Goals/AbstractGoal.h @@ -66,7 +66,8 @@ namespace Goals COMPLETE_QUEST, ADVENTURE_SPELL_CAST, EXECUTE_HERO_CHAIN, - EXCHANGE_SWAP_TOWN_HEROES + EXCHANGE_SWAP_TOWN_HEROES, + DISMISS_HERO }; class DLL_EXPORT TSubgoal : public std::shared_ptr diff --git a/AI/Nullkiller/Goals/DismissHero.cpp b/AI/Nullkiller/Goals/DismissHero.cpp new file mode 100644 index 000000000..3dcbd2de5 --- /dev/null +++ b/AI/Nullkiller/Goals/DismissHero.cpp @@ -0,0 +1,55 @@ +/* +* DismissHero.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "DismissHero.h" +#include "../VCAI.h" +#include "../FuzzyHelper.h" +#include "../AIhelper.h" +#include "../../../lib/mapping/CMap.h" //for victory conditions +#include "../../../lib/CPathfinder.h" + +extern boost::thread_specific_ptr cb; +extern boost::thread_specific_ptr ai; +extern FuzzyHelper * fh; + +using namespace Goals; + +bool DismissHero::operator==(const DismissHero & other) const +{ + return hero.h == other.hero.h; +} + +TSubgoal DismissHero::whatToDoToAchieve() +{ + if(!hero.validAndSet()) + throw cannotFulfillGoalException("Invalid hero!"); + + return iAmElementar(); +} + +void DismissHero::accept(VCAI * ai) +{ + if(!hero.validAndSet()) + throw cannotFulfillGoalException("Invalid hero!"); + + cb->dismissHero(hero.h); + + throw goalFulfilledException(sptr(*this)); +} + +std::string DismissHero::name() const +{ + return "DismissHero " + hero.name; +} + +std::string DismissHero::completeMessage() const +{ + return "Hero dismissed successfully " + hero.name; +} diff --git a/AI/Nullkiller/Goals/DismissHero.h b/AI/Nullkiller/Goals/DismissHero.h new file mode 100644 index 000000000..21c35601c --- /dev/null +++ b/AI/Nullkiller/Goals/DismissHero.h @@ -0,0 +1,36 @@ +/* +* DismissHero.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" + +namespace Goals +{ + class DLL_EXPORT DismissHero : public CGoal + { + public: + DismissHero(HeroPtr hero) + : CGoal(Goals::DISMISS_HERO) + { + sethero(hero); + } + + 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 DismissHero & other) const override; + }; +} diff --git a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp index e7758a9e6..1a78c1a29 100644 --- a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp +++ b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp @@ -126,6 +126,10 @@ void ExecuteHeroChain::accept(VCAI * ai) return; } + // do not lock hero if it is simple one hero chain + if(chainPath.exchangeCount == 1) + return; + // no exception means we were not able to rich the tile ai->nullkiller->lockHero(hero.get()); blockedIndexes.insert(node.parentIndex); diff --git a/AI/Nullkiller/VCAI.cpp b/AI/Nullkiller/VCAI.cpp index c57099d05..3c277c152 100644 --- a/AI/Nullkiller/VCAI.cpp +++ b/AI/Nullkiller/VCAI.cpp @@ -2035,7 +2035,11 @@ void VCAI::tryRealize(Goals::Explore & g) void VCAI::tryRealize(Goals::RecruitHero & g) { - if(const CGTownInstance * t = findTownWithTavern()) + const CGTownInstance * t = g.town; + + if(!t) t = findTownWithTavern(); + + if(t) { recruitHero(t, true); //TODO try to free way to blocked town