mirror of
https://github.com/vcmi/vcmi.git
synced 2025-08-13 19:54:17 +02:00
AI: implement hero skills evaluator
This commit is contained in:
committed by
Andrii Danylchenko
parent
01975e339b
commit
14adf1d108
@@ -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<SlotInfo>::iterator AIhelper::getWeakestCreature(std::vector<SlotInf
|
||||
std::vector<SlotInfo> AIhelper::getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const
|
||||
{
|
||||
return armyManager->getSortedSlots(target, source);
|
||||
}
|
||||
|
||||
int AIhelper::selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const
|
||||
{
|
||||
return heroManager->selectBestSkill(hero, skills);
|
||||
}
|
||||
|
||||
std::map<HeroPtr, HeroRole> AIhelper::getHeroRoles() const
|
||||
{
|
||||
return heroManager->getHeroRoles();
|
||||
}
|
@@ -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> buildingManager;
|
||||
std::shared_ptr<PathfindingManager> pathfindingManager;
|
||||
std::shared_ptr<ArmyManager> armyManager;
|
||||
std::shared_ptr<HeroManager> heroManager;
|
||||
//TODO: vector<IAbstractManager>
|
||||
public:
|
||||
AIhelper();
|
||||
@@ -78,6 +80,9 @@ public:
|
||||
std::vector<SlotInfo>::iterator getWeakestCreature(std::vector<SlotInfo> & army) const override;
|
||||
std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const override;
|
||||
|
||||
std::map<HeroPtr, HeroRole> getHeroRoles() const override;
|
||||
int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const override;
|
||||
|
||||
private:
|
||||
bool notifyGoalCompleted(Goals::TSubgoal goal) override;
|
||||
|
||||
|
@@ -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
|
||||
|
237
AI/Nullkiller/HeroManager.cpp
Normal file
237
AI/Nullkiller/HeroManager.cpp
Normal file
@@ -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<SecondarySkillScoreMap>(
|
||||
std::map<SecondarySkill, float>
|
||||
{
|
||||
{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<ExistingSkillRule>(),
|
||||
std::make_shared<WisdomRule>(),
|
||||
std::make_shared<AtLeastOneMagicRule>()
|
||||
});
|
||||
|
||||
SecondarySkillEvaluator HeroManager::scountSkillsScores = SecondarySkillEvaluator(
|
||||
{
|
||||
std::make_shared<SecondarySkillScoreMap>(
|
||||
std::map<SecondarySkill, float>
|
||||
{
|
||||
{SecondarySkill::LOGISTICS, 2},
|
||||
{SecondarySkill::ESTATES, 2},
|
||||
{SecondarySkill::PATHFINDING, 1},
|
||||
{SecondarySkill::SCHOLAR, 1}
|
||||
}),
|
||||
std::make_shared<ExistingSkillRule>()
|
||||
});
|
||||
|
||||
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<HeroPtr, HeroRole> HeroManager::getHeroRoles() const
|
||||
{
|
||||
std::map<HeroPtr, float> scores;
|
||||
std::map<HeroPtr, HeroRole> 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<SecondarySkill> & 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<SecondarySkill, float> 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<SecondarySkill> 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<std::shared_ptr<ISecondarySkillRule>> 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;
|
||||
}
|
107
AI/Nullkiller/HeroManager.h
Normal file
107
AI/Nullkiller/HeroManager.h
Normal file
@@ -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<HeroPtr, HeroRole> getHeroRoles() const = 0;
|
||||
virtual int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & 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<std::shared_ptr<ISecondarySkillRule>> evaluationRules;
|
||||
|
||||
public:
|
||||
SecondarySkillEvaluator(std::vector<std::shared_ptr<ISecondarySkillRule>> 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<HeroPtr, HeroRole> getHeroRoles() const override;
|
||||
int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & 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<SecondarySkill, float> scoreMap;
|
||||
|
||||
public:
|
||||
SecondarySkillScoreMap(std::map<SecondarySkill, float> 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<SecondarySkill> magicSchools;
|
||||
|
||||
public:
|
||||
void evaluateScore(const CGHeroInstance * hero, SecondarySkill skill, float & score) const override;
|
||||
};
|
@@ -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<ui32> skills, QueryID queryID)
|
||||
|
Reference in New Issue
Block a user