1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-25 22:42:04 +02:00

refactoring RecruitHeroBehavior + test sample; renamed more Nullkiller variables consistently

This commit is contained in:
Mircea TheHonestCTO
2025-08-24 09:12:34 +02:00
parent 54ad621357
commit 58543f23cf
44 changed files with 335 additions and 261 deletions

View File

@@ -79,7 +79,7 @@ class DLL_EXPORT BuildAnalyzer
Nullkiller * aiNk;
public:
explicit BuildAnalyzer(Nullkiller * ai) : aiNk(ai) {}
explicit BuildAnalyzer(Nullkiller * aiNk) : aiNk(aiNk) {}
void update();
TResources getResourcesRequiredNow() const;

View File

@@ -79,7 +79,7 @@ private:
std::map<ObjectInstanceID, std::vector<HitMapInfo>> townThreats;
public:
DangerHitMapAnalyzer(const Nullkiller * ai) :aiNk(ai) {}
DangerHitMapAnalyzer(const Nullkiller * aiNk) :aiNk(aiNk) {}
void updateHitMap();
void calculateTileOwners();

View File

@@ -97,6 +97,7 @@ float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const
float HeroManager::evaluateFightingStrength(const CGHeroInstance * hero) const
{
// TODO: Mircea: Shouldn't we count bonuses from artifacts when generating the fighting strength? That could make a huge difference
return evaluateSpeciality(hero) + wariorSkillsScores.evaluateSecSkills(hero) + hero->getBasePrimarySkillValue(PrimarySkill::ATTACK) + hero->getBasePrimarySkillValue(PrimarySkill::DEFENSE) + hero->getBasePrimarySkillValue(PrimarySkill::SPELL_POWER) + hero->getBasePrimarySkillValue(PrimarySkill::KNOWLEDGE);
}
@@ -127,37 +128,37 @@ void HeroManager::update()
}
std::sort(myHeroes.begin(), myHeroes.end(), scoreSort);
heroRoles.clear();
heroToRoleMap.clear();
for(auto hero : myHeroes)
{
if(hero->patrol.patrolling)
{
heroRoles[hero] = HeroRole::MAIN;
heroToRoleMap[hero] = HeroRole::MAIN;
}
else
{
heroRoles[hero] = (globalMainCount--) > 0 ? HeroRole::MAIN : HeroRole::SCOUT;
heroToRoleMap[hero] = (globalMainCount--) > 0 ? HeroRole::MAIN : HeroRole::SCOUT;
}
}
for(auto hero : myHeroes)
{
logAi->trace("Hero %s has role %s", hero->getNameTranslated(), heroRoles[hero] == HeroRole::MAIN ? "main" : "scout");
logAi->trace("Hero %s has role %s", hero->getNameTranslated(), heroToRoleMap[hero] == HeroRole::MAIN ? "main" : "scout");
}
}
HeroRole HeroManager::getHeroRole(const HeroPtr & hero) const
{
if (heroRoles.find(hero) != heroRoles.end())
return heroRoles.at(hero);
if (heroToRoleMap.find(hero) != heroToRoleMap.end())
return heroToRoleMap.at(hero);
else
return HeroRole::SCOUT;
}
const std::map<HeroPtr, HeroRole> & HeroManager::getHeroRoles() const
const std::map<HeroPtr, HeroRole> & HeroManager::getHeroToRoleMap() const
{
return heroRoles;
return heroToRoleMap;
}
int HeroManager::selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const

View File

@@ -44,12 +44,12 @@ private:
CCallback * cc; //this is enough, but we downcast from CCallback
const Nullkiller * aiNk;
std::map<HeroPtr, HeroRole> heroRoles;
std::map<HeroPtr, HeroRole> heroToRoleMap;
std::map<ObjectInstanceID, float> knownFightingStrength;
public:
HeroManager(CCallback * cc, const Nullkiller * ai) : cc(cc), aiNk(ai) {}
const std::map<HeroPtr, HeroRole> & getHeroRoles() const;
HeroManager(CCallback * cc, const Nullkiller * aiNk) : cc(cc), aiNk(aiNk) {}
const std::map<HeroPtr, HeroRole> & getHeroToRoleMap() const;
HeroRole getHeroRole(const HeroPtr & hero) const;
int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const;
void update();

View File

@@ -81,7 +81,7 @@ public:
const CGObjectInstance * getBlocker(const AIPath & path) const;
std::optional<const CGObjectInstance *> getBlocker(const AIPathNodeInfo & node) const;
ObjectClusterizer(const Nullkiller * ai): aiNk(ai), valueEvaluator(ai), isUpToDate(false){}
ObjectClusterizer(const Nullkiller * aiNk): aiNk(aiNk), valueEvaluator(aiNk), isUpToDate(false){}
void validateObjects();
void onObjectRemoved(ObjectInstanceID id);

View File

@@ -24,7 +24,7 @@ namespace Goals
std::string toString() const override;
bool operator==(const BuyArmyBehavior & other) const override
{
return true;
return true; // TODO: Mircea: how does this make sense?
}
};
}

View File

@@ -9,6 +9,8 @@
*/
#include "StdInc.h"
#include "RecruitHeroBehavior.h"
#include <algorithm>
#include "../AIGateway.h"
#include "../AIUtility.h"
#include "../Goals/RecruitHero.h"
@@ -28,124 +30,119 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * aiNk) const
{
Goals::TGoalVec tasks;
const auto ourTowns = aiNk->cc->getTownsInfo();
const auto ourHeroes = aiNk->heroManager->getHeroRoles();
auto minScoreToHireMain = std::numeric_limits<float>::max();
int currentArmyValue = 0;
for(const auto & hero : ourHeroes)
{
currentArmyValue += hero.first->getArmyCost();
if(hero.second != HeroRole::MAIN)
continue;
const auto newScore = aiNk->heroManager->evaluateHero(hero.first.get());
if(minScoreToHireMain > newScore)
{
// weakest main hero score
minScoreToHireMain = newScore;
}
}
// If we don't have any heroes, lower our expectations.
if (ourHeroes.empty())
minScoreToHireMain = 0;
const CGHeroInstance* bestHeroToHire = nullptr;
const CGTownInstance* bestTownToHireFrom = nullptr;
float bestScore = 0;
const auto ourHeroes = aiNk->heroManager->getHeroToRoleMap();
RecruitHeroChoice bestChoice;
bool haveCapitol = false;
int treasureSourcesCount = 0;
// Simplification: Moved this call before getting into the decomposer
// aiNk->dangerHitMap->updateHitMap();
int treasureSourcesCount = 0;
int bestClosestThreat = UINT8_MAX;
for(const auto * town : ourTowns)
{
uint8_t closestThreat = UINT8_MAX;
for (const auto & threat : aiNk->dangerHitMap->getTownThreats(town))
{
closestThreat = std::min(closestThreat, threat.turn);
}
if (town->getVisitingHero() && town->getGarrisonHero())
if(town->getVisitingHero() && town->getGarrisonHero())
continue;
float visitability = 0;
for (const auto & hero : ourHeroes)
uint8_t closestThreatTurn = UINT8_MAX;
for(const auto & threat : aiNk->dangerHitMap->getTownThreats(town))
{
if (aiNk->dangerHitMap->getClosestTown(hero.first.get()->visitablePos()) == town)
visitability++;
closestThreatTurn = std::min(closestThreatTurn, threat.turn);
}
float visitabilityRatio = 0;
for(const auto & [hero, role] : ourHeroes)
{
if(aiNk->dangerHitMap->getClosestTown(hero.get()->visitablePos()) == town)
visitabilityRatio += 1.0f / ourHeroes.size();
}
if(aiNk->heroManager->canRecruitHero(town))
{
auto availableHeroes = aiNk->cc->getAvailableHeroes(town);
for (const auto * obj : aiNk->objectClusterizer->getNearbyObjects())
{
if (obj->ID == Obj::RESOURCE
|| obj->ID == Obj::TREASURE_CHEST
|| obj->ID == Obj::CAMPFIRE
|| isWeeklyRevisitable(aiNk->playerID, obj)
|| obj->ID == Obj::ARTIFACT)
{
auto tile = obj->visitablePos();
if (town == aiNk->dangerHitMap->getClosestTown(tile))
treasureSourcesCount++; // TODO: Mircea: Shouldn't it be used to determine the best town?
}
calculateTreasureSources(aiNk->objectClusterizer->getNearbyObjects(), aiNk->playerID, *aiNk->dangerHitMap, treasureSourcesCount, town);
calculateBestHero(aiNk->cc->getAvailableHeroes(town),
*aiNk->heroManager,
bestChoice,
town,
closestThreatTurn,
visitabilityRatio);
}
for(const auto hero : availableHeroes)
{
if ((town->getVisitingHero() || town->getGarrisonHero())
&& closestThreat < 1
&& hero->getArmyCost() < GameConstants::HERO_GOLD_COST / 3.0)
continue;
auto score = aiNk->heroManager->evaluateHero(hero);
if(score > minScoreToHireMain)
{
score *= score / minScoreToHireMain;
}
score *= hero->getArmyCost() + currentArmyValue;
if (hero->getFactionID() == town->getFactionID())
score *= 1.5;
if (vstd::isAlmostZero(visitability))
score *= 30 * town->getTownLevel();
else
score *= town->getTownLevel() / visitability;
if (score > bestScore)
{
bestScore = score;
bestHeroToHire = hero;
bestTownToHireFrom = town; // TODO: Mircea: Seems to be no logic to choose the right town?
bestClosestThreat = closestThreat;
}
}
}
if (town->hasCapitol())
if(town->hasCapitol())
haveCapitol = true;
}
if (bestHeroToHire && bestTownToHireFrom)
if(!vstd::isAlmostZero(bestChoice.score))
{
if (aiNk->cc->getHeroesInfo().size() == 0
|| treasureSourcesCount > aiNk->cc->getHeroesInfo().size() * 5
|| (bestHeroToHire->getArmyCost() > GameConstants::HERO_GOLD_COST / 2.0 && (bestClosestThreat < 1 || !aiNk->buildAnalyzer->isGoldPressureOverMax()))
if(ourHeroes.empty()
|| treasureSourcesCount > ourHeroes.size() * 5
// TODO: Mircea: The next condition should always consider a hero if under attack especially if it has towers
|| (bestChoice.hero->getArmyCost() > GameConstants::HERO_GOLD_COST / 2.0 && (
bestChoice.closestThreat < 1 || !aiNk->buildAnalyzer->isGoldPressureOverMax()))
|| (aiNk->getFreeResources()[EGameResID::GOLD] > 10000 && !aiNk->buildAnalyzer->isGoldPressureOverMax() && haveCapitol)
|| (aiNk->getFreeResources()[EGameResID::GOLD] > 30000 && !aiNk->buildAnalyzer->isGoldPressureOverMax()))
{
tasks.push_back(Goals::sptr(Goals::RecruitHero(bestTownToHireFrom, bestHeroToHire).setpriority((float)3 / (ourHeroes.size() + 1))));
tasks.push_back(Goals::sptr(Goals::RecruitHero(bestChoice.town, bestChoice.hero).setpriority((float)3 / (ourHeroes.size() + 1))));
}
}
return tasks;
}
void RecruitHeroBehavior::calculateTreasureSources(const std::vector<const CGObjectInstance *> & nearbyObjects,
const PlayerColor & playerID,
const DangerHitMapAnalyzer & dangerHitMap,
int & treasureSourcesCount,
const CGTownInstance * town)
{
for(const auto * obj : nearbyObjects)
{
if(obj->ID == Obj::RESOURCE
|| obj->ID == Obj::TREASURE_CHEST
|| obj->ID == Obj::CAMPFIRE
|| isWeeklyRevisitable(playerID, obj)
|| obj->ID == Obj::ARTIFACT)
{
if(town == dangerHitMap.getClosestTown(obj->visitablePos()))
treasureSourcesCount++; // TODO: Mircea: Shouldn't it be used to determine the best town?
}
}
}
void RecruitHeroBehavior::calculateBestHero(const std::vector<const CGHeroInstance *> & availableHeroes,
const HeroManager & heroManager,
const RecruitHeroChoice & bestChoice,
const CGTownInstance * town,
const uint8_t closestThreatTurn,
const float visitabilityRatio)
{
for(const auto * const hero : availableHeroes)
{
if((town->getVisitingHero() || town->getGarrisonHero())
&& closestThreatTurn < 1
&& hero->getArmyCost() < GameConstants::HERO_GOLD_COST / 3.0)
continue;
const float heroScore = heroManager.evaluateHero(hero);
float totalScore = heroScore + hero->getArmyCost();
// TODO: Mircea: Score higher if ballista/tent/ammo cart by the cost in gold? Or should that be covered in evaluateHero?
// getArtifactScoreForHero(hero, ...) ArtifactID::BALLISTA
if(hero->getFactionID() == town->getFactionID())
totalScore += heroScore * 1.5;
// prioritize a more developed town especially if no heroes can visit it (smaller ratio, bigger score)
totalScore += heroScore * town->getTownLevel() * (1 - visitabilityRatio);
if(totalScore > bestChoice.score)
{
bestChoice.score = totalScore;
bestChoice.hero = hero;
bestChoice.town = town;
bestChoice.closestThreat = closestThreatTurn;
}
}
}
}

View File

@@ -12,27 +12,51 @@
#include "lib/GameLibrary.h"
#include "../Goals/CGoal.h"
#include "../AIUtility.h"
#include "../Analyzers/DangerHitMapAnalyzer.h"
#include "../Analyzers/HeroManager.h"
namespace NK2AI
{
namespace Goals
{
class RecruitHeroBehavior : public CGoal<RecruitHeroBehavior>
{
public:
struct RecruitHeroChoice
{
mutable float score = 0;
mutable const CGHeroInstance * hero = nullptr;
mutable const CGTownInstance * town = nullptr;
mutable int closestThreat = 0;
};
class RecruitHeroBehavior : public CGoal<RecruitHeroBehavior>
{
public:
RecruitHeroBehavior()
:CGoal(RECRUIT_HERO_BEHAVIOR)
: CGoal(RECRUIT_HERO_BEHAVIOR)
{
}
~RecruitHeroBehavior() override = default;
TGoalVec decompose(const Nullkiller * aiNk) const override;
std::string toString() const override;
bool operator==(const RecruitHeroBehavior & other) const override
{
return true;
return true; // TODO: Mircea: How does that make sense?
}
};
static void calculateTreasureSources(const std::vector<const CGObjectInstance *> & nearbyObjects,
const PlayerColor & playerID,
const DangerHitMapAnalyzer & dangerHitMap,
int & treasureSourcesCount,
const CGTownInstance * town);
static void calculateBestHero(const std::vector<const CGHeroInstance *> & availableHeroes,
const HeroManager & heroManager,
const RecruitHeroChoice & bestChoice,
const CGTownInstance * town,
uint8_t closestThreatTurn,
float visitabilityRatio);
};
}
}

View File

@@ -27,7 +27,7 @@ namespace NK2AI
using namespace Goals;
DeepDecomposer::DeepDecomposer(const Nullkiller * aiNk)
:ai(aiNk), depth(0)
:aiNk(aiNk), depth(0)
{
}
@@ -139,7 +139,7 @@ Goals::TSubgoal DeepDecomposer::aggregateGoals(int startDepth, TSubgoal last)
Goals::TSubgoal DeepDecomposer::unwrapComposition(Goals::TSubgoal goal)
{
return goal->goalType == Goals::COMPOSITION ? goal->decompose(ai).back() : goal;
return goal->goalType == Goals::COMPOSITION ? goal->decompose(aiNk).back() : goal;
}
bool isEquivalentGoals(TSubgoal goal1, TSubgoal goal2)
@@ -159,7 +159,7 @@ bool isEquivalentGoals(TSubgoal goal1, TSubgoal goal2)
bool DeepDecomposer::isCompositionLoop(TSubgoal goal)
{
auto goalsToTest = goal->goalType == Goals::COMPOSITION ? goal->decompose(ai) : TGoalVec{goal};
auto goalsToTest = goal->goalType == Goals::COMPOSITION ? goal->decompose(aiNk) : TGoalVec{goal};
for(auto goalToTest : goalsToTest)
{
@@ -209,7 +209,7 @@ TGoalVec DeepDecomposer::decomposeCached(TSubgoal goal, bool & fromCache)
fromCache = false;
return goal->decompose(ai);
return goal->decompose(aiNk);
}
void DeepDecomposer::addToCache(TSubgoal goal)

View File

@@ -30,7 +30,7 @@ private:
std::vector<Goals::TGoalVec> goals;
std::vector<TGoalHashSet> decompositionCache;
int depth;
const Nullkiller * ai;
const Nullkiller * aiNk;
public:
DeepDecomposer(const Nullkiller * aiNk);

View File

@@ -22,7 +22,7 @@ private:
TacticalAdvantageEngine tacticalAdvantageEngine;
public:
FuzzyHelper(const Nullkiller * ai): aiNk(ai) {}
FuzzyHelper(const Nullkiller * aiNk): aiNk(aiNk) {}
ui64 evaluateDanger(const CGObjectInstance * obj);
ui64 evaluateDanger(const int3 & tile, const CGHeroInstance * visitor, bool checkGuards = true);

View File

@@ -107,14 +107,15 @@ public:
std::unique_ptr<Settings> settings;
/// Same value as AIGateway->playerID
PlayerColor playerID;
/// Same value as AIGateway->cc
std::shared_ptr<CCallback> cc;
std::mutex aiStateMutex;
mutable ThreadInterruption makingTurnInterrupption;
Nullkiller();
~Nullkiller();
virtual ~Nullkiller();
void init(std::shared_ptr<CCallback> cb, AIGateway * aiGw);
void makeTurn();
virtual void makeTurn();
bool makeTurnHelperPriorityPass(Goals::TGoalVec& tempResults, int passIndex);
bool isActive(const CGHeroInstance * hero) const { return activeHero == hero; }
bool isHeroLocked(const CGHeroInstance * hero) const;

View File

@@ -37,7 +37,7 @@ namespace NK2AI
constexpr float MAX_CRITICAL_VALUE = 2.0f;
EvaluationContext::EvaluationContext(const Nullkiller* ai)
EvaluationContext::EvaluationContext(const Nullkiller* aiNk)
: movementCost(0.0),
manaCost(0),
danger(0),
@@ -52,7 +52,7 @@ EvaluationContext::EvaluationContext(const Nullkiller* ai)
turn(0),
strategicalValue(0),
conquestValue(0),
evaluator(ai),
evaluator(aiNk),
enemyHeroDangerRatio(0),
threat(0),
armyGrowth(0),
@@ -929,7 +929,7 @@ private:
const Nullkiller * aiNk;
public:
ExecuteHeroChainEvaluationContextBuilder(const Nullkiller * ai) : aiNk(ai) {}
ExecuteHeroChainEvaluationContextBuilder(const Nullkiller * aiNk) : aiNk(aiNk) {}
void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
{
@@ -1045,7 +1045,7 @@ public:
class ClusterEvaluationContextBuilder : public IEvaluationContextBuilder
{
public:
ClusterEvaluationContextBuilder(const Nullkiller * ai) {}
ClusterEvaluationContextBuilder(const Nullkiller * aiNk) {}
void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
{
@@ -1132,7 +1132,7 @@ private:
const Nullkiller * aiNk;
public:
DismissHeroContextBuilder(const Nullkiller * ai) : aiNk(ai) {}
DismissHeroContextBuilder(const Nullkiller * aiNk) : aiNk(aiNk) {}
void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
{
@@ -1264,18 +1264,18 @@ uint64_t RewardEvaluator::getUpgradeArmyReward(const CGTownInstance * town, cons
return upgradedPower - creaturesToUpgrade.power;
}
PriorityEvaluator::PriorityEvaluator(const Nullkiller * ai)
:aiNk(ai)
PriorityEvaluator::PriorityEvaluator(const Nullkiller * aiNk)
:aiNk(aiNk)
{
initVisitTile();
evaluationContextBuilders.push_back(std::make_shared<ExecuteHeroChainEvaluationContextBuilder>(ai));
evaluationContextBuilders.push_back(std::make_shared<ExecuteHeroChainEvaluationContextBuilder>(aiNk));
evaluationContextBuilders.push_back(std::make_shared<BuildThisEvaluationContextBuilder>());
evaluationContextBuilders.push_back(std::make_shared<ClusterEvaluationContextBuilder>(ai));
evaluationContextBuilders.push_back(std::make_shared<ClusterEvaluationContextBuilder>(aiNk));
evaluationContextBuilders.push_back(std::make_shared<HeroExchangeEvaluator>());
evaluationContextBuilders.push_back(std::make_shared<ArmyUpgradeEvaluator>());
evaluationContextBuilders.push_back(std::make_shared<DefendTownEvaluator>());
evaluationContextBuilders.push_back(std::make_shared<ExchangeSwapTownHeroesContextBuilder>());
evaluationContextBuilders.push_back(std::make_shared<DismissHeroContextBuilder>(ai));
evaluationContextBuilders.push_back(std::make_shared<DismissHeroContextBuilder>(aiNk));
evaluationContextBuilders.push_back(std::make_shared<StayAtTownManaRecoveryEvaluator>());
evaluationContextBuilders.push_back(std::make_shared<ExplorePointEvaluator>());
}

View File

@@ -32,7 +32,7 @@ class RewardEvaluator
public:
const Nullkiller * aiNk;
RewardEvaluator(const Nullkiller * ai) : aiNk(ai) {}
RewardEvaluator(const Nullkiller * aiNk) : aiNk(aiNk) {}
uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army, bool checkGold) const;
uint64_t getArmyGrowth(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army) const;
@@ -86,7 +86,7 @@ struct DLL_EXPORT EvaluationContext
int explorePriority;
float powerRatio;
EvaluationContext(const Nullkiller * ai);
EvaluationContext(const Nullkiller * aiNk);
void addNonCriticalStrategicalValue(float value);
};
@@ -103,7 +103,7 @@ class Nullkiller;
class PriorityEvaluator
{
public:
PriorityEvaluator(const Nullkiller * ai);
PriorityEvaluator(const Nullkiller * aiNk);
~PriorityEvaluator();
void initVisitTile();

View File

@@ -71,20 +71,15 @@ std::string AbstractGoal::toString() const
return desc;
}
bool AbstractGoal::operator==(const AbstractGoal & g) const
{
return false;
}
//TODO: find out why the following are not generated automatically on MVS?
bool TSubgoal::operator==(const TSubgoal & rhs) const
{
return *get() == *rhs.get(); //comparison for Goals is overloaded, so they don't need to be identical to match
}
bool AbstractGoal::invalid() const
bool TSubgoal::operator<(const TSubgoal & rhs) const
{
return goalType == EGoals::INVALID;
return false;
}
}

View File

@@ -112,10 +112,12 @@ namespace Goals
const CGHeroInstance * hero; SETTER(CGHeroInstance *, hero)
const CGTownInstance *town; SETTER(CGTownInstance *, town)
int bid; SETTER(int, bid)
EGoals goalType;
AbstractGoal(EGoals goal = EGoals::INVALID): goalType(goal)
explicit AbstractGoal(EGoals goal = EGoals::INVALID): goalType(goal)
{
isAbstract = false;
// isAbstract = false;
isAbstract = true;
value = 0;
aid = -1;
resID = -1;
@@ -126,25 +128,31 @@ namespace Goals
bid = -1;
goldCost = 0;
}
virtual ~AbstractGoal() {}
virtual ~AbstractGoal() = default;
//FIXME: abstract goal should be abstract, but serializer fails to instantiate subgoals in such case
virtual AbstractGoal * clone() const
{
return const_cast<AbstractGoal *>(this);
// auto * copy = new AbstractGoal(*this);
// return copy;
}
virtual TGoalVec decompose(const Nullkiller * ai) const
virtual TGoalVec decompose(const Nullkiller * aiNk) const
{
return TGoalVec();
}
EGoals goalType;
virtual std::string toString() const;
bool invalid() const;
// virtual bool invalid() const;
virtual bool invalid() const
{
return goalType == EGoals::INVALID;
}
virtual bool operator==(const AbstractGoal & g) const;
// virtual bool operator==(const AbstractGoal & g) const;
virtual bool operator==(const AbstractGoal & g) const { return false; }
virtual bool isElementar() const { return false; }
@@ -175,7 +183,7 @@ namespace Goals
virtual void accept(AIGateway * aiGw) = 0; //unhandled goal will report standard error
virtual std::string toString() const = 0;
virtual const CGHeroInstance * getHero() const = 0;
virtual ~ITask() {}
virtual ~ITask() = default;
virtual int getHeroExchangeCount() const = 0;
virtual bool isObjectAffected(ObjectInstanceID h) const = 0;
virtual std::vector<ObjectInstanceID> getAffectedObjects() const = 0;

View File

@@ -22,17 +22,19 @@ namespace Goals
class DLL_EXPORT CGoal : public AbstractGoal
{
public:
CGoal(EGoals goal = INVALID) : AbstractGoal(goal)
explicit CGoal(EGoals goal = INVALID) : AbstractGoal(goal)
{
isAbstract = true;
value = 0;
aid = -1;
objid = -1;
resID = -1;
tile = int3(-1, -1, -1);
town = nullptr;
// isAbstract = true;
// value = 0;
// aid = -1;
// objid = -1;
// resID = -1;
// tile = int3(-1, -1, -1);
// town = nullptr;
}
~CGoal() override = default;
CGoal * clone() const override
{
return new T(static_cast<T const &>(*this)); //casting enforces template instantiation
@@ -48,9 +50,9 @@ namespace Goals
virtual bool operator==(const T & other) const = 0;
TGoalVec decompose(const Nullkiller * ai) const override
TGoalVec decompose(const Nullkiller * aiNk) const override
{
TSubgoal single = decomposeSingle(ai);
TSubgoal single = decomposeSingle(aiNk);
if(!single || single->invalid())
return {};
@@ -59,7 +61,7 @@ namespace Goals
}
protected:
virtual TSubgoal decomposeSingle(const Nullkiller * ai) const
virtual TSubgoal decomposeSingle(const Nullkiller * aiNk) const
{
return TSubgoal();
}

View File

@@ -31,37 +31,37 @@ std::string CompleteQuest::toString() const
return "Complete quest " + questToString();
}
TGoalVec CompleteQuest::decompose(const Nullkiller * ai) const
TGoalVec CompleteQuest::decompose(const Nullkiller * aiNk) const
{
if(isKeyMaster(q))
{
return missionKeymaster(ai);
return missionKeymaster(aiNk);
}
logAi->debug("Trying to realize quest: %s", questToString());
auto quest = q.getQuest(ccTl);
if(!quest->mission.artifacts.empty())
return missionArt(ai);
return missionArt(aiNk);
if(!quest->mission.heroes.empty())
return missionHero(ai);
return missionHero(aiNk);
if(!quest->mission.creatures.empty())
return missionArmy(ai);
return missionArmy(aiNk);
if(quest->mission.resources.nonZero())
return missionResources(ai);
return missionResources(aiNk);
if(quest->killTarget != ObjectInstanceID::NONE)
return missionDestroyObj(ai);
return missionDestroyObj(aiNk);
for(auto & s : quest->mission.primary)
if(s)
return missionIncreasePrimaryStat(ai);
return missionIncreasePrimaryStat(aiNk);
if(quest->mission.heroLevel > 0)
return missionLevel(ai);
return missionLevel(aiNk);
return TGoalVec();
}
@@ -118,9 +118,9 @@ TGoalVec CompleteQuest::tryCompleteQuest(const Nullkiller * aiNk) const
return CaptureObjectsBehavior::getVisitGoals(paths, aiNk, q.getObject(ccTl));
}
TGoalVec CompleteQuest::missionArt(const Nullkiller * ai) const
TGoalVec CompleteQuest::missionArt(const Nullkiller * aiNk) const
{
TGoalVec solutions = tryCompleteQuest(ai);
TGoalVec solutions = tryCompleteQuest(aiNk);
if(!solutions.empty())
return solutions;
@@ -135,9 +135,9 @@ TGoalVec CompleteQuest::missionArt(const Nullkiller * ai) const
return solutions;
}
TGoalVec CompleteQuest::missionHero(const Nullkiller * ai) const
TGoalVec CompleteQuest::missionHero(const Nullkiller * aiNk) const
{
TGoalVec solutions = tryCompleteQuest(ai);
TGoalVec solutions = tryCompleteQuest(aiNk);
if(solutions.empty())
{
@@ -160,31 +160,31 @@ TGoalVec CompleteQuest::missionArmy(const Nullkiller * aiNk) const
return CaptureObjectsBehavior::getVisitGoals(paths, aiNk, q.getObject(ccTl));
}
TGoalVec CompleteQuest::missionIncreasePrimaryStat(const Nullkiller * ai) const
TGoalVec CompleteQuest::missionIncreasePrimaryStat(const Nullkiller * aiNk) const
{
return tryCompleteQuest(ai);
return tryCompleteQuest(aiNk);
}
TGoalVec CompleteQuest::missionLevel(const Nullkiller * ai) const
TGoalVec CompleteQuest::missionLevel(const Nullkiller * aiNk) const
{
return tryCompleteQuest(ai);
return tryCompleteQuest(aiNk);
}
TGoalVec CompleteQuest::missionKeymaster(const Nullkiller * ai) const
TGoalVec CompleteQuest::missionKeymaster(const Nullkiller * aiNk) const
{
if(isObjectPassable(ai, q.getObject(ccTl)))
if(isObjectPassable(aiNk, q.getObject(ccTl)))
{
return CaptureObjectsBehavior(q.getObject(ccTl)).decompose(ai);
return CaptureObjectsBehavior(q.getObject(ccTl)).decompose(aiNk);
}
else
{
return CaptureObjectsBehavior().ofType(Obj::KEYMASTER, q.getObject(ccTl)->subID).decompose(ai);
return CaptureObjectsBehavior().ofType(Obj::KEYMASTER, q.getObject(ccTl)->subID).decompose(aiNk);
}
}
TGoalVec CompleteQuest::missionResources(const Nullkiller * ai) const
TGoalVec CompleteQuest::missionResources(const Nullkiller * aiNk) const
{
TGoalVec solutions = tryCompleteQuest(ai);
TGoalVec solutions = tryCompleteQuest(aiNk);
return solutions;
}

View File

@@ -28,7 +28,7 @@ namespace Goals
{
}
Goals::TGoalVec decompose(const Nullkiller * ai) const override;
Goals::TGoalVec decompose(const Nullkiller * aiNk) const override;
std::string toString() const override;
bool hasHash() const override { return true; }
uint64_t getHash() const override;
@@ -37,14 +37,14 @@ namespace Goals
private:
TGoalVec tryCompleteQuest(const Nullkiller * aiNk) const;
TGoalVec missionArt(const Nullkiller * ai) const;
TGoalVec missionHero(const Nullkiller * ai) const;
TGoalVec missionArt(const Nullkiller * aiNk) const;
TGoalVec missionHero(const Nullkiller * aiNk) const;
TGoalVec missionArmy(const Nullkiller * aiNk) const;
TGoalVec missionResources(const Nullkiller * ai) const;
TGoalVec missionResources(const Nullkiller * aiNk) const;
TGoalVec missionDestroyObj(const Nullkiller * aiNk) const;
TGoalVec missionIncreasePrimaryStat(const Nullkiller * ai) const;
TGoalVec missionLevel(const Nullkiller * ai) const;
TGoalVec missionKeymaster(const Nullkiller * ai) const;
TGoalVec missionIncreasePrimaryStat(const Nullkiller * aiNk) const;
TGoalVec missionLevel(const Nullkiller * aiNk) const;
TGoalVec missionKeymaster(const Nullkiller * aiNk) const;
std::string questToString() const;
};
}

View File

@@ -27,7 +27,7 @@ namespace Goals
{
priority = -1;
}
TGoalVec decompose(const Nullkiller * ai) const override
TGoalVec decompose(const Nullkiller * aiNk) const override
{
return TGoalVec();
}

View File

@@ -28,7 +28,7 @@ private:
std::shared_ptr<CCallback> cb; //this is enough, but we downcast from CCallback
public:
ArmyFormation(std::shared_ptr<CCallback> CB, const Nullkiller * ai): cb(CB) {}
ArmyFormation(std::shared_ptr<CCallback> CB, const Nullkiller * aiNk): cb(CB) {}
void rearrangeArmyForSiege(const CGTownInstance * town, const CGHeroInstance * attacker);

View File

@@ -18,8 +18,8 @@ namespace NK2AI
std::map<ObjectInstanceID, std::unique_ptr<GraphPaths>> AIPathfinder::heroGraphs;
AIPathfinder::AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * ai)
:cb(cb), aiNk(ai)
AIPathfinder::AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * aiNk)
:cb(cb), aiNk(aiNk)
{
}

View File

@@ -44,7 +44,7 @@ private:
static std::map<ObjectInstanceID, std::unique_ptr<GraphPaths>> heroGraphs;
public:
AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * ai);
AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * aiNk);
void calculatePathInfo(std::vector<AIPath> & paths, const int3 & tile, bool includeGraph = false) const;
bool isTileAccessible(const HeroPtr & hero, const int3 & tile) const;
void updatePaths(const std::map<const CGHeroInstance *, HeroRole> & heroes, PathfinderSettings pathfinderSettings);

View File

@@ -23,17 +23,17 @@ namespace AIPathfinding
{
std::vector<std::shared_ptr<IPathfindingRule>> makeRuleset(
CPlayerSpecificInfoCallback * cb,
Nullkiller * ai,
Nullkiller * aiNk,
std::shared_ptr<AINodeStorage> nodeStorage,
bool allowBypassObjects)
{
std::vector<std::shared_ptr<IPathfindingRule>> rules = {
std::make_shared<AILayerTransitionRule>(cb, ai, nodeStorage),
std::make_shared<AILayerTransitionRule>(cb, aiNk, nodeStorage),
std::make_shared<DestinationActionRule>(),
std::make_shared<AIMovementToDestinationRule>(nodeStorage, allowBypassObjects),
std::make_shared<MovementCostRule>(),
std::make_shared<AIPreviousNodeRule>(nodeStorage),
std::make_shared<AIMovementAfterDestinationRule>(ai, cb, nodeStorage, allowBypassObjects)
std::make_shared<AIMovementAfterDestinationRule>(aiNk, cb, nodeStorage, allowBypassObjects)
};
return rules;

View File

@@ -56,7 +56,7 @@ namespace AIPathfinding
Goals::AdventureSpellCast(hero, spellToCast).accept(aiGw);
}
bool AdventureCastAction::canAct(const Nullkiller * ai, const AIPathNode * source) const
bool AdventureCastAction::canAct(const Nullkiller * aiNk, const AIPathNode * source) const
{
assert(hero == this->hero);

View File

@@ -38,7 +38,7 @@ namespace AIPathfinding
AIPathNode * dstMode,
const AIPathNode * srcNode) const override;
bool canAct(const Nullkiller * ai, const AIPathNode * source) const override;
bool canAct(const Nullkiller * aiNk, const AIPathNode * source) const override;
std::string toString() const override;
};

View File

@@ -37,7 +37,7 @@ namespace AIPathfinding
return Goals::sptr(Goals::Invalid());
}
bool BuildBoatAction::canAct(const Nullkiller * ai, const CGHeroInstance * hero, const TResources & reservedResources) const
bool BuildBoatAction::canAct(const Nullkiller * aiNk, const CGHeroInstance * hero, const TResources & reservedResources) const
{
if(cpsic->getPlayerRelations(hero->tempOwner, shipyard->getObject()->getOwner()) == PlayerRelations::ENEMIES)
{
@@ -63,16 +63,16 @@ namespace AIPathfinding
return true;
}
bool BuildBoatAction::canAct(const Nullkiller * ai, const AIPathNode * source) const
bool BuildBoatAction::canAct(const Nullkiller * aiNk, const AIPathNode * source) const
{
return canAct(ai, source->actor->hero, source->actor->armyCost);
return canAct(aiNk, source->actor->hero, source->actor->armyCost);
}
bool BuildBoatAction::canAct(const Nullkiller * ai, const AIPathNodeInfo & source) const
bool BuildBoatAction::canAct(const Nullkiller * aiNk, const AIPathNodeInfo & source) const
{
TResources res;
return canAct(ai, source.targetHero, res);
return canAct(aiNk, source.targetHero, res);
}
const CGObjectInstance * BuildBoatAction::targetObject() const

View File

@@ -62,9 +62,9 @@ namespace AIPathfinding
{
}
bool canAct(const Nullkiller * ai, const AIPathNode * source) const override;
bool canAct(const Nullkiller * ai, const AIPathNodeInfo & source) const override;
bool canAct(const Nullkiller * ai, const CGHeroInstance * hero, const TResources & reservedResources) const;
bool canAct(const Nullkiller * aiNk, const AIPathNode * source) const override;
bool canAct(const Nullkiller * aiNk, const AIPathNodeInfo & source) const override;
bool canAct(const Nullkiller * aiNk, const CGHeroInstance * hero, const TResources & reservedResources) const;
void execute(AIGateway * aiGw, const CGHeroInstance * hero) const override;

View File

@@ -21,7 +21,7 @@ namespace AIPathfinding
private:
public:
bool canAct(const Nullkiller * ai, const AIPathNode * source) const override
bool canAct(const Nullkiller * aiNk, const AIPathNode * source) const override
{
return true;
}

View File

@@ -19,14 +19,14 @@ namespace NK2AI
namespace AIPathfinding
{
bool QuestAction::canAct(const Nullkiller * ai, const AIPathNode * node) const
bool QuestAction::canAct(const Nullkiller * aiNk, const AIPathNode * node) const
{
return canAct(ai, node->actor->hero);
return canAct(aiNk, node->actor->hero);
}
bool QuestAction::canAct(const Nullkiller * ai, const AIPathNodeInfo & node) const
bool QuestAction::canAct(const Nullkiller * aiNk, const AIPathNodeInfo & node) const
{
return canAct(ai, node.targetHero);
return canAct(aiNk, node.targetHero);
}
bool QuestAction::canAct(const Nullkiller * aiNk, const CGHeroInstance * hero) const
@@ -45,7 +45,7 @@ namespace AIPathfinding
|| quest->checkQuest(hero);
}
Goals::TSubgoal QuestAction::decompose(const Nullkiller * ai, const CGHeroInstance * hero) const
Goals::TSubgoal QuestAction::decompose(const Nullkiller * aiNk, const CGHeroInstance * hero) const
{
return Goals::sptr(Goals::CompleteQuest(questInfo));
}

View File

@@ -28,11 +28,11 @@ namespace AIPathfinding
{
}
bool canAct(const Nullkiller * ai, const AIPathNode * node) const override;
bool canAct(const Nullkiller * ai, const AIPathNodeInfo & node) const override;
bool canAct(const Nullkiller * aiNk, const AIPathNode * node) const override;
bool canAct(const Nullkiller * aiNk, const AIPathNodeInfo & node) const override;
bool canAct(const Nullkiller * aiNk, const CGHeroInstance * hero) const;
Goals::TSubgoal decompose(const Nullkiller * ai, const CGHeroInstance * hero) const override;
Goals::TSubgoal decompose(const Nullkiller * aiNk, const CGHeroInstance * hero) const override;
void execute(AIGateway * aiGw, const CGHeroInstance * hero) const override;

View File

@@ -17,7 +17,7 @@
namespace NK2AI
{
Goals::TSubgoal SpecialAction::decompose(const Nullkiller * ai, const CGHeroInstance * hero) const
Goals::TSubgoal SpecialAction::decompose(const Nullkiller * aiNk, const CGHeroInstance * hero) const
{
return Goals::sptr(Goals::Invalid());
}
@@ -27,26 +27,26 @@ void SpecialAction::execute(AIGateway * aiGw, const CGHeroInstance * hero) const
throw cannotFulfillGoalException("Can not execute " + toString());
}
bool CompositeAction::canAct(const Nullkiller * ai, const AIPathNode * source) const
bool CompositeAction::canAct(const Nullkiller * aiNk, const AIPathNode * source) const
{
for(auto part : parts)
{
if(!part->canAct(ai, source)) return false;
if(!part->canAct(aiNk, source)) return false;
}
return true;
}
Goals::TSubgoal CompositeAction::decompose(const Nullkiller * ai, const CGHeroInstance * hero) const
Goals::TSubgoal CompositeAction::decompose(const Nullkiller * aiNk, const CGHeroInstance * hero) const
{
for(auto part : parts)
{
auto goal = part->decompose(ai, hero);
auto goal = part->decompose(aiNk, hero);
if(!goal->invalid()) return goal;
}
return SpecialAction::decompose(ai, hero);
return SpecialAction::decompose(aiNk, hero);
}
void CompositeAction::execute(AIGateway * aiGw, const CGHeroInstance * hero) const

View File

@@ -30,17 +30,17 @@ class SpecialAction
public:
virtual ~SpecialAction() = default;
virtual bool canAct(const Nullkiller * ai, const AIPathNode * source) const
virtual bool canAct(const Nullkiller * aiNk, const AIPathNode * source) const
{
return true;
}
virtual bool canAct(const Nullkiller * ai, const AIPathNodeInfo & source) const
virtual bool canAct(const Nullkiller * aiNk, const AIPathNodeInfo & source) const
{
return true;
}
virtual Goals::TSubgoal decompose(const Nullkiller * ai, const CGHeroInstance * hero) const;
virtual Goals::TSubgoal decompose(const Nullkiller * aiNk, const CGHeroInstance * hero) const;
virtual void execute(AIGateway * aiGw, const CGHeroInstance * hero) const;
@@ -76,11 +76,11 @@ private:
public:
CompositeAction(std::vector<std::shared_ptr<const SpecialAction>> parts) : parts(parts) {}
bool canAct(const Nullkiller * ai, const AIPathNode * source) const override;
bool canAct(const Nullkiller * aiNk, const AIPathNode * source) const override;
void execute(AIGateway * aiGw, const CGHeroInstance * hero) const override;
std::string toString() const override;
const CGObjectInstance * targetObject() const override;
Goals::TSubgoal decompose(const Nullkiller * ai, const CGHeroInstance * hero) const override;
Goals::TSubgoal decompose(const Nullkiller * aiNk, const CGHeroInstance * hero) const override;
std::vector<std::shared_ptr<const SpecialAction>> getParts() const override
{
@@ -98,7 +98,7 @@ public:
class ISpecialActionFactory
{
public:
virtual std::shared_ptr<SpecialAction> create(const Nullkiller * ai) = 0;
virtual std::shared_ptr<SpecialAction> create(const Nullkiller * aiNk) = 0;
virtual ~ISpecialActionFactory() = default;
};

View File

@@ -97,10 +97,10 @@ std::string ObjectActor::toString() const
return object->getObjectName() + " at " + object->visitablePos().toString();
}
HeroActor::HeroActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t chainMask, const Nullkiller * ai)
HeroActor::HeroActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t chainMask, const Nullkiller * aiNk)
:ChainActor(hero, heroRole, chainMask)
{
exchangeMap.reset(new HeroExchangeMap(this, ai));
exchangeMap.reset(new HeroExchangeMap(this, aiNk));
setupSpecialActors();
}
@@ -108,10 +108,10 @@ HeroActor::HeroActor(
const ChainActor * carrier,
const ChainActor * other,
const HeroExchangeArmy * army,
const Nullkiller * ai)
const Nullkiller * aiNk)
:ChainActor(carrier, other, army)
{
exchangeMap.reset(new HeroExchangeMap(this, ai));
exchangeMap.reset(new HeroExchangeMap(this, aiNk));
armyCost += army->armyCost;
actorAction = army->getActorAction();
setupSpecialActors();
@@ -185,8 +185,8 @@ ExchangeResult HeroActor::tryExchangeNoLock(const ChainActor * specialActor, con
return result;
}
HeroExchangeMap::HeroExchangeMap(const HeroActor * actor, const Nullkiller * ai)
:actor(actor), aiNk(ai), sync()
HeroExchangeMap::HeroExchangeMap(const HeroActor * actor, const Nullkiller * aiNk)
:actor(actor), aiNk(aiNk), sync()
{
}

View File

@@ -96,7 +96,7 @@ private:
std::shared_mutex sync;
public:
HeroExchangeMap(const HeroActor * actor, const Nullkiller * ai);
HeroExchangeMap(const HeroActor * actor, const Nullkiller * aiNk);
~HeroExchangeMap();
ExchangeResult tryExchangeNoLock(const ChainActor * other);
@@ -121,8 +121,8 @@ public:
std::shared_ptr<SpecialAction> exchangeAction;
// chain flags, can be combined meaning hero exchange and so on
HeroActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t chainMask, const Nullkiller * ai);
HeroActor(const ChainActor * carrier, const ChainActor * other, const HeroExchangeArmy * army, const Nullkiller * ai);
HeroActor(const CGHeroInstance * hero, HeroRole heroRole, uint64_t chainMask, const Nullkiller * aiNk);
HeroActor(const ChainActor * carrier, const ChainActor * other, const HeroExchangeArmy * army, const Nullkiller * aiNk);
protected:
ExchangeResult tryExchangeNoLock(const ChainActor * specialActor, const ChainActor * other) const override;

View File

@@ -33,14 +33,14 @@ GraphPaths::GraphPaths()
}
std::shared_ptr<SpecialAction> getCompositeAction(
const Nullkiller * ai,
const Nullkiller * aiNk,
std::shared_ptr<ISpecialActionFactory> linkActionFactory,
std::shared_ptr<SpecialAction> transitionAction)
{
if(!linkActionFactory)
return transitionAction;
auto linkAction = linkActionFactory->create(ai);
auto linkAction = linkActionFactory->create(aiNk);
if(!transitionAction)
return linkAction;

View File

@@ -20,8 +20,8 @@
namespace NK2AI
{
ObjectGraphCalculator::ObjectGraphCalculator(ObjectGraph * target, const Nullkiller * ai)
:aiNk(ai), target(target), syncLock()
ObjectGraphCalculator::ObjectGraphCalculator(ObjectGraph * target, const Nullkiller * aiNk)
:aiNk(aiNk), target(target), syncLock()
{
}

View File

@@ -37,7 +37,7 @@ private:
std::vector<std::unique_ptr<CGHeroInstance>> temporaryActorHeroes;
public:
ObjectGraphCalculator(ObjectGraph * target, const Nullkiller * ai);
ObjectGraphCalculator(ObjectGraph * target, const Nullkiller * aiNk);
void setGraphObjects();
void calculateConnections();
float getNeighborConnectionsCost(const int3 & pos, std::vector<AIPath> & pathCache);

View File

@@ -21,9 +21,9 @@ namespace AIPathfinding
{
AILayerTransitionRule::AILayerTransitionRule(
CPlayerSpecificInfoCallback * cb,
Nullkiller * ai,
Nullkiller * aiNk,
std::shared_ptr<AINodeStorage> nodeStorage)
:cb(cb), aiNk(ai), nodeStorage(nodeStorage)
:cb(cb), aiNk(aiNk), nodeStorage(nodeStorage)
{
setup();
}

View File

@@ -35,7 +35,7 @@ namespace AIPathfinding
public:
AILayerTransitionRule(
CPlayerSpecificInfoCallback * cb,
Nullkiller * ai,
Nullkiller * aiNk,
std::shared_ptr<AINodeStorage> nodeStorage);
virtual void process(

View File

@@ -23,11 +23,11 @@ namespace NK2AI
namespace AIPathfinding
{
AIMovementAfterDestinationRule::AIMovementAfterDestinationRule(
const Nullkiller * ai,
const Nullkiller * aiNk,
CPlayerSpecificInfoCallback * cb,
std::shared_ptr<AINodeStorage> nodeStorage,
bool allowBypassObjects)
:aiNk(ai), cb(cb), nodeStorage(nodeStorage), allowBypassObjects(allowBypassObjects)
:aiNk(aiNk), cb(cb), nodeStorage(nodeStorage), allowBypassObjects(allowBypassObjects)
{
}

View File

@@ -29,7 +29,7 @@ namespace AIPathfinding
public:
AIMovementAfterDestinationRule(
const Nullkiller * ai,
const Nullkiller * aiNk,
CPlayerSpecificInfoCallback * cb,
std::shared_ptr<AINodeStorage> nodeStorage,
bool allowBypassObjects);

View File

@@ -140,6 +140,15 @@ if(ENABLE_ERM)
)
endif()
if(ENABLE_NULLKILLER_AI)
list(APPEND test_SRCS
nullkiller2/Behaviors/RecruitHeroBehaviorTest.cpp
)
list(APPEND test_HEADERS
)
endif()
assign_source_group(${test_SRCS} ${test_HEADERS})
set(mock_HEADERS
@@ -182,6 +191,12 @@ target_link_libraries(vcmitest PRIVATE gtest gmock vcmi ${SYSTEM_LIBS})
if(ENABLE_LUA)
target_link_libraries(vcmitest PRIVATE vcmiLua)
endif()
if(ENABLE_NULLKILLER_AI)
target_link_libraries(vcmitest PRIVATE vcmi fuzzylite::fuzzylite TBB::tbb)
# TODO: Mircea: fix code to eliminate all linking issues, then remove the options below
target_link_options(vcmitest PRIVATE -Wl,--warn-unresolved-symbols)
# target_link_libraries(vcmitest PUBLIC Nullkiller2)
endif()
target_include_directories(vcmitest
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}

View File

@@ -0,0 +1,31 @@
/*
* PriorityEvaluatorTest.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 "AI/Nullkiller2/Behaviors/RecruitHeroBehavior.h"
#include "AI/Nullkiller2/Engine/Nullkiller.h"
class MockNullkiller : public NK2AI::Nullkiller
{
public:
~MockNullkiller() override = default;
MOCK_METHOD(void, makeTurn, (), (override));
};
TEST(Nullkiller2_Behaviors_RecruitHeroBehavior, calculateBestHero)
{
EXPECT_EQ(1, 1);
auto behavior = NK2AI::Goals::RecruitHeroBehavior();
EXPECT_FALSE(behavior.invalid());
EXPECT_EQ(1, 1);
auto * const aiNk = new MockNullkiller();
EXPECT_CALL(*aiNk, makeTurn()).Times(1);
aiNk->makeTurn();
delete aiNk;
}