1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-27 22:49:25 +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; Nullkiller * aiNk;
public: public:
explicit BuildAnalyzer(Nullkiller * ai) : aiNk(ai) {} explicit BuildAnalyzer(Nullkiller * aiNk) : aiNk(aiNk) {}
void update(); void update();
TResources getResourcesRequiredNow() const; TResources getResourcesRequiredNow() const;

View File

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

View File

@@ -97,6 +97,7 @@ float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const
float HeroManager::evaluateFightingStrength(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); 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); std::sort(myHeroes.begin(), myHeroes.end(), scoreSort);
heroRoles.clear(); heroToRoleMap.clear();
for(auto hero : myHeroes) for(auto hero : myHeroes)
{ {
if(hero->patrol.patrolling) if(hero->patrol.patrolling)
{ {
heroRoles[hero] = HeroRole::MAIN; heroToRoleMap[hero] = HeroRole::MAIN;
} }
else else
{ {
heroRoles[hero] = (globalMainCount--) > 0 ? HeroRole::MAIN : HeroRole::SCOUT; heroToRoleMap[hero] = (globalMainCount--) > 0 ? HeroRole::MAIN : HeroRole::SCOUT;
} }
} }
for(auto hero : myHeroes) 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 HeroRole HeroManager::getHeroRole(const HeroPtr & hero) const
{ {
if (heroRoles.find(hero) != heroRoles.end()) if (heroToRoleMap.find(hero) != heroToRoleMap.end())
return heroRoles.at(hero); return heroToRoleMap.at(hero);
else else
return HeroRole::SCOUT; 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 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 CCallback * cc; //this is enough, but we downcast from CCallback
const Nullkiller * aiNk; const Nullkiller * aiNk;
std::map<HeroPtr, HeroRole> heroRoles; std::map<HeroPtr, HeroRole> heroToRoleMap;
std::map<ObjectInstanceID, float> knownFightingStrength; std::map<ObjectInstanceID, float> knownFightingStrength;
public: public:
HeroManager(CCallback * cc, const Nullkiller * ai) : cc(cc), aiNk(ai) {} HeroManager(CCallback * cc, const Nullkiller * aiNk) : cc(cc), aiNk(aiNk) {}
const std::map<HeroPtr, HeroRole> & getHeroRoles() const; const std::map<HeroPtr, HeroRole> & getHeroToRoleMap() const;
HeroRole getHeroRole(const HeroPtr & hero) const; HeroRole getHeroRole(const HeroPtr & hero) const;
int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const; int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const;
void update(); void update();

View File

@@ -81,7 +81,7 @@ public:
const CGObjectInstance * getBlocker(const AIPath & path) const; const CGObjectInstance * getBlocker(const AIPath & path) const;
std::optional<const CGObjectInstance *> getBlocker(const AIPathNodeInfo & node) 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 validateObjects();
void onObjectRemoved(ObjectInstanceID id); void onObjectRemoved(ObjectInstanceID id);

View File

@@ -24,7 +24,7 @@ namespace Goals
std::string toString() const override; std::string toString() const override;
bool operator==(const BuyArmyBehavior & other) 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 "StdInc.h"
#include "RecruitHeroBehavior.h" #include "RecruitHeroBehavior.h"
#include <algorithm>
#include "../AIGateway.h" #include "../AIGateway.h"
#include "../AIUtility.h" #include "../AIUtility.h"
#include "../Goals/RecruitHero.h" #include "../Goals/RecruitHero.h"
@@ -28,124 +30,119 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * aiNk) const
{ {
Goals::TGoalVec tasks; Goals::TGoalVec tasks;
const auto ourTowns = aiNk->cc->getTownsInfo(); const auto ourTowns = aiNk->cc->getTownsInfo();
const auto ourHeroes = aiNk->heroManager->getHeroRoles(); const auto ourHeroes = aiNk->heroManager->getHeroToRoleMap();
auto minScoreToHireMain = std::numeric_limits<float>::max(); RecruitHeroChoice bestChoice;
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;
bool haveCapitol = false; bool haveCapitol = false;
int treasureSourcesCount = 0;
// Simplification: Moved this call before getting into the decomposer // Simplification: Moved this call before getting into the decomposer
// aiNk->dangerHitMap->updateHitMap(); // aiNk->dangerHitMap->updateHitMap();
int treasureSourcesCount = 0;
int bestClosestThreat = UINT8_MAX;
for(const auto * town : ourTowns) for(const auto * town : ourTowns)
{ {
uint8_t closestThreat = UINT8_MAX; if(town->getVisitingHero() && town->getGarrisonHero())
for (const auto & threat : aiNk->dangerHitMap->getTownThreats(town))
{
closestThreat = std::min(closestThreat, threat.turn);
}
if (town->getVisitingHero() && town->getGarrisonHero())
continue; continue;
float visitability = 0; uint8_t closestThreatTurn = UINT8_MAX;
for (const auto & hero : ourHeroes) for(const auto & threat : aiNk->dangerHitMap->getTownThreats(town))
{ {
if (aiNk->dangerHitMap->getClosestTown(hero.first.get()->visitablePos()) == town) closestThreatTurn = std::min(closestThreatTurn, threat.turn);
visitability++; }
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)) if(aiNk->heroManager->canRecruitHero(town))
{ {
auto availableHeroes = aiNk->cc->getAvailableHeroes(town); calculateTreasureSources(aiNk->objectClusterizer->getNearbyObjects(), aiNk->playerID, *aiNk->dangerHitMap, treasureSourcesCount, town);
calculateBestHero(aiNk->cc->getAvailableHeroes(town),
for (const auto * obj : aiNk->objectClusterizer->getNearbyObjects()) *aiNk->heroManager,
{ bestChoice,
if (obj->ID == Obj::RESOURCE town,
|| obj->ID == Obj::TREASURE_CHEST closestThreatTurn,
|| obj->ID == Obj::CAMPFIRE visitabilityRatio);
|| 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?
}
} }
for(const auto hero : availableHeroes) if(town->hasCapitol())
{
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())
haveCapitol = true; haveCapitol = true;
} }
if (bestHeroToHire && bestTownToHireFrom) if(!vstd::isAlmostZero(bestChoice.score))
{ {
if (aiNk->cc->getHeroesInfo().size() == 0 if(ourHeroes.empty()
|| treasureSourcesCount > aiNk->cc->getHeroesInfo().size() * 5 || treasureSourcesCount > ourHeroes.size() * 5
|| (bestHeroToHire->getArmyCost() > GameConstants::HERO_GOLD_COST / 2.0 && (bestClosestThreat < 1 || !aiNk->buildAnalyzer->isGoldPressureOverMax())) // 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] > 10000 && !aiNk->buildAnalyzer->isGoldPressureOverMax() && haveCapitol)
|| (aiNk->getFreeResources()[EGameResID::GOLD] > 30000 && !aiNk->buildAnalyzer->isGoldPressureOverMax())) || (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; 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 "lib/GameLibrary.h"
#include "../Goals/CGoal.h" #include "../Goals/CGoal.h"
#include "../AIUtility.h" #include "../AIUtility.h"
#include "../Analyzers/DangerHitMapAnalyzer.h"
#include "../Analyzers/HeroManager.h"
namespace NK2AI namespace NK2AI
{ {
namespace Goals namespace Goals
{ {
class RecruitHeroBehavior : public CGoal<RecruitHeroBehavior> struct RecruitHeroChoice
{ {
public: 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() RecruitHeroBehavior()
:CGoal(RECRUIT_HERO_BEHAVIOR) : CGoal(RECRUIT_HERO_BEHAVIOR)
{ {
} }
~RecruitHeroBehavior() override = default;
TGoalVec decompose(const Nullkiller * aiNk) const override; TGoalVec decompose(const Nullkiller * aiNk) const override;
std::string toString() const override; std::string toString() const override;
bool operator==(const RecruitHeroBehavior & other) 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; using namespace Goals;
DeepDecomposer::DeepDecomposer(const Nullkiller * aiNk) 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) 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) bool isEquivalentGoals(TSubgoal goal1, TSubgoal goal2)
@@ -159,7 +159,7 @@ bool isEquivalentGoals(TSubgoal goal1, TSubgoal goal2)
bool DeepDecomposer::isCompositionLoop(TSubgoal goal) 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) for(auto goalToTest : goalsToTest)
{ {
@@ -209,7 +209,7 @@ TGoalVec DeepDecomposer::decomposeCached(TSubgoal goal, bool & fromCache)
fromCache = false; fromCache = false;
return goal->decompose(ai); return goal->decompose(aiNk);
} }
void DeepDecomposer::addToCache(TSubgoal goal) void DeepDecomposer::addToCache(TSubgoal goal)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -71,20 +71,15 @@ std::string AbstractGoal::toString() const
return desc; return desc;
} }
bool AbstractGoal::operator==(const AbstractGoal & g) const
{
return false;
}
//TODO: find out why the following are not generated automatically on MVS? //TODO: find out why the following are not generated automatically on MVS?
bool TSubgoal::operator==(const TSubgoal & rhs) const 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 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 CGHeroInstance * hero; SETTER(CGHeroInstance *, hero)
const CGTownInstance *town; SETTER(CGTownInstance *, town) const CGTownInstance *town; SETTER(CGTownInstance *, town)
int bid; SETTER(int, bid) 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; value = 0;
aid = -1; aid = -1;
resID = -1; resID = -1;
@@ -126,25 +128,31 @@ namespace Goals
bid = -1; bid = -1;
goldCost = 0; goldCost = 0;
} }
virtual ~AbstractGoal() {}
virtual ~AbstractGoal() = default;
//FIXME: abstract goal should be abstract, but serializer fails to instantiate subgoals in such case //FIXME: abstract goal should be abstract, but serializer fails to instantiate subgoals in such case
virtual AbstractGoal * clone() const virtual AbstractGoal * clone() const
{ {
return const_cast<AbstractGoal *>(this); 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(); return TGoalVec();
} }
EGoals goalType;
virtual std::string toString() const; 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; } 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 void accept(AIGateway * aiGw) = 0; //unhandled goal will report standard error
virtual std::string toString() const = 0; virtual std::string toString() const = 0;
virtual const CGHeroInstance * getHero() const = 0; virtual const CGHeroInstance * getHero() const = 0;
virtual ~ITask() {} virtual ~ITask() = default;
virtual int getHeroExchangeCount() const = 0; virtual int getHeroExchangeCount() const = 0;
virtual bool isObjectAffected(ObjectInstanceID h) const = 0; virtual bool isObjectAffected(ObjectInstanceID h) const = 0;
virtual std::vector<ObjectInstanceID> getAffectedObjects() const = 0; virtual std::vector<ObjectInstanceID> getAffectedObjects() const = 0;

View File

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

View File

@@ -31,37 +31,37 @@ std::string CompleteQuest::toString() const
return "Complete quest " + questToString(); return "Complete quest " + questToString();
} }
TGoalVec CompleteQuest::decompose(const Nullkiller * ai) const TGoalVec CompleteQuest::decompose(const Nullkiller * aiNk) const
{ {
if(isKeyMaster(q)) if(isKeyMaster(q))
{ {
return missionKeymaster(ai); return missionKeymaster(aiNk);
} }
logAi->debug("Trying to realize quest: %s", questToString()); logAi->debug("Trying to realize quest: %s", questToString());
auto quest = q.getQuest(ccTl); auto quest = q.getQuest(ccTl);
if(!quest->mission.artifacts.empty()) if(!quest->mission.artifacts.empty())
return missionArt(ai); return missionArt(aiNk);
if(!quest->mission.heroes.empty()) if(!quest->mission.heroes.empty())
return missionHero(ai); return missionHero(aiNk);
if(!quest->mission.creatures.empty()) if(!quest->mission.creatures.empty())
return missionArmy(ai); return missionArmy(aiNk);
if(quest->mission.resources.nonZero()) if(quest->mission.resources.nonZero())
return missionResources(ai); return missionResources(aiNk);
if(quest->killTarget != ObjectInstanceID::NONE) if(quest->killTarget != ObjectInstanceID::NONE)
return missionDestroyObj(ai); return missionDestroyObj(aiNk);
for(auto & s : quest->mission.primary) for(auto & s : quest->mission.primary)
if(s) if(s)
return missionIncreasePrimaryStat(ai); return missionIncreasePrimaryStat(aiNk);
if(quest->mission.heroLevel > 0) if(quest->mission.heroLevel > 0)
return missionLevel(ai); return missionLevel(aiNk);
return TGoalVec(); return TGoalVec();
} }
@@ -118,9 +118,9 @@ TGoalVec CompleteQuest::tryCompleteQuest(const Nullkiller * aiNk) const
return CaptureObjectsBehavior::getVisitGoals(paths, aiNk, q.getObject(ccTl)); 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()) if(!solutions.empty())
return solutions; return solutions;
@@ -135,9 +135,9 @@ TGoalVec CompleteQuest::missionArt(const Nullkiller * ai) const
return solutions; 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()) if(solutions.empty())
{ {
@@ -160,31 +160,31 @@ TGoalVec CompleteQuest::missionArmy(const Nullkiller * aiNk) const
return CaptureObjectsBehavior::getVisitGoals(paths, aiNk, q.getObject(ccTl)); 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 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; 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; std::string toString() const override;
bool hasHash() const override { return true; } bool hasHash() const override { return true; }
uint64_t getHash() const override; uint64_t getHash() const override;
@@ -37,14 +37,14 @@ namespace Goals
private: private:
TGoalVec tryCompleteQuest(const Nullkiller * aiNk) const; TGoalVec tryCompleteQuest(const Nullkiller * aiNk) const;
TGoalVec missionArt(const Nullkiller * ai) const; TGoalVec missionArt(const Nullkiller * aiNk) const;
TGoalVec missionHero(const Nullkiller * ai) const; TGoalVec missionHero(const Nullkiller * aiNk) const;
TGoalVec missionArmy(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 missionDestroyObj(const Nullkiller * aiNk) const;
TGoalVec missionIncreasePrimaryStat(const Nullkiller * ai) const; TGoalVec missionIncreasePrimaryStat(const Nullkiller * aiNk) const;
TGoalVec missionLevel(const Nullkiller * ai) const; TGoalVec missionLevel(const Nullkiller * aiNk) const;
TGoalVec missionKeymaster(const Nullkiller * ai) const; TGoalVec missionKeymaster(const Nullkiller * aiNk) const;
std::string questToString() const; std::string questToString() const;
}; };
} }

View File

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

View File

@@ -28,7 +28,7 @@ private:
std::shared_ptr<CCallback> cb; //this is enough, but we downcast from CCallback std::shared_ptr<CCallback> cb; //this is enough, but we downcast from CCallback
public: 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); 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; std::map<ObjectInstanceID, std::unique_ptr<GraphPaths>> AIPathfinder::heroGraphs;
AIPathfinder::AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * ai) AIPathfinder::AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * aiNk)
:cb(cb), aiNk(ai) :cb(cb), aiNk(aiNk)
{ {
} }

View File

@@ -44,7 +44,7 @@ private:
static std::map<ObjectInstanceID, std::unique_ptr<GraphPaths>> heroGraphs; static std::map<ObjectInstanceID, std::unique_ptr<GraphPaths>> heroGraphs;
public: public:
AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * ai); AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * aiNk);
void calculatePathInfo(std::vector<AIPath> & paths, const int3 & tile, bool includeGraph = false) const; void calculatePathInfo(std::vector<AIPath> & paths, const int3 & tile, bool includeGraph = false) const;
bool isTileAccessible(const HeroPtr & hero, const int3 & tile) const; bool isTileAccessible(const HeroPtr & hero, const int3 & tile) const;
void updatePaths(const std::map<const CGHeroInstance *, HeroRole> & heroes, PathfinderSettings pathfinderSettings); 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( std::vector<std::shared_ptr<IPathfindingRule>> makeRuleset(
CPlayerSpecificInfoCallback * cb, CPlayerSpecificInfoCallback * cb,
Nullkiller * ai, Nullkiller * aiNk,
std::shared_ptr<AINodeStorage> nodeStorage, std::shared_ptr<AINodeStorage> nodeStorage,
bool allowBypassObjects) bool allowBypassObjects)
{ {
std::vector<std::shared_ptr<IPathfindingRule>> rules = { 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<DestinationActionRule>(),
std::make_shared<AIMovementToDestinationRule>(nodeStorage, allowBypassObjects), std::make_shared<AIMovementToDestinationRule>(nodeStorage, allowBypassObjects),
std::make_shared<MovementCostRule>(), std::make_shared<MovementCostRule>(),
std::make_shared<AIPreviousNodeRule>(nodeStorage), std::make_shared<AIPreviousNodeRule>(nodeStorage),
std::make_shared<AIMovementAfterDestinationRule>(ai, cb, nodeStorage, allowBypassObjects) std::make_shared<AIMovementAfterDestinationRule>(aiNk, cb, nodeStorage, allowBypassObjects)
}; };
return rules; return rules;

View File

@@ -56,7 +56,7 @@ namespace AIPathfinding
Goals::AdventureSpellCast(hero, spellToCast).accept(aiGw); 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); assert(hero == this->hero);

View File

@@ -38,7 +38,7 @@ namespace AIPathfinding
AIPathNode * dstMode, AIPathNode * dstMode,
const AIPathNode * srcNode) const override; 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; std::string toString() const override;
}; };

View File

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

View File

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

View File

@@ -19,14 +19,14 @@ namespace NK2AI
namespace AIPathfinding 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 bool QuestAction::canAct(const Nullkiller * aiNk, const CGHeroInstance * hero) const
@@ -45,7 +45,7 @@ namespace AIPathfinding
|| quest->checkQuest(hero); || 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)); 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 * aiNk, const AIPathNode * node) const override;
bool canAct(const Nullkiller * ai, const AIPathNodeInfo & node) const override; bool canAct(const Nullkiller * aiNk, const AIPathNodeInfo & node) const override;
bool canAct(const Nullkiller * aiNk, const CGHeroInstance * hero) const; 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; void execute(AIGateway * aiGw, const CGHeroInstance * hero) const override;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -23,11 +23,11 @@ namespace NK2AI
namespace AIPathfinding namespace AIPathfinding
{ {
AIMovementAfterDestinationRule::AIMovementAfterDestinationRule( AIMovementAfterDestinationRule::AIMovementAfterDestinationRule(
const Nullkiller * ai, const Nullkiller * aiNk,
CPlayerSpecificInfoCallback * cb, CPlayerSpecificInfoCallback * cb,
std::shared_ptr<AINodeStorage> nodeStorage, std::shared_ptr<AINodeStorage> nodeStorage,
bool allowBypassObjects) 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: public:
AIMovementAfterDestinationRule( AIMovementAfterDestinationRule(
const Nullkiller * ai, const Nullkiller * aiNk,
CPlayerSpecificInfoCallback * cb, CPlayerSpecificInfoCallback * cb,
std::shared_ptr<AINodeStorage> nodeStorage, std::shared_ptr<AINodeStorage> nodeStorage,
bool allowBypassObjects); bool allowBypassObjects);

View File

@@ -140,6 +140,15 @@ if(ENABLE_ERM)
) )
endif() 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}) assign_source_group(${test_SRCS} ${test_HEADERS})
set(mock_HEADERS set(mock_HEADERS
@@ -182,6 +191,12 @@ target_link_libraries(vcmitest PRIVATE gtest gmock vcmi ${SYSTEM_LIBS})
if(ENABLE_LUA) if(ENABLE_LUA)
target_link_libraries(vcmitest PRIVATE vcmiLua) target_link_libraries(vcmitest PRIVATE vcmiLua)
endif() 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 target_include_directories(vcmitest
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} 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;
}