From ebe155fa95af9fb27c4ebab0e5e5ffd88d5feabb Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sun, 24 Sep 2023 13:07:42 +0300 Subject: [PATCH] NKAI: mana recovery --- AI/Nullkiller/Analyzers/HeroManager.cpp | 35 ++++++++++ AI/Nullkiller/Analyzers/HeroManager.h | 2 + AI/Nullkiller/Analyzers/ObjectClusterizer.cpp | 22 +++--- .../Behaviors/StayAtTownBehavior.cpp | 70 +++++++++++++++++++ AI/Nullkiller/Behaviors/StayAtTownBehavior.h | 39 +++++++++++ AI/Nullkiller/CMakeLists.txt | 4 ++ AI/Nullkiller/Engine/Nullkiller.cpp | 4 +- AI/Nullkiller/Engine/PriorityEvaluator.cpp | 26 +++++++ AI/Nullkiller/Engine/PriorityEvaluator.h | 1 + AI/Nullkiller/Goals/AbstractGoal.h | 4 +- AI/Nullkiller/Goals/StayAtTown.cpp | 52 ++++++++++++++ AI/Nullkiller/Goals/StayAtTown.h | 36 ++++++++++ AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 4 +- AI/Nullkiller/Pathfinding/AINodeStorage.h | 1 + 14 files changed, 289 insertions(+), 11 deletions(-) create mode 100644 AI/Nullkiller/Behaviors/StayAtTownBehavior.cpp create mode 100644 AI/Nullkiller/Behaviors/StayAtTownBehavior.h create mode 100644 AI/Nullkiller/Goals/StayAtTown.cpp create mode 100644 AI/Nullkiller/Goals/StayAtTown.h diff --git a/AI/Nullkiller/Analyzers/HeroManager.cpp b/AI/Nullkiller/Analyzers/HeroManager.cpp index b896ab728..4397620b6 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.cpp +++ b/AI/Nullkiller/Analyzers/HeroManager.cpp @@ -190,6 +190,41 @@ bool HeroManager::heroCapReached() const || heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP); } +float HeroManager::getMagicStrength(const CGHeroInstance * hero) const +{ + auto hasFly = hero->spellbookContainsSpell(SpellID::FLY); + auto hasTownPortal = hero->spellbookContainsSpell(SpellID::TOWN_PORTAL); + auto manaLimit = hero->manaLimit(); + auto spellPower = hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER); + auto hasEarth = hero->getSpellSchoolLevel(SpellID(SpellID::TOWN_PORTAL).toSpell()) > 0; + + auto score = 0.0f; + + for(auto spellId : hero->getSpellsInSpellbook()) + { + auto spell = spellId.toSpell(); + auto schoolLevel = hero->getSpellSchoolLevel(spell); + + score += (spell->getLevel() + 1) * (schoolLevel + 1) * 0.05f; + } + + vstd::amin(score, 1); + + score *= std::min(1.0f, spellPower / 10.0f); + + if(hasFly) + score += 0.3f; + + if(hasTownPortal && hasEarth) + score += 0.6f; + + vstd::amin(score, 1); + + score *= std::min(1.0f, manaLimit / 100.0f); + + return std::min(score, 1.0f); +} + bool HeroManager::canRecruitHero(const CGTownInstance * town) const { if(!town) diff --git a/AI/Nullkiller/Analyzers/HeroManager.h b/AI/Nullkiller/Analyzers/HeroManager.h index 1009fd31e..a7744ad1f 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.h +++ b/AI/Nullkiller/Analyzers/HeroManager.h @@ -34,6 +34,7 @@ public: virtual bool heroCapReached() const = 0; virtual const CGHeroInstance * findHeroWithGrail() const = 0; virtual const CGHeroInstance * findWeakHeroToDismiss(uint64_t armyLimit) const = 0; + virtual float getMagicStrength(const CGHeroInstance * hero) const = 0; }; class DLL_EXPORT ISecondarySkillRule @@ -76,6 +77,7 @@ public: bool heroCapReached() const override; const CGHeroInstance * findHeroWithGrail() const override; const CGHeroInstance * findWeakHeroToDismiss(uint64_t armyLimit) const override; + float getMagicStrength(const CGHeroInstance * hero) const override; private: float evaluateFightingStrength(const CGHeroInstance * hero) const; diff --git a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp index 5e2b41977..a228f1b4d 100644 --- a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp +++ b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp @@ -94,16 +94,22 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons { for(auto node = path.nodes.rbegin(); node != path.nodes.rend(); node++) { - auto guardPos = ai->cb->getGuardingCreaturePosition(node->coord); - auto blockers = ai->cb->getVisitableObjs(node->coord); - - if(guardPos.valid()) - { - auto guard = ai->cb->getTopObj(ai->cb->getGuardingCreaturePosition(node->coord)); + std::vector blockers = {}; - if(guard) + if(node->layer == EPathfindingLayer::LAND || node->layer == EPathfindingLayer::SAIL) + { + auto guardPos = ai->cb->getGuardingCreaturePosition(node->coord); + + blockers = ai->cb->getVisitableObjs(node->coord); + + if(guardPos.valid()) { - blockers.insert(blockers.begin(), guard); + auto guard = ai->cb->getTopObj(ai->cb->getGuardingCreaturePosition(node->coord)); + + if(guard) + { + blockers.insert(blockers.begin(), guard); + } } } diff --git a/AI/Nullkiller/Behaviors/StayAtTownBehavior.cpp b/AI/Nullkiller/Behaviors/StayAtTownBehavior.cpp new file mode 100644 index 000000000..2f7c89b2f --- /dev/null +++ b/AI/Nullkiller/Behaviors/StayAtTownBehavior.cpp @@ -0,0 +1,70 @@ +/* +* StartupBehavior.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 "StayAtTownBehavior.h" +#include "../AIGateway.h" +#include "../AIUtility.h" +#include "../Goals/StayAtTown.h" +#include "../Goals/Composition.h" +#include "../Goals/ExecuteHeroChain.h" +#include "lib/mapObjects/MapObjects.h" //for victory conditions +#include "../Engine/Nullkiller.h" + +namespace NKAI +{ + +using namespace Goals; + +std::string StayAtTownBehavior::toString() const +{ + return "StayAtTownBehavior"; +} + +Goals::TGoalVec StayAtTownBehavior::decompose() const +{ + Goals::TGoalVec tasks; + auto towns = cb->getTownsInfo(); + + if(!towns.size()) + return tasks; + + for(auto town : towns) + { + if(!town->hasBuilt(BuildingID::MAGES_GUILD_1)) + continue; + + auto paths = ai->nullkiller->pathfinder->getPathInfo(town->visitablePos()); + + for(auto & path : paths) + { + if(town->visitingHero && town->visitingHero.get() != path.targetHero) + continue; + + if(path.turn() == 0 && !path.getFirstBlockedAction() && path.exchangeCount <= 1) + { + if(path.targetHero->mana == path.targetHero->manaLimit()) + continue; + + Composition stayAtTown; + + stayAtTown.addNextSequence({ + sptr(ExecuteHeroChain(path)), + sptr(StayAtTown(town, path)) + }); + + tasks.push_back(sptr(stayAtTown)); + } + } + } + + return tasks; +} + +} diff --git a/AI/Nullkiller/Behaviors/StayAtTownBehavior.h b/AI/Nullkiller/Behaviors/StayAtTownBehavior.h new file mode 100644 index 000000000..260cf136a --- /dev/null +++ b/AI/Nullkiller/Behaviors/StayAtTownBehavior.h @@ -0,0 +1,39 @@ +/* +* StayAtTownBehavior.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 "lib/VCMI_Lib.h" +#include "../Goals/CGoal.h" +#include "../AIUtility.h" + +namespace NKAI +{ +namespace Goals +{ + class StayAtTownBehavior : public CGoal + { + public: + StayAtTownBehavior() + :CGoal(STAY_AT_TOWN_BEHAVIOR) + { + } + + virtual TGoalVec decompose() const override; + virtual std::string toString() const override; + + virtual bool operator==(const StayAtTownBehavior & other) const override + { + return true; + } + }; +} + + +} diff --git a/AI/Nullkiller/CMakeLists.txt b/AI/Nullkiller/CMakeLists.txt index a22641684..042cb5a0d 100644 --- a/AI/Nullkiller/CMakeLists.txt +++ b/AI/Nullkiller/CMakeLists.txt @@ -35,6 +35,7 @@ set(Nullkiller_SRCS Goals/ExecuteHeroChain.cpp Goals/ExchangeSwapTownHeroes.cpp Goals/CompleteQuest.cpp + Goals/StayAtTown.cpp Markers/ArmyUpgrade.cpp Markers/HeroExchange.cpp Markers/UnlockCluster.cpp @@ -53,6 +54,7 @@ set(Nullkiller_SRCS Behaviors/BuildingBehavior.cpp Behaviors/GatherArmyBehavior.cpp Behaviors/ClusterBehavior.cpp + Behaviors/StayAtTownBehavior.cpp Helpers/ArmyFormation.cpp AIGateway.cpp ) @@ -99,6 +101,7 @@ set(Nullkiller_HEADERS Goals/ExchangeSwapTownHeroes.h Goals/CompleteQuest.h Goals/Goals.h + Goals/StayAtTown.h Markers/ArmyUpgrade.h Markers/HeroExchange.h Markers/UnlockCluster.h @@ -117,6 +120,7 @@ set(Nullkiller_HEADERS Behaviors/BuildingBehavior.h Behaviors/GatherArmyBehavior.h Behaviors/ClusterBehavior.h + Behaviors/StayAtTownBehavior.h Helpers/ArmyFormation.h AIGateway.h ) diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index b69073f33..3d9a78c33 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -18,6 +18,7 @@ #include "../Behaviors/BuildingBehavior.h" #include "../Behaviors/GatherArmyBehavior.h" #include "../Behaviors/ClusterBehavior.h" +#include "../Behaviors/StayAtTownBehavior.h" #include "../Goals/Invalid.h" #include "../Goals/Composition.h" @@ -262,7 +263,8 @@ void Nullkiller::makeTurn() choseBestTask(sptr(CaptureObjectsBehavior()), 1), choseBestTask(sptr(ClusterBehavior()), MAX_DEPTH), choseBestTask(sptr(DefenceBehavior()), MAX_DEPTH), - choseBestTask(sptr(GatherArmyBehavior()), MAX_DEPTH) + choseBestTask(sptr(GatherArmyBehavior()), MAX_DEPTH), + choseBestTask(sptr(StayAtTownBehavior()), MAX_DEPTH) }; if(cb->getDate(Date::DAY) == 1) diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index 202644db3..1888c876a 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -22,6 +22,7 @@ #include "../../../lib/filesystem/Filesystem.h" #include "../Goals/ExecuteHeroChain.h" #include "../Goals/BuildThis.h" +#include "../Goals/StayAtTown.h" #include "../Goals/ExchangeSwapTownHeroes.h" #include "../Goals/DismissHero.h" #include "../Markers/UnlockCluster.h" @@ -309,6 +310,9 @@ uint64_t RewardEvaluator::getArmyReward( : 0; case Obj::PANDORAS_BOX: return 5000; + case Obj::MAGIC_WELL: + case Obj::MAGIC_SPRING: + return getManaRecoveryArmyReward(hero); default: return 0; } @@ -450,6 +454,11 @@ uint64_t RewardEvaluator::townArmyGrowth(const CGTownInstance * town) const return result; } +uint64_t RewardEvaluator::getManaRecoveryArmyReward(const CGHeroInstance * hero) const +{ + return ai->heroManager->getMagicStrength(hero) * 10000 * (1.0f - std::sqrt(static_cast(hero->mana) / hero->manaLimit())); +} + float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) const { if(!target) @@ -693,6 +702,22 @@ public: } }; +class StayAtTownManaRecoveryEvaluator : public IEvaluationContextBuilder +{ +public: + virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override + { + if(task->goalType != Goals::STAY_AT_TOWN) + return; + + Goals::StayAtTown & stayAtTown = dynamic_cast(*task); + + evaluationContext.armyReward += evaluationContext.evaluator.getManaRecoveryArmyReward(stayAtTown.getHero().get()); + evaluationContext.movementCostByRole[evaluationContext.heroRole] += stayAtTown.getMovementWasted(); + evaluationContext.movementCost += stayAtTown.getMovementWasted(); + } +}; + void addTileDanger(EvaluationContext & evaluationContext, const int3 & tile, uint8_t turn, uint64_t ourStrength) { HitMapInfo enemyDanger = evaluationContext.evaluator.getEnemyHeroDanger(tile, turn); @@ -998,6 +1023,7 @@ PriorityEvaluator::PriorityEvaluator(const Nullkiller * ai) evaluationContextBuilders.push_back(std::make_shared()); evaluationContextBuilders.push_back(std::make_shared()); evaluationContextBuilders.push_back(std::make_shared(ai)); + evaluationContextBuilders.push_back(std::make_shared()); } EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal) const diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.h b/AI/Nullkiller/Engine/PriorityEvaluator.h index 5ab2a6082..4853e4aed 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.h +++ b/AI/Nullkiller/Engine/PriorityEvaluator.h @@ -49,6 +49,7 @@ public: 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; + uint64_t getManaRecoveryArmyReward(const CGHeroInstance * hero) const; }; struct DLL_EXPORT EvaluationContext diff --git a/AI/Nullkiller/Goals/AbstractGoal.h b/AI/Nullkiller/Goals/AbstractGoal.h index a5b170c9f..d089083bf 100644 --- a/AI/Nullkiller/Goals/AbstractGoal.h +++ b/AI/Nullkiller/Goals/AbstractGoal.h @@ -71,7 +71,9 @@ namespace Goals ARMY_UPGRADE, DEFEND_TOWN, CAPTURE_OBJECT, - SAVE_RESOURCES + SAVE_RESOURCES, + STAY_AT_TOWN_BEHAVIOR, + STAY_AT_TOWN }; class DLL_EXPORT TSubgoal : public std::shared_ptr diff --git a/AI/Nullkiller/Goals/StayAtTown.cpp b/AI/Nullkiller/Goals/StayAtTown.cpp new file mode 100644 index 000000000..82342cb51 --- /dev/null +++ b/AI/Nullkiller/Goals/StayAtTown.cpp @@ -0,0 +1,52 @@ +/* +* ArmyUpgrade.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 "StayAtTown.h" +#include "../AIGateway.h" +#include "../Engine/Nullkiller.h" +#include "../AIUtility.h" + +namespace NKAI +{ + +using namespace Goals; + +StayAtTown::StayAtTown(const CGTownInstance * town, AIPath & path) + : ElementarGoal(Goals::STAY_AT_TOWN) +{ + sethero(path.targetHero); + settown(town); + movementWasted = static_cast(hero->movementPointsRemaining()) / hero->movementPointsLimit(!hero->boat) - path.movementCost(); + vstd::amax(movementWasted, 0); +} + +bool StayAtTown::operator==(const StayAtTown & other) const +{ + return hero == other.hero && town == other.town; +} + +std::string StayAtTown::toString() const +{ + return "Stay at town " + town->getNameTranslated() + + " hero " + hero->getNameTranslated() + + ", mana: " + std::to_string(hero->mana); +} + +void StayAtTown::accept(AIGateway * ai) +{ + if(hero->visitedTown != town) + { + logAi->error("Hero %s expected visiting town %s", hero->getNameTranslated(), town->getNameTranslated()); + } + + ai->nullkiller->lockHero(hero.get(), HeroLockedReason::DEFENCE); +} + +} diff --git a/AI/Nullkiller/Goals/StayAtTown.h b/AI/Nullkiller/Goals/StayAtTown.h new file mode 100644 index 000000000..880881386 --- /dev/null +++ b/AI/Nullkiller/Goals/StayAtTown.h @@ -0,0 +1,36 @@ +/* +* ArmyUpgrade.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 "../Goals/CGoal.h" +#include "../Pathfinding/AINodeStorage.h" +#include "../Analyzers/ArmyManager.h" +#include "../Analyzers/DangerHitMapAnalyzer.h" + +namespace NKAI +{ +namespace Goals +{ + class DLL_EXPORT StayAtTown : public ElementarGoal + { + private: + float movementWasted; + + public: + StayAtTown(const CGTownInstance * town, AIPath & path); + + virtual bool operator==(const StayAtTown & other) const override; + virtual std::string toString() const override; + void accept(AIGateway * ai) override; + float getMovementWasted() const { return movementWasted; } + }; +} + +} diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 7707033eb..067525c3e 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -279,9 +279,10 @@ void AINodeStorage::commit( #if NKAI_PATHFINDER_TRACE_LEVEL >= 2 logAi->trace( - "Commited %s -> %s, cost: %f, turn: %s, mp: %d, hero: %s, mask: %x, army: %lld", + "Commited %s -> %s, layer: %d, cost: %f, turn: %s, mp: %d, hero: %s, mask: %x, army: %lld", source->coord.toString(), destination->coord.toString(), + destination->layer, destination->getCost(), std::to_string(destination->turns), destination->moveRemains, @@ -1343,6 +1344,7 @@ void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path, int pa pathNode.coord = node->coord; pathNode.parentIndex = parentIndex; pathNode.actionIsBlocked = false; + pathNode.layer = node->layer; if(pathNode.specialAction) { diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index 71464509d..068304955 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -65,6 +65,7 @@ struct AIPathNodeInfo float cost; uint8_t turns; int3 coord; + EPathfindingLayer layer; uint64_t danger; const CGHeroInstance * targetHero; int parentIndex;