diff --git a/AI/Nullkiller/AIhelper.cpp b/AI/Nullkiller/AIhelper.cpp index 8f1139705..27e484cb1 100644 --- a/AI/Nullkiller/AIhelper.cpp +++ b/AI/Nullkiller/AIhelper.cpp @@ -17,6 +17,7 @@ AIhelper::AIhelper() buildingManager.reset(new BuildingManager()); pathfindingManager.reset(new PathfindingManager()); armyManager.reset(new ArmyManager()); + heroManager.reset(new HeroManager()); } AIhelper::~AIhelper() @@ -34,6 +35,7 @@ void AIhelper::init(CPlayerSpecificInfoCallback * CB) buildingManager->init(CB); pathfindingManager->init(CB); armyManager->init(CB); + heroManager->init(CB); } void AIhelper::setAI(VCAI * AI) @@ -42,6 +44,7 @@ void AIhelper::setAI(VCAI * AI) buildingManager->setAI(AI); pathfindingManager->setAI(AI); armyManager->setAI(AI); + heroManager->setAI(AI); } bool AIhelper::getBuildingOptions(const CGTownInstance * t) @@ -187,4 +190,14 @@ std::vector::iterator AIhelper::getWeakestCreature(std::vector AIhelper::getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const { return armyManager->getSortedSlots(target, source); +} + +int AIhelper::selectBestSkill(const HeroPtr & hero, const std::vector & skills) const +{ + return heroManager->selectBestSkill(hero, skills); +} + +std::map AIhelper::getHeroRoles() const +{ + return heroManager->getHeroRoles(); } \ No newline at end of file diff --git a/AI/Nullkiller/AIhelper.h b/AI/Nullkiller/AIhelper.h index d7c9fef28..03a371e2e 100644 --- a/AI/Nullkiller/AIhelper.h +++ b/AI/Nullkiller/AIhelper.h @@ -17,6 +17,7 @@ #include "ResourceManager.h" #include "BuildingManager.h" #include "ArmyManager.h" +#include "HeroManager.h" #include "Pathfinding/PathfindingManager.h" class ResourceManager; @@ -24,7 +25,7 @@ class BuildingManager; //indirection interface for various modules -class DLL_EXPORT AIhelper : public IResourceManager, public IBuildingManager, public IPathfindingManager, public IArmyManager +class DLL_EXPORT AIhelper : public IResourceManager, public IBuildingManager, public IPathfindingManager, public IArmyManager, public IHeroManager { friend class VCAI; friend struct SetGlobalState; //mess? @@ -33,6 +34,7 @@ class DLL_EXPORT AIhelper : public IResourceManager, public IBuildingManager, pu std::shared_ptr buildingManager; std::shared_ptr pathfindingManager; std::shared_ptr armyManager; + std::shared_ptr heroManager; //TODO: vector public: AIhelper(); @@ -78,6 +80,9 @@ public: std::vector::iterator getWeakestCreature(std::vector & army) const override; std::vector getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const override; + std::map getHeroRoles() const override; + int selectBestSkill(const HeroPtr & hero, const std::vector & skills) const override; + private: bool notifyGoalCompleted(Goals::TSubgoal goal) override; diff --git a/AI/Nullkiller/CMakeLists.txt b/AI/Nullkiller/CMakeLists.txt index d159394ec..9c52e075b 100644 --- a/AI/Nullkiller/CMakeLists.txt +++ b/AI/Nullkiller/CMakeLists.txt @@ -23,6 +23,7 @@ set(VCAI_SRCS AIUtility.cpp AIhelper.cpp ArmyManager.cpp + HeroManager.cpp ResourceManager.cpp BuildingManager.cpp SectorMap.cpp @@ -83,6 +84,7 @@ set(VCAI_HEADERS AIUtility.h AIhelper.h ArmyManager.h + HeroManager.h ResourceManager.h BuildingManager.h SectorMap.h diff --git a/AI/Nullkiller/HeroManager.cpp b/AI/Nullkiller/HeroManager.cpp new file mode 100644 index 000000000..66a214eb9 --- /dev/null +++ b/AI/Nullkiller/HeroManager.cpp @@ -0,0 +1,237 @@ +/* +* HeroManager.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 "HeroManager.h" + +#include "../../CCallback.h" +#include "../../lib/mapObjects/MapObjects.h" +#include "../../lib/CHeroHandler.h" + +SecondarySkillEvaluator HeroManager::wariorSkillsScores = SecondarySkillEvaluator( + { + std::make_shared( + std::map + { + {SecondarySkill::DIPLOMACY, 2}, + {SecondarySkill::LOGISTICS, 2}, + {SecondarySkill::EARTH_MAGIC, 2}, + {SecondarySkill::ARMORER, 2}, + {SecondarySkill::OFFENCE, 2}, + {SecondarySkill::AIR_MAGIC, 1}, + {SecondarySkill::WISDOM, 1}, + {SecondarySkill::LEADERSHIP, 1}, + {SecondarySkill::INTELLIGENCE, 1}, + {SecondarySkill::RESISTANCE, 1}, + {SecondarySkill::MYSTICISM, -1}, + {SecondarySkill::SORCERY, -1}, + {SecondarySkill::ESTATES, -1}, + {SecondarySkill::FIRST_AID, -1}, + {SecondarySkill::LEARNING, -1}, + {SecondarySkill::SCHOLAR, -1}, + {SecondarySkill::EAGLE_EYE, -1}, + {SecondarySkill::NAVIGATION, -1} + }), + std::make_shared(), + std::make_shared(), + std::make_shared() + }); + +SecondarySkillEvaluator HeroManager::scountSkillsScores = SecondarySkillEvaluator( + { + std::make_shared( + std::map + { + {SecondarySkill::LOGISTICS, 2}, + {SecondarySkill::ESTATES, 2}, + {SecondarySkill::PATHFINDING, 1}, + {SecondarySkill::SCHOLAR, 1} + }), + std::make_shared() + }); + +void HeroManager::init(CPlayerSpecificInfoCallback * CB) +{ + cb = CB; +} + +void HeroManager::setAI(VCAI * AI) +{ + ai = AI; +} + +float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const +{ + auto heroSpecial = Selector::source(Bonus::HERO_SPECIAL, hero->type->ID.getNum()); + auto secondarySkillBonus = Selector::type(Bonus::SECONDARY_SKILL_PREMY); + auto specialSecondarySkillBonuses = hero->getBonuses(heroSpecial.And(secondarySkillBonus)); + float specialityScore = 0.0f; + + for(auto bonus : *specialSecondarySkillBonuses) + { + SecondarySkill bonusSkill = SecondarySkill(bonus->subtype); + float bonusScore = wariorSkillsScores.evaluateSecSkill(hero, bonusSkill); + + if(bonusScore > 0) + specialityScore += bonusScore * bonusScore * bonusScore; + } + + return specialityScore; +} + +float HeroManager::evaluateFightingStrength(const CGHeroInstance * hero) const +{ + return evaluateSpeciality(hero) + wariorSkillsScores.evaluateSecSkills(hero) + hero->level * 1.5f; +} + +std::map HeroManager::getHeroRoles() const +{ + std::map scores; + std::map result; + auto myHeroes = ai->getMyHeroes(); + + for(auto & hero : myHeroes) + { + scores[hero] = evaluateFightingStrength(hero.get()); + } + + std::sort(myHeroes.begin(), myHeroes.end(), [&](const HeroPtr & h1, const HeroPtr & h2) -> bool + { + return scores.at(h1) < scores.at(h2); + }); + + int mainHeroCount = 4; + + for(auto & hero : myHeroes) + { + result[hero] = (mainHeroCount--) > 0 ? HeroRole::MAIN : HeroRole::SCOUT; + } + + return result; +} + +int HeroManager::selectBestSkill(const HeroPtr & hero, const std::vector & skills) const +{ + auto roles = getHeroRoles(); + auto role = roles[hero]; + auto & evaluator = role == HeroRole::MAIN ? wariorSkillsScores : scountSkillsScores; + + int result = 0; + float resultScore = -100; + + for(int i = 0; i < skills.size(); i++) + { + auto score = evaluator.evaluateSecSkill(hero.get(), skills[i]); + + if(score > resultScore) + { + resultScore = score; + result = i; + } + + logAi->trace( + "Hero %s is proposed to learn %d with score %f", + hero.name, + skills[i].toEnum(), + score); + } + + return result; +} + +SecondarySkillScoreMap::SecondarySkillScoreMap(std::map scoreMap) + :scoreMap(scoreMap) +{ +} + +void SecondarySkillScoreMap::evaluateScore(const CGHeroInstance * hero, SecondarySkill skill, float & score) const +{ + auto it = scoreMap.find(skill); + + if(it != scoreMap.end()) + { + score = it->second; + } +} + +void ExistingSkillRule::evaluateScore(const CGHeroInstance * hero, SecondarySkill skill, float & score) const +{ + int upgradesLeft = 0; + + for(auto & heroSkill : hero->secSkills) + { + if(heroSkill.first == skill) + return; + + upgradesLeft += SecSkillLevel::EXPERT - heroSkill.second; + } + + if(score >= 2 || score >= 1 && upgradesLeft <= 1) + score += 1.5; +} + +void WisdomRule::evaluateScore(const CGHeroInstance * hero, SecondarySkill skill, float & score) const +{ + if(skill != SecondarySkill::WISDOM) + return; + + auto wisdomLevel = hero->getSecSkillLevel(SecondarySkill::WISDOM); + + if(hero->level > 10 && wisdomLevel == SecSkillLevel::NONE) + score += 1.5; +} + +std::vector AtLeastOneMagicRule::magicSchools = { + SecondarySkill::AIR_MAGIC, + SecondarySkill::EARTH_MAGIC, + SecondarySkill::FIRE_MAGIC, + SecondarySkill::WATER_MAGIC +}; + +void AtLeastOneMagicRule::evaluateScore(const CGHeroInstance * hero, SecondarySkill skill, float & score) const +{ + if(!vstd::contains(magicSchools, skill)) + return; + + bool heroHasAnyMagic = vstd::contains_if(magicSchools, [&](SecondarySkill skill) -> bool + { + return hero->getSecSkillLevel(skill) > SecSkillLevel::NONE; + }); + + if(!heroHasAnyMagic) + score += 1; +} + +SecondarySkillEvaluator::SecondarySkillEvaluator(std::vector> evaluationRules) + : evaluationRules(evaluationRules) +{ +} + +float SecondarySkillEvaluator::evaluateSecSkills(const CGHeroInstance * hero) const +{ + float totalScore = 0; + + for(auto skill : hero->secSkills) + { + totalScore += skill.second * evaluateSecSkill(hero, skill.first); + } + + return totalScore; +} + +float SecondarySkillEvaluator::evaluateSecSkill(const CGHeroInstance * hero, SecondarySkill skill) const +{ + float score = 0; + + for(auto rule : evaluationRules) + rule->evaluateScore(hero, skill, score); + + return score; +} diff --git a/AI/Nullkiller/HeroManager.h b/AI/Nullkiller/HeroManager.h new file mode 100644 index 000000000..ed713ae5e --- /dev/null +++ b/AI/Nullkiller/HeroManager.h @@ -0,0 +1,107 @@ +/* +* HeroManager.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 "AIUtility.h" + +#include "../../lib/GameConstants.h" +#include "../../lib/VCMI_Lib.h" +#include "../../lib/CTownHandler.h" +#include "../../lib/CBuildingHandler.h" +#include "VCAI.h" + +enum HeroRole +{ + MAIN, + + SCOUT +}; + +class DLL_EXPORT IHeroManager //: public: IAbstractManager +{ +public: + virtual void init(CPlayerSpecificInfoCallback * CB) = 0; + virtual void setAI(VCAI * AI) = 0; + virtual std::map getHeroRoles() const = 0; + virtual int selectBestSkill(const HeroPtr & hero, const std::vector & skills) const = 0; +}; + +class DLL_EXPORT ISecondarySkillRule +{ +public: + virtual void evaluateScore(const CGHeroInstance * hero, SecondarySkill skill, float & score) const = 0; +}; + +class DLL_EXPORT SecondarySkillEvaluator +{ +private: + std::vector> evaluationRules; + +public: + SecondarySkillEvaluator(std::vector> evaluationRules); + float evaluateSecSkills(const CGHeroInstance * hero) const; + float evaluateSecSkill(const CGHeroInstance * hero, SecondarySkill skill) const; +}; + +class DLL_EXPORT HeroManager : public IHeroManager +{ +private: + static SecondarySkillEvaluator wariorSkillsScores; + static SecondarySkillEvaluator scountSkillsScores; + + CPlayerSpecificInfoCallback * cb; //this is enough, but we downcast from CCallback + VCAI * ai; + +public: + void init(CPlayerSpecificInfoCallback * CB) override; + void setAI(VCAI * AI) override; + std::map getHeroRoles() const override; + int selectBestSkill(const HeroPtr & hero, const std::vector & skills) const override; + +private: + float evaluateFightingStrength(const CGHeroInstance * hero) const; + float evaluateSpeciality(const CGHeroInstance * hero) const; +}; + +// basic skill scores. missing skills will have score of 0 +class DLL_EXPORT SecondarySkillScoreMap : public ISecondarySkillRule +{ +private: + std::map scoreMap; + +public: + SecondarySkillScoreMap(std::map scoreMap); + void evaluateScore(const CGHeroInstance * hero, SecondarySkill skill, float & score) const override; +}; + +// Controls when to upgrade existing skills and when get new +class ExistingSkillRule : public ISecondarySkillRule +{ +public: + void evaluateScore(const CGHeroInstance * hero, SecondarySkill skill, float & score) const override; +}; + +// Allows to get wisdom at 12 lvl +class WisdomRule : public ISecondarySkillRule +{ +public: + void evaluateScore(const CGHeroInstance * hero, SecondarySkill skill, float & score) const override; +}; + +// Dynamically controls scores for magic skills +class AtLeastOneMagicRule : public ISecondarySkillRule +{ +private: + static std::vector magicSchools; + +public: + void evaluateScore(const CGHeroInstance * hero, SecondarySkill skill, float & score) const override; +}; \ No newline at end of file diff --git a/AI/Nullkiller/VCAI.cpp b/AI/Nullkiller/VCAI.cpp index 059f05a29..03f857478 100644 --- a/AI/Nullkiller/VCAI.cpp +++ b/AI/Nullkiller/VCAI.cpp @@ -624,7 +624,7 @@ void VCAI::heroGotLevel(const CGHeroInstance * hero, PrimarySkill::PrimarySkill LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID); NET_EVENT_HANDLER; status.addQuery(queryID, boost::str(boost::format("Hero %s got level %d") % hero->name % hero->level)); - requestActionASAP([=](){ answerQuery(queryID, 0); }); + requestActionASAP([=](){ answerQuery(queryID, ah->selectBestSkill(hero, skills)); }); } void VCAI::commanderGotLevel(const CCommanderInstance * commander, std::vector skills, QueryID queryID)