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

overall refactoring, HeroPtr adjustments, AiNodeStorage remove unnecessary callback

This commit is contained in:
Mircea TheHonestCTO
2025-09-04 00:07:12 +02:00
parent 19076ee29d
commit 7a40981a95
22 changed files with 209 additions and 208 deletions

View File

@@ -194,7 +194,7 @@ void AIGateway::gameOver(PlayerColor player, const EVictoryLossCheckResult & vic
logAi->debug("AIGateway: Player %d (%s) lost. It's me. What a disappointment! :(", player, player.toString());
}
nullkiller->makingTurnInterrupption.interruptThread();
nullkiller->makingTurnInterruption.interruptThread();
}
}
@@ -572,7 +572,7 @@ void AIGateway::yourTurn(QueryID queryID)
executeActionAsync("yourTurn", [this, queryID](){ answerQuery(queryID, 0); });
status.startedTurn();
nullkiller->makingTurnInterrupption.reset();
nullkiller->makingTurnInterruption.reset();
asyncTasks->run([this]()
{
@@ -593,11 +593,11 @@ void AIGateway::heroGotLevel(const CGHeroInstance * hero, PrimarySkill pskill, s
{
int sel = 0;
if(heroPtr.isValid())
if(heroPtr.isVerified())
{
std::unique_lock lockGuard(nullkiller->aiStateMutex);
nullkiller->heroManager->update();
sel = nullkiller->heroManager->selectBestSkill(heroPtr, skills);
sel = nullkiller->heroManager->selectBestSkillIndex(heroPtr, skills);
}
answerQuery(queryID, sel);
@@ -630,7 +630,7 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
bool answer = true;
auto objects = cc->getVisitableObjs(target);
if(heroPtr.isValid() && target.isValid() && !objects.empty())
if(heroPtr.isVerified() && target.isValid() && !objects.empty())
{
auto topObj = objects.front()->id == heroPtr->id ? objects.back() : objects.front();
auto objType = topObj->ID; // top object should be our hero
@@ -683,10 +683,10 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
std::unique_lock mxLock(nullkiller->aiStateMutex);
// TODO: Find better way to understand it is Chest of Treasures
if(heroPtr.isValid()
if(heroPtr.isVerified()
&& components.size() == 2
&& components.front().type == ComponentType::RESOURCE
&& (nullkiller->heroManager->getHeroRole(heroPtr) != HeroRole::MAIN
&& (nullkiller->heroManager->getHeroRoleOrDefault(heroPtr) != HeroRole::MAIN
|| nullkiller->buildAnalyzer->isGoldPressureOverMax()))
{
sel = 1;
@@ -873,7 +873,7 @@ void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h
if(!heroPtr->getVisitedTown()->getGarrisonHero() || !nullkiller->isHeroLocked(heroPtr->getVisitedTown()->getGarrisonHero()))
moveCreaturesToHero(heroPtr->getVisitedTown());
if(nullkiller->heroManager->getHeroRole(heroPtr) == HeroRole::MAIN && !heroPtr->hasSpellbook()
if(nullkiller->heroManager->getHeroRoleOrDefault(heroPtr) == HeroRole::MAIN && !heroPtr->hasSpellbook()
&& nullkiller->getFreeGold() >= GameConstants::SPELLBOOK_GOLD_COST)
{
if(heroPtr->getVisitedTown()->hasBuilt(BuildingID::MAGES_GUILD_1))
@@ -1406,7 +1406,7 @@ void AIGateway::buildArmyIn(const CGTownInstance * t)
void AIGateway::finish()
{
nullkiller->makingTurnInterrupption.interruptThread();
nullkiller->makingTurnInterruption.interruptThread();
if (asyncTasks)
{
@@ -1429,13 +1429,13 @@ void AIGateway::executeActionAsync(const std::string & description, const std::f
});
}
void AIGateway::lostHero(HeroPtr h)
void AIGateway::lostHero(const HeroPtr & heroPtr) const
{
logAi->debug("I lost my hero %s. It's best to forget and move on.", h.nameOrDefault());
logAi->debug("I lost my hero %s. It's best to forget and move on.", heroPtr.nameOrDefault());
nullkiller->invalidatePathfinderData();
}
void AIGateway::answerQuery(QueryID queryID, int selection)
void AIGateway::answerQuery(const QueryID queryID, const int selection) const
{
logAi->debug("I'll answer the query %d giving the choice %d", queryID, selection);
if(queryID != QueryID(-1))
@@ -1564,7 +1564,7 @@ void AIStatus::waitTillFree()
while(battle != NO_BATTLE || !remainingQueries.empty() || !objectsBeingVisited.empty() || ongoingHeroMovement)
{
cv.wait_for(lock, std::chrono::milliseconds(10));
aiGw->nullkiller->makingTurnInterrupption.interruptionPoint();
aiGw->nullkiller->makingTurnInterruption.interruptionPoint();
}
}

View File

@@ -207,7 +207,7 @@ public:
bool moveHeroToTile(int3 dst, const HeroPtr & heroPtr);
void buildStructure(const CGTownInstance * t, BuildingID building);
void lostHero(HeroPtr h); //should remove all references to hero (assigned tasks and so on)
void lostHero(const HeroPtr & heroPtr) const; //should remove all references to hero (assigned tasks and so on)
void waitTillFree();
void validateObject(const CGObjectInstance * obj); //checks if object is still visible and if not, removes references to it
@@ -215,7 +215,7 @@ public:
virtual std::vector<const CGObjectInstance *> getFlaggedObjects() const;
void requestSent(const CPackForServer * pack, int requestID) override;
void answerQuery(QueryID queryID, int selection);
void answerQuery(QueryID queryID, int selection) const;
//special function that can be called ONLY from game events handling thread and will send request ASAP
void executeActionAsync(const std::string & description, const std::function<void()> & whatToDo);

View File

@@ -78,17 +78,24 @@ ObjectInstanceID HeroPtr::idOrNone() const
std::string HeroPtr::nameOrDefault() const
{
if (hero)
return hero->getNameTextID();
return hero->getNameTextID() + "(" + hero->getNameTranslated() + ")";
return "<NO HERO>";
}
// enforces isVerified()
const CGHeroInstance * HeroPtr::get() const
{
assert(isValid());
assert(isVerified());
return hero;
}
bool HeroPtr::validate() const
// does not enforce isVerified()
const CGHeroInstance * HeroPtr::getUnverified() const
{
return hero;
}
bool HeroPtr::verify() const
{
// TODO: check if these all assertions every time we get info about hero affect efficiency
// behave terribly when attempting unauthorized access to hero that is not ours (or was lost)
@@ -101,7 +108,6 @@ bool HeroPtr::validate() const
if(!obj)
return false;
assert(hero == obj);
return true;
//assert(owned);
}
@@ -109,23 +115,27 @@ bool HeroPtr::validate() const
return false;
}
// enforces isVerified()
const CGHeroInstance * HeroPtr::operator->() const
{
assert(isValid());
assert(isVerified());
return hero;
}
bool HeroPtr::isValid() const
// Should be called before using the hero inside this HeroPtr, that's the point of this
bool HeroPtr::isVerified() const
{
return validate();
return verify();
}
// enforces isVerified()
const CGHeroInstance * HeroPtr::operator*() const
{
assert(isValid());
assert(isVerified());
return hero;
}
// enforces isVerified() on input
bool HeroPtr::operator==(const HeroPtr & rhs) const
{
return hero == rhs.get();
@@ -712,7 +722,7 @@ bool shouldVisit(const Nullkiller * aiNk, const CGHeroInstance * hero, const CGO
break;
case Obj::TREE_OF_KNOWLEDGE:
{
if(aiNk->heroManager->getHeroRole(hero) == HeroRole::SCOUT)
if(aiNk->heroManager->getHeroRoleOrDefaultInefficient(hero) == HeroRole::SCOUT)
return false;
TResources myRes = aiNk->getFreeResources();

View File

@@ -79,14 +79,14 @@ struct DLL_EXPORT HeroPtr
private:
const CGHeroInstance * hero;
std::shared_ptr<CPlayerSpecificInfoCallback> cpsic;
bool validate() const;
bool verify() const;
public:
explicit HeroPtr(const CGHeroInstance * input, std::shared_ptr<CPlayerSpecificInfoCallback> cpsic);
explicit operator bool() const
{
return isValid();
return isVerified();
}
bool operator<(const HeroPtr & rhs) const;
@@ -101,7 +101,8 @@ public:
ObjectInstanceID idOrNone() const;
std::string nameOrDefault() const;
const CGHeroInstance * get() const;
bool isValid() const;
const CGHeroInstance * getUnverified() const;
bool isVerified() const;
};
enum BattleState

View File

@@ -30,7 +30,7 @@ void logHitmap(PlayerColor playerID, DangerHitMapAnalyzer & data)
auto & threat = data.getTileThreat(pos).maximumDanger;
b.addText(pos, std::to_string(threat.danger));
if(threat.heroPtr.isValid())
if(threat.heroPtr.isVerified())
{
b.addText(pos, std::to_string(threat.turn));
b.addText(pos, threat.heroPtr->getNameTranslated());
@@ -45,7 +45,7 @@ void logHitmap(PlayerColor playerID, DangerHitMapAnalyzer & data)
auto & threat = data.getTileThreat(pos).fastestDanger;
b.addText(pos, std::to_string(threat.danger));
if(threat.heroPtr.isValid())
if(threat.heroPtr.isVerified())
{
b.addText(pos, std::to_string(threat.turn));
b.addText(pos, threat.heroPtr->getNameTranslated());
@@ -119,7 +119,7 @@ void DangerHitMapAnalyzer::updateHitMap()
aiNk->pathfinder->updatePaths(pair.second, ps);
aiNk->makingTurnInterrupption.interruptionPoint();
aiNk->makingTurnInterruption.interruptionPoint();
pforeachTilePaths(mapSize, aiNk, [&](const int3 & pos, const std::vector<AIPath> & paths)
{

View File

@@ -77,7 +77,9 @@ public:
const HitMapNode & getTileThreat(const int3 & tile) const;
std::set<const CGObjectInstance *> getOneTurnAccessibleObjects(const CGHeroInstance * enemy) const;
void resetHitmap() {hitMapUpToDate = false;}
bool isHitMapUpToDate() const { return hitMapUpToDate; }
void resetTileOwners() { tileOwnersUpToDate = false; }
bool isTileOwnersUpToDate() const { return tileOwnersUpToDate; }
PlayerColor getTileOwner(const int3 & tile) const;
const CGTownInstance * getClosestTown(const int3 & tile) const;
const std::vector<HitMapInfo> & getTownThreats(const CGTownInstance * town) const;

View File

@@ -18,7 +18,7 @@
namespace NK2AI
{
const SecondarySkillEvaluator HeroManager::wariorSkillsScores = SecondarySkillEvaluator(
const SecondarySkillEvaluator HeroManager::mainSkillsEvaluator = SecondarySkillEvaluator(
{
std::make_shared<SecondarySkillScoreMap>(
std::map<SecondarySkill, float>
@@ -47,7 +47,7 @@ const SecondarySkillEvaluator HeroManager::wariorSkillsScores = SecondarySkillEv
std::make_shared<AtLeastOneMagicRule>()
});
const SecondarySkillEvaluator HeroManager::scountSkillsScores = SecondarySkillEvaluator(
const SecondarySkillEvaluator HeroManager::scoutSkillsEvaluator = SecondarySkillEvaluator(
{
std::make_shared<SecondarySkillScoreMap>(
std::map<SecondarySkill, float>
@@ -62,12 +62,12 @@ const SecondarySkillEvaluator HeroManager::scountSkillsScores = SecondarySkillEv
float HeroManager::evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const
{
auto role = getHeroRole(hero);
auto role = getHeroRoleOrDefaultInefficient(hero);
if(role == HeroRole::MAIN)
return wariorSkillsScores.evaluateSecSkill(hero, skill);
return mainSkillsEvaluator.evaluateSecSkill(hero, skill);
return scountSkillsScores.evaluateSecSkill(hero, skill);
return scoutSkillsEvaluator.evaluateSecSkill(hero, skill);
}
float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const
@@ -85,7 +85,7 @@ float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const
if(hasBonus)
{
SecondarySkill bonusSkill = bonus->sid.as<SecondarySkill>();
float bonusScore = wariorSkillsScores.evaluateSecSkill(hero, bonusSkill);
float bonusScore = mainSkillsEvaluator.evaluateSecSkill(hero, bonusSkill);
if(bonusScore > 0)
specialityScore += bonusScore * bonusScore * bonusScore;
@@ -98,7 +98,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);
return evaluateSpeciality(hero) + mainSkillsEvaluator.evaluateSecSkills(hero) + hero->getBasePrimarySkillValue(PrimarySkill::ATTACK) + hero->getBasePrimarySkillValue(PrimarySkill::DEFENSE) + hero->getBasePrimarySkillValue(PrimarySkill::SPELL_POWER) + hero->getBasePrimarySkillValue(PrimarySkill::KNOWLEDGE);
}
void HeroManager::update()
@@ -120,64 +120,58 @@ void HeroManager::update()
};
int globalMainCount = std::min(((int)myHeroes.size() + 2) / 3, cc->getMapSize().x / 50 + 1);
//vstd::amin(globalMainCount, 1 + (cb->getTownsInfo().size() / 3));
if(cc->getTownsInfo().size() < 4 && globalMainCount > 2)
{
globalMainCount = 2;
}
// TODO: Mircea: Make it dependent on myHeroes.size() or better?
logAi->trace("Max number of main heroes (globalMainCount) is %d", globalMainCount);
std::sort(myHeroes.begin(), myHeroes.end(), scoreSort);
heroToRoleMap.clear();
for(auto hero : myHeroes)
for(const auto hero : myHeroes)
{
HeroPtr heroPtr(hero, aiNk->cc);
HeroRole role;
if(hero->patrol.patrolling)
{
heroToRoleMap[HeroPtr(hero, aiNk->cc)] = HeroRole::MAIN;
role = MAIN;
}
else
{
heroToRoleMap[HeroPtr(hero, aiNk->cc)] = (globalMainCount--) > 0 ? HeroRole::MAIN : HeroRole::SCOUT;
role = globalMainCount-- > 0 ? MAIN : SCOUT;
}
heroToRoleMap[heroPtr] = role;
logAi->trace("Hero %s has role %s", heroPtr.nameOrDefault(), role == MAIN ? "main" : "scout");
}
}
for(auto hero : myHeroes)
HeroRole HeroManager::getHeroRoleOrDefaultInefficient(const CGHeroInstance * hero) const
{
logAi->trace("Hero %s has role %s", hero->getNameTranslated(), heroToRoleMap[HeroPtr(hero, aiNk->cc)] == HeroRole::MAIN ? "main" : "scout");
}
}
HeroRole HeroManager::getHeroRole(const CGHeroInstance * hero) const
{
return getHeroRole(HeroPtr(hero, aiNk->cc));
return getHeroRoleOrDefault(HeroPtr(hero, aiNk->cc));
}
// TODO: Mircea: Do we need this map on HeroPtr or is enough just on hero?
HeroRole HeroManager::getHeroRole(const HeroPtr & heroPtr) const
HeroRole HeroManager::getHeroRoleOrDefault(const HeroPtr & heroPtr) const
{
if(heroToRoleMap.find(heroPtr) != heroToRoleMap.end())
return heroToRoleMap.at(heroPtr);
return HeroRole::SCOUT;
}
const std::map<HeroPtr, HeroRole> & HeroManager::getHeroToRoleMap() const
int HeroManager::selectBestSkillIndex(const HeroPtr & heroPtr, const std::vector<SecondarySkill> & skills) const
{
return heroToRoleMap;
}
int HeroManager::selectBestSkill(const HeroPtr & heroPtr, const std::vector<SecondarySkill> & skills) const
{
auto role = getHeroRole(heroPtr);
auto & evaluator = role == HeroRole::MAIN ? wariorSkillsScores : scountSkillsScores;
const auto role = getHeroRoleOrDefault(heroPtr);
const auto & evaluator = role == MAIN ? mainSkillsEvaluator : scoutSkillsEvaluator;
int result = 0;
float resultScore = -100;
for(int i = 0; i < skills.size(); i++)
{
auto score = evaluator.evaluateSecSkill(heroPtr.get(), skills[i]);
const auto score = evaluator.evaluateSecSkill(heroPtr.getUnverified(), skills[i]);
if(score > resultScore)
{
resultScore = score;
@@ -185,7 +179,7 @@ int HeroManager::selectBestSkill(const HeroPtr & heroPtr, const std::vector<Seco
}
logAi->trace(
"Hero %s is proposed to learn %d with score %f",
"Hero %s is offered to learn %d with score %f",
heroPtr.nameOrDefault(),
skills[i].toEnum(),
score);
@@ -307,7 +301,7 @@ const CGHeroInstance * HeroManager::findWeakHeroToDismiss(uint64_t armyLimit, co
{
if(aiNk->getHeroLockedReason(existingHero) == HeroLockedReason::DEFENCE
|| existingHero->getArmyStrength() >armyLimit
|| getHeroRole(existingHero) == HeroRole::MAIN
|| getHeroRoleOrDefaultInefficient(existingHero) == HeroRole::MAIN
|| existingHero->movementPointsRemaining()
|| (townToSpare != nullptr && existingHero->getVisitedTown() == townToSpare)
|| existingHero->artifactsWorn.size() > (existingHero->hasSpellbook() ? 2 : 1))

View File

@@ -39,20 +39,19 @@ public:
class DLL_EXPORT HeroManager
{
private:
static const SecondarySkillEvaluator wariorSkillsScores;
static const SecondarySkillEvaluator scountSkillsScores;
static const SecondarySkillEvaluator mainSkillsEvaluator;
static const SecondarySkillEvaluator scoutSkillsEvaluator;
CCallback * cc; //this is enough, but we downcast from CCallback
const Nullkiller * aiNk;
std::map<HeroPtr, HeroRole> heroToRoleMap;
std::map<HeroPtr, HeroRole> heroToRoleMap; // can get out of sync with cc->getHeroesInfo() and include lost heroes
std::map<ObjectInstanceID, float> knownFightingStrength;
public:
HeroManager(CCallback * cc, const Nullkiller * aiNk) : cc(cc), aiNk(aiNk) {}
const std::map<HeroPtr, HeroRole> & getHeroToRoleMap() const;
HeroRole getHeroRole(const CGHeroInstance * hero) const;
HeroRole getHeroRole(const HeroPtr & heroPtr) const;
int selectBestSkill(const HeroPtr & heroPtr, const std::vector<SecondarySkill> & skills) const;
HeroRole getHeroRoleOrDefaultInefficient(const CGHeroInstance * hero) const;
HeroRole getHeroRoleOrDefault(const HeroPtr & heroPtr) const;
int selectBestSkillIndex(const HeroPtr & heroPtr, const std::vector<SecondarySkill> & skills) const;
void update();
float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const;
float evaluateHero(const CGHeroInstance * hero) const;

View File

@@ -427,7 +427,7 @@ void ObjectClusterizer::clusterizeObject(
logAi->trace("ObjectClusterizer Checking path %s", path.toString());
#endif
if(aiNk->heroManager->getHeroRole(path.targetHero) == HeroRole::SCOUT)
if(aiNk->heroManager->getHeroRoleOrDefaultInefficient(path.targetHero) == HeroRole::SCOUT)
{
// TODO: Mircea: Shouldn't this be linked with scoutHeroTurnDistanceLimit?
// TODO: Mircea: Move to constant

View File

@@ -53,7 +53,7 @@ Goals::TGoalVec BuyArmyBehavior::decompose(const Nullkiller * aiNk) const
for(const CGHeroInstance * targetHero : heroes)
{
if(aiNk->heroManager->getHeroRole(targetHero) == HeroRole::MAIN)
if(aiNk->heroManager->getHeroRoleOrDefaultInefficient(targetHero) == HeroRole::MAIN)
{
auto reinforcement = aiNk->armyManager->howManyReinforcementsCanGet(
targetHero,

View File

@@ -82,7 +82,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(
if (hero->getOwner() != nullkiller->playerID)
continue;
if(nullkiller->heroManager->getHeroRole(hero) == HeroRole::SCOUT
if(nullkiller->heroManager->getHeroRoleOrDefaultInefficient(hero) == HeroRole::SCOUT
&& (path.getTotalDanger() == 0 || path.turn() > 0)
&& path.exchangeCount > 1)
{
@@ -132,13 +132,10 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(
{
auto newWay = new ExecuteHeroChain(path, objToVisit);
TSubgoal sharedPtr;
sharedPtr.reset(newWay);
auto heroRole = nullkiller->heroManager->getHeroRole(path.targetHero);
auto heroRole = nullkiller->heroManager->getHeroRoleOrDefaultInefficient(path.targetHero);
auto & closestWay = closestWaysByRole[heroRole];
if(!closestWay || closestWay->movementCost() > path.movementCost())
{
closestWay = &path;
@@ -152,15 +149,14 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(
}
}
for(auto way : waysToVisitObj)
for(auto * way : waysToVisitObj)
{
auto heroRole = nullkiller->heroManager->getHeroRole(way->getPath().targetHero);
auto closestWay = closestWaysByRole[heroRole];
auto heroRole = nullkiller->heroManager->getHeroRoleOrDefaultInefficient(way->getPath().targetHero);
const auto * closestWay = closestWaysByRole[heroRole];
if(closestWay)
{
way->closestWayRatio
= closestWay->movementCost() / way->getPath().movementCost();
way->closestWayRatio = closestWay->movementCost() / way->getPath().movementCost();
}
}
@@ -173,14 +169,10 @@ void CaptureObjectsBehavior::decomposeObjects(
const Nullkiller * nullkiller) const
{
if(objs.empty())
{
return;
}
std::mutex sync;
logAi->debug("Scanning objects, count %d", objs.size());
tbb::parallel_for(
tbb::blocked_range<size_t>(0, objs.size()),
[this, &objs, &sync, &result, nullkiller](const tbb::blocked_range<size_t> & r)
@@ -191,8 +183,7 @@ void CaptureObjectsBehavior::decomposeObjects(
for(auto i = r.begin(); i != r.end(); i++)
{
auto objToVisit = objs[i];
const auto * objToVisit = objs[i];
if(!objectMatchesFilter(objToVisit))
continue;
@@ -200,6 +191,7 @@ void CaptureObjectsBehavior::decomposeObjects(
logAi->trace("Checking object %s, %s", objToVisit->getObjectName(), objToVisit->visitablePos().toString());
#endif
// FIXME: Mircea: This one uses the deleted hero
nullkiller->pathfinder->calculatePathInfo(paths, objToVisit->visitablePos(), nullkiller->isObjectGraphAllowed());
#if NK2AI_TRACE_LEVEL >= 1

View File

@@ -84,7 +84,7 @@ void handleCounterAttack(
const Nullkiller * aiNk,
Goals::TGoalVec & tasks)
{
if(threat.heroPtr.isValid()
if(threat.heroPtr.isVerified()
&& threat.turn <= 1
&& (threat.danger == maximumDanger.danger || threat.turn < maximumDanger.turn))
{
@@ -132,7 +132,7 @@ bool handleGarrisonHeroFromPreviousTurn(const CGTownInstance * town, Goals::TGoa
return false;
}
else if(aiNk->heroManager->getHeroRole(town->getGarrisonHero()) == HeroRole::MAIN)
else if(aiNk->heroManager->getHeroRoleOrDefaultInefficient(town->getGarrisonHero()) == HeroRole::MAIN)
{
auto armyDismissLimit = 1000;
auto heroToDismiss = aiNk->heroManager->findWeakHeroToDismiss(armyDismissLimit);

View File

@@ -43,7 +43,7 @@ Goals::TGoalVec GatherArmyBehavior::decompose(const Nullkiller * aiNk) const
for(const CGHeroInstance * hero : heroes)
{
if(aiNk->heroManager->getHeroRole(hero) == HeroRole::MAIN)
if(aiNk->heroManager->getHeroRoleOrDefaultInefficient(hero) == HeroRole::MAIN)
{
vstd::concatenate(tasks, deliverArmyToHero(aiNk, hero));
}
@@ -121,7 +121,7 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const Nullkiller * aiNk, c
{
if(!node.targetHero) continue;
auto heroRole = aiNk->heroManager->getHeroRole(node.targetHero);
auto heroRole = aiNk->heroManager->getHeroRoleOrDefaultInefficient(node.targetHero);
if(heroRole == HeroRole::MAIN)
{
@@ -239,7 +239,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * aiNk, const C
for(const AIPath & path : paths)
{
auto heroRole = aiNk->heroManager->getHeroRole(path.targetHero);
auto heroRole = aiNk->heroManager->getHeroRoleOrDefaultInefficient(path.targetHero);
if(heroRole == HeroRole::MAIN && path.turn() < aiNk->settings->getScoutHeroTurnDistanceLimit())
hasMainAround = true;
@@ -290,7 +290,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * aiNk, const C
auto upgrade = aiNk->armyManager->calculateCreaturesUpgrade(path.heroArmy, upgrader, availableResources);
if(!upgrader->getGarrisonHero()
&& (hasMainAround || aiNk->heroManager->getHeroRole(path.targetHero) == HeroRole::MAIN))
&& (hasMainAround || aiNk->heroManager->getHeroRoleOrDefaultInefficient(path.targetHero) == HeroRole::MAIN))
{
ArmyUpgradeInfo armyToGetOrBuy;

View File

@@ -172,7 +172,7 @@ Goals::TGoalVec StartupBehavior::decompose(const Nullkiller * aiNk) const
auto garrisonHeroScore = aiNk->heroManager->evaluateHero(garrisonHero);
if(visitingHeroScore > garrisonHeroScore
|| (aiNk->heroManager->getHeroRole(garrisonHero) == HeroRole::SCOUT && aiNk->heroManager->getHeroRole(visitingHero) == HeroRole::MAIN))
|| (aiNk->heroManager->getHeroRoleOrDefaultInefficient(garrisonHero) == HeroRole::SCOUT && aiNk->heroManager->getHeroRoleOrDefaultInefficient(visitingHero) == HeroRole::MAIN))
{
if(canRecruitHero || aiNk->armyManager->howManyReinforcementsCanGet(visitingHero, garrisonHero) > 200)
{

View File

@@ -15,7 +15,6 @@
#include "../Behaviors/CaptureObjectsBehavior.h"
#include "../Behaviors/RecruitHeroBehavior.h"
#include "../Behaviors/BuyArmyBehavior.h"
#include "../Behaviors/StartupBehavior.h"
#include "../Behaviors/DefenceBehavior.h"
#include "../Behaviors/BuildingBehavior.h"
#include "../Behaviors/GatherArmyBehavior.h"
@@ -23,7 +22,6 @@
#include "../Behaviors/StayAtTownBehavior.h"
#include "../Behaviors/ExplorationBehavior.h"
#include "../Goals/Invalid.h"
#include "../Goals/Composition.h"
#include "../../../lib/CPlayerState.h"
#include "../../lib/StartInfo.h"
#include "../../lib/pathfinder/PathfinderCache.h"
@@ -41,7 +39,6 @@ Nullkiller::Nullkiller()
: activeHero(nullptr)
, scanDepth(ScanDepth::MAIN_FULL)
, useHeroChain(true)
, pathfinderInvalidated(false)
, memory(std::make_unique<AIMemory>())
{
@@ -49,7 +46,7 @@ Nullkiller::Nullkiller()
Nullkiller::~Nullkiller() = default;
bool canUseOpenMap(std::shared_ptr<CCallback> cb, PlayerColor playerID)
bool canUseOpenMap(const std::shared_ptr<CCallback>& cb, const PlayerColor playerID)
{
if(!cb->getStartInfo()->extraOptionsInfo.cheatsAllowed)
{
@@ -58,31 +55,33 @@ bool canUseOpenMap(std::shared_ptr<CCallback> cb, PlayerColor playerID)
const TeamState * team = cb->getPlayerTeam(playerID);
auto hasHumanInTeam = vstd::contains_if(team->players, [cb](PlayerColor teamMateID) -> bool
const auto hasHumanInTeam = vstd::contains_if(
team->players,
[cb](const PlayerColor teamMateID) -> bool
{
return cb->getPlayerState(teamMateID)->isHuman();
});
}
);
return !hasHumanInTeam;
}
void Nullkiller::init(std::shared_ptr<CCallback> cb, AIGateway * aiGw)
void Nullkiller::init(const std::shared_ptr<CCallback> & cbInput, AIGateway * aiGwInput)
{
this->cc = cb;
this->aiGw = aiGw;
this->playerID = aiGw->playerID;
cc = cbInput;
aiGw = aiGwInput;
playerID = aiGwInput->playerID;
settings = std::make_unique<Settings>(cb->getStartInfo()->difficulty);
PathfinderOptions pathfinderOptions(*cb);
settings = std::make_unique<Settings>(cc->getStartInfo()->difficulty);
PathfinderOptions pathfinderOptions(*cc);
pathfinderOptions.useTeleportTwoWay = true;
pathfinderOptions.useTeleportOneWay = settings->isOneWayMonolithUsageAllowed();
pathfinderOptions.useTeleportOneWayRandom = settings->isOneWayMonolithUsageAllowed();
pathfinderCache = std::make_unique<PathfinderCache>(cb.get(), pathfinderOptions);
pathfinderCache = std::make_unique<PathfinderCache>(cc.get(), pathfinderOptions);
if(canUseOpenMap(cb, playerID))
if(canUseOpenMap(cc, playerID))
{
useObjectGraph = settings->isObjectGraphAllowed();
openMap = settings->isOpenMap() || useObjectGraph;
@@ -94,7 +93,6 @@ void Nullkiller::init(std::shared_ptr<CCallback> cb, AIGateway * aiGw)
}
baseGraph.reset();
priorityEvaluator.reset(new PriorityEvaluator(this));
priorityEvaluators.reset(
new SharedPool<PriorityEvaluator>(
@@ -107,15 +105,16 @@ void Nullkiller::init(std::shared_ptr<CCallback> cb, AIGateway * aiGw)
buildAnalyzer.reset(new BuildAnalyzer(this));
objectClusterizer.reset(new ObjectClusterizer(this));
dangerEvaluator.reset(new FuzzyHelper(this));
pathfinder.reset(new AIPathfinder(cb.get(), this));
armyManager.reset(new ArmyManager(cb.get(), this));
heroManager.reset(new HeroManager(cb.get(), this));
pathfinder.reset(new AIPathfinder(cc.get(), this));
armyManager.reset(new ArmyManager(cc.get(), this));
heroManager.reset(new HeroManager(cc.get(), this));
decomposer.reset(new DeepDecomposer(this));
armyFormation.reset(new ArmyFormation(cb, this));
armyFormation.reset(new ArmyFormation(cc, this));
}
TaskPlanItem::TaskPlanItem(const TSubgoal & task)
:task(task), affectedObjects(task->asTask()->getAffectedObjects())
: affectedObjects(task->asTask()->getAffectedObjects())
, task(task)
{
}
@@ -216,12 +215,12 @@ Goals::TTaskVec Nullkiller::buildPlan(TGoalVec & tasks, int priorityTier) const
void Nullkiller::decompose(Goals::TGoalVec & results, const Goals::TSubgoal& behavior, int decompositionMaxDepth) const
{
makingTurnInterrupption.interruptionPoint();
makingTurnInterruption.interruptionPoint();
logAi->debug("Decomposing behavior %s", behavior->toString());
const auto start = std::chrono::high_resolution_clock::now();
decomposer->decompose(results, behavior, decompositionMaxDepth);
makingTurnInterrupption.interruptionPoint();
makingTurnInterruption.interruptionPoint();
logAi->debug("Decomposing behavior %s done in %ld", behavior->toString(), timeElapsed(start));
}
@@ -250,9 +249,13 @@ void Nullkiller::invalidatePathfinderData()
void Nullkiller::updateState()
{
makingTurnInterrupption.interruptionPoint();
#if NK2AI_TRACE_LEVEL >= 1
logAi->info("PERFORMANCE: AI updateState started");
#endif
makingTurnInterruption.interruptionPoint();
std::unique_lock lockGuard(aiStateMutex);
auto start = std::chrono::high_resolution_clock::now();
const auto start = std::chrono::high_resolution_clock::now();
activeHero = nullptr;
setTargetObject(-1);
@@ -260,29 +263,18 @@ void Nullkiller::updateState()
buildAnalyzer->update();
if (!pathfinderInvalidated)
logAi->trace("Skipping paths regeneration - up to date");
if(!pathfinderInvalidated && dangerHitMap->isHitMapUpToDate() && dangerHitMap->isTileOwnersUpToDate())
logAi->trace("Skipping full state regeneration - up to date");
else
{
memory->removeInvisibleObjects(cc.get());
dangerHitMap->updateHitMap();
dangerHitMap->calculateTileOwners();
makingTurnInterrupption.interruptionPoint();
makingTurnInterruption.interruptionPoint();
heroManager->update();
logAi->trace("Updating paths");
std::map<const CGHeroInstance *, HeroRole> activeHeroes;
for(auto hero : cc->getHeroesInfo())
{
if(getHeroLockedReason(hero) == HeroLockedReason::DEFENCE)
continue;
activeHeroes[hero] = heroManager->getHeroRole(hero);
}
PathfinderSettings cfg;
cfg.useHeroChain = useHeroChain;
cfg.allowBypassObjects = true;
@@ -297,27 +289,35 @@ void Nullkiller::updateState()
cfg.scoutTurnDistanceLimit = settings->getScoutHeroTurnDistanceLimit();
}
makingTurnInterrupption.interruptionPoint();
pathfinder->updatePaths(activeHeroes, cfg);
makingTurnInterruption.interruptionPoint();
const auto heroes = getHeroesForPathfinding();
pathfinder->updatePaths(heroes, cfg);
if(isObjectGraphAllowed())
{
pathfinder->updateGraphs(
activeHeroes,
heroes,
scanDepth == ScanDepth::SMALL ? PathfinderSettings::MaxTurnDistanceLimit : 10,
scanDepth == ScanDepth::ALL_FULL ? PathfinderSettings::MaxTurnDistanceLimit : 3);
}
makingTurnInterrupption.interruptionPoint();
makingTurnInterruption.interruptionPoint();
objectClusterizer->clusterize();
pathfinderInvalidated = false;
}
armyManager->update();
logAi->debug("AI state updated in %ld ms", timeElapsed(start));
#if NK2AI_TRACE_LEVEL >= 1
if(const auto timeElapsedMs = timeElapsed(start); timeElapsedMs > 499)
{
logAi->warn("PERFORMANCE: AI updateState took %ld ms", timeElapsedMs);
}
else
{
logAi->info("PERFORMANCE: AI updateState took %ld ms", timeElapsedMs);
}
#endif
}
bool Nullkiller::isHeroLocked(const CGHeroInstance * hero) const
@@ -335,7 +335,7 @@ bool Nullkiller::arePathHeroesLocked(const AIPath & path) const
return true;
}
for(auto & node : path.nodes)
for(const auto & node : path.nodes)
{
auto lockReason = getHeroLockedReason(node.targetHero);
@@ -368,9 +368,7 @@ void Nullkiller::makeTurn()
for(int i = 1; i <= settings->getMaxPass() && cc->getPlayerStatus(playerID) == EPlayerStatus::INGAME; i++)
{
updateState();
if (!makeTurnHelperPriorityPass(tasks, i)) return;
if (!updateStateAndExecutePriorityPass(tasks, i)) return;
tasks.clear();
decompose(tasks, sptr(CaptureObjectsBehavior()), 1);
@@ -493,16 +491,15 @@ void Nullkiller::makeTurn()
}
}
bool Nullkiller::makeTurnHelperPriorityPass(Goals::TGoalVec & tempResults, int passIndex)
bool Nullkiller::updateStateAndExecutePriorityPass(Goals::TGoalVec & tempResults, const int passIndex)
{
updateState();
Goals::TTask bestPrioPassTask = taskptr(Goals::Invalid());
for(int i = 1; i <= settings->getMaxPriorityPass() && cc->getPlayerStatus(playerID) == EPlayerStatus::INGAME; i++)
{
tempResults.clear();
// Call updateHitMap here instead of inside each of the 3 behaviors
dangerHitMap->updateHitMap();
decompose(tempResults, sptr(RecruitHeroBehavior()), 1);
decompose(tempResults, sptr(BuyArmyBehavior()), 1);
decompose(tempResults, sptr(BuildingBehavior()), 1);
@@ -510,13 +507,15 @@ bool Nullkiller::makeTurnHelperPriorityPass(Goals::TGoalVec & tempResults, int p
bestPrioPassTask = choseBestTask(tempResults);
if(bestPrioPassTask->priority > 0)
{
#if NK2AI_TRACE_LEVEL >= 1
logAi->info("Pass %d: Performing priorityPass %d task %s with prio: %d", passIndex, i, bestPrioPassTask->toString(), bestPrioPassTask->priority);
#endif
if(!executeTask(bestPrioPassTask))
{
logAi->warn("Task failed to execute");
return false;
}
// TODO: Mircea: Might want to consider calling it before executeTask just in case
updateState();
}
else
@@ -532,7 +531,7 @@ bool Nullkiller::makeTurnHelperPriorityPass(Goals::TGoalVec & tempResults, int p
return true;
}
bool Nullkiller::areAffectedObjectsPresent(Goals::TTask task) const
bool Nullkiller::areAffectedObjectsPresent(const Goals::TTask & task) const
{
auto affectedObjs = task->getAffectedObjects();
@@ -545,23 +544,23 @@ bool Nullkiller::areAffectedObjectsPresent(Goals::TTask task) const
return true;
}
HeroRole Nullkiller::getTaskRole(Goals::TTask task) const
HeroRole Nullkiller::getTaskRole(const Goals::TTask & task) const
{
HeroPtr heroPtr(task->getHero(), cc);
HeroRole heroRole = HeroRole::MAIN;
if(heroPtr.isValid())
heroRole = heroManager->getHeroRole(heroPtr);
if(heroPtr.isVerified())
heroRole = heroManager->getHeroRoleOrDefault(heroPtr);
return heroRole;
}
bool Nullkiller::executeTask(Goals::TTask task)
bool Nullkiller::executeTask(const Goals::TTask & task)
{
auto start = std::chrono::high_resolution_clock::now();
std::string taskDescr = task->toString();
makingTurnInterrupption.interruptionPoint();
makingTurnInterruption.interruptionPoint();
logAi->debug("Trying to realize %s (value %2.3f)", taskDescr, task->priority);
try
@@ -602,7 +601,7 @@ bool Nullkiller::handleTrading()
bool haveTraded = false;
bool shouldTryToTrade = true;
ObjectInstanceID marketId;
for (auto town : cc->getTownsInfo())
for (const auto town : cc->getTownsInfo())
{
if (town->hasBuiltSomeTradeBuilding())
{
@@ -645,7 +644,7 @@ bool Nullkiller::handleTrading()
for (int i = 0; i < required.size(); ++i)
{
float ratio = available[i];
float ratio;
if (required[i] > 0)
ratio = static_cast<float>(available[i]) / required[i];
else
@@ -724,4 +723,17 @@ void Nullkiller::tracePlayerStatus(bool beginning) const
#endif
}
std::map<const CGHeroInstance *, HeroRole> Nullkiller::getHeroesForPathfinding() const
{
std::map<const CGHeroInstance *, HeroRole> activeHeroes;
for(auto hero : cc->getHeroesInfo())
{
if(getHeroLockedReason(hero) == HeroLockedReason::DEFENCE)
continue;
activeHeroes[hero] = heroManager->getHeroRoleOrDefaultInefficient(hero);
}
return activeHeroes;
}
}

View File

@@ -110,13 +110,13 @@ public:
/// Same value as AIGateway->cc
std::shared_ptr<CCallback> cc;
std::mutex aiStateMutex;
mutable ThreadInterruption makingTurnInterrupption;
mutable ThreadInterruption makingTurnInterruption;
Nullkiller();
virtual ~Nullkiller();
void init(std::shared_ptr<CCallback> cb, AIGateway * aiGw);
void init(const std::shared_ptr<CCallback> & cbInput, AIGateway * aiGwInput);
virtual void makeTurn();
bool makeTurnHelperPriorityPass(Goals::TGoalVec& tempResults, int passIndex);
bool updateStateAndExecutePriorityPass(Goals::TGoalVec& tempResults, int passIndex);
bool isActive(const CGHeroInstance * hero) const { return activeHero == hero; }
bool isHeroLocked(const CGHeroInstance * hero) const;
HeroPtr getActiveHero() { return HeroPtr(activeHero, cc); }
@@ -140,6 +140,7 @@ public:
std::shared_ptr<const CPathsInfo> getPathsInfo(const CGHeroInstance * h) const;
void invalidatePaths();
std::map<const CGHeroInstance *, HeroRole> getHeroesForPathfinding() const;
private:
void resetState();
@@ -147,9 +148,9 @@ private:
void decompose(Goals::TGoalVec & results, const Goals::TSubgoal& behavior, int decompositionMaxDepth) const;
Goals::TTask choseBestTask(Goals::TGoalVec & tasks) const;
Goals::TTaskVec buildPlan(Goals::TGoalVec & tasks, int priorityTier) const;
bool executeTask(Goals::TTask task);
bool areAffectedObjectsPresent(Goals::TTask task) const;
HeroRole getTaskRole(Goals::TTask task) const;
bool executeTask(const Goals::TTask & task);
bool areAffectedObjectsPresent(const Goals::TTask & task) const;
HeroRole getTaskRole(const Goals::TTask & task) const;
void tracePlayerStatus(bool beginning) const;
};

View File

@@ -778,7 +778,7 @@ public:
evaluationContext.addNonCriticalStrategicalValue(2.0f * armyStrength / (float)heroExchange.hero->getArmyStrength());
evaluationContext.conquestValue += 2.0f * armyStrength / (float)heroExchange.hero->getArmyStrength();
evaluationContext.heroRole = evaluationContext.evaluator.aiNk->heroManager->getHeroRole(heroExchange.hero);
evaluationContext.heroRole = evaluationContext.evaluator.aiNk->heroManager->getHeroRoleOrDefaultInefficient(heroExchange.hero);
evaluationContext.isExchange = true;
}
};
@@ -958,7 +958,7 @@ public:
float highestCostForSingleHero = 0;
for(auto pair : costsPerHero)
{
auto role = evaluationContext.evaluator.aiNk->heroManager->getHeroRole(pair.first);
auto role = evaluationContext.evaluator.aiNk->heroManager->getHeroRoleOrDefaultInefficient(pair.first);
evaluationContext.movementCostByRole[role] += pair.second;
if (pair.second > highestCostForSingleHero)
highestCostForSingleHero = pair.second;
@@ -975,7 +975,7 @@ public:
auto army = path.heroArmy;
const CGObjectInstance * target = aiNk->cc->getObj((ObjectInstanceID)task->objid, false);
auto heroRole = evaluationContext.evaluator.aiNk->heroManager->getHeroRole(hero);
auto heroRole = evaluationContext.evaluator.aiNk->heroManager->getHeroRoleOrDefaultInefficient(hero);
if(heroRole == HeroRole::MAIN)
evaluationContext.heroRole = heroRole;
@@ -1056,7 +1056,7 @@ public:
std::shared_ptr<ObjectCluster> cluster = clusterGoal.getCluster();
auto hero = clusterGoal.hero;
auto role = evaluationContext.evaluator.aiNk->heroManager->getHeroRole(hero);
auto role = evaluationContext.evaluator.aiNk->heroManager->getHeroRoleOrDefaultInefficient(hero);
std::vector<std::pair<ObjectInstanceID, ClusterObjectInfo>> objects(cluster->objects.begin(), cluster->objects.end());
@@ -1113,7 +1113,7 @@ public:
if(garrisonHero && swapCommand.getLockingReason() == HeroLockedReason::DEFENCE)
{
auto defenderRole = evaluationContext.evaluator.aiNk->heroManager->getHeroRole(garrisonHero);
auto defenderRole = evaluationContext.evaluator.aiNk->heroManager->getHeroRoleOrDefaultInefficient(garrisonHero);
auto mpLeft = garrisonHero->movementPointsRemaining() / (float)garrisonHero->movementPointsLimit(true);
evaluationContext.movementCost += mpLeft;
@@ -1142,7 +1142,7 @@ public:
Goals::DismissHero & dismissCommand = dynamic_cast<Goals::DismissHero &>(*task);
const CGHeroInstance * dismissedHero = dismissCommand.getHero();
auto role = aiNk->heroManager->getHeroRole(dismissedHero);
auto role = aiNk->heroManager->getHeroRoleOrDefaultInefficient(dismissedHero);
auto mpLeft = dismissedHero->movementPointsRemaining();
evaluationContext.movementCost += mpLeft;

View File

@@ -111,7 +111,7 @@ void ExecuteHeroChain::accept(AIGateway * aiGw)
const CGHeroInstance * hero = node->targetHero;
HeroPtr heroPtr(hero, aiGw->cc);
if(!heroPtr.isValid())
if(!heroPtr.isVerified())
{
logAi->error("Hero %s was lost. Exit hero chain.", heroPtr.nameOrDefault());
@@ -148,7 +148,7 @@ void ExecuteHeroChain::accept(AIGateway * aiGw)
node->specialAction->execute(aiGw, hero);
if(!heroPtr.isValid())
if(!heroPtr.isVerified())
{
logAi->error("Hero %s was lost trying to execute special action. Exit hero chain.", heroPtr.nameOrDefault());
@@ -229,7 +229,7 @@ void ExecuteHeroChain::accept(AIGateway * aiGw)
}
catch(const cannotFulfillGoalException &)
{
if(!heroPtr.isValid())
if(!heroPtr.isVerified())
{
logAi->error("Hero %s was lost. Exit hero chain.", heroPtr.nameOrDefault());
@@ -275,7 +275,7 @@ void ExecuteHeroChain::accept(AIGateway * aiGw)
}
catch(const goalFulfilledException &)
{
if(!heroPtr.isValid())
if(!heroPtr.isVerified())
{
logAi->debug("Hero %s was killed while attempting to reach %s", heroPtr.nameOrDefault(), node->coord.toString());

View File

@@ -70,8 +70,8 @@ void RecruitHero::accept(AIGateway * aiGw)
ccTl->recruitHero(t, heroToHire);
{
// TODO: Mircea: Consider same behavior when a hero is lost? Relevant?
std::unique_lock lockGuard(aiGw->nullkiller->aiStateMutex);
aiGw->nullkiller->heroManager->update();
aiGw->nullkiller->objectClusterizer->reset();
}

View File

@@ -43,22 +43,22 @@ const bool DO_NOT_SAVE_TO_COMMITTED_TILES = false;
AISharedStorage::AISharedStorage(int3 sizes, int numChains)
{
if(!shared){
shared.reset(new boost::multi_array<AIPathNode, 4>(
boost::extents[sizes.z][sizes.x][sizes.y][numChains]));
if(!shared)
{
shared.reset(new boost::multi_array<AIPathNode, 4>(boost::extents[sizes.z][sizes.x][sizes.y][numChains]));
nodes = shared;
foreach_tile_pos([&](const int3 & pos)
foreach_tile_pos(
[&](const int3 & pos)
{
for(auto i = 0; i < numChains; i++)
{
auto & node = get(pos)[i];
node.version = -1;
node.coord = pos;
}
});
}
);
}
else
nodes = shared;
@@ -105,7 +105,7 @@ int AINodeStorage::getBucketSize() const
}
AINodeStorage::AINodeStorage(const Nullkiller * aiNk, const int3 & Sizes)
: sizes(Sizes), aiNk(aiNk), cc(aiNk->cc.get()), nodes(Sizes, aiNk->settings->getPathfinderBucketSize() * aiNk->settings->getPathfinderBucketsCount())
: sizes(Sizes), aiNk(aiNk), nodes(Sizes, aiNk->settings->getPathfinderBucketSize() * aiNk->settings->getPathfinderBucketsCount())
{
accessibility = std::make_unique<boost::multi_array<EPathAccessibility, 4>>(
boost::extents[sizes.z][sizes.x][sizes.y][EPathfindingLayer::NUM_LAYERS]);
@@ -197,7 +197,6 @@ std::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
for(auto i = aiNk->settings->getPathfinderBucketSize() - 1; i >= 0; i--)
{
AIPathNode & node = chains[i + bucketOffset];
if(node.version != AISharedStorage::version)
{
node.reset(layer, getAccessibility(pos, layer));
@@ -1169,11 +1168,11 @@ void AINodeStorage::calculateTownPortal(
const std::vector<CGPathNode *> & initialNodes,
TVector & output)
{
auto towns = cc->getTownsInfo(false);
auto towns = aiNk->cc->getTownsInfo(false);
vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool
{
return cc->getPlayerRelations(actor->hero->tempOwner, t->tempOwner) == PlayerRelations::ENEMIES;
return aiNk->cc->getPlayerRelations(actor->hero->tempOwner, t->tempOwner) == PlayerRelations::ENEMIES;
});
if(!towns.size())
@@ -1399,7 +1398,6 @@ bool AINodeStorage::isOtherChainBetter(
bool AINodeStorage::isTileAccessible(const HeroPtr & heroPtr, const int3 & pos, const EPathfindingLayer layer) const
{
auto chains = nodes.get(pos);
for(const AIPathNode & node : chains)
{
if(node.version == AISharedStorage::version
@@ -1415,7 +1413,7 @@ bool AINodeStorage::isTileAccessible(const HeroPtr & heroPtr, const int3 & pos,
return false;
}
void AINodeStorage::calculateChainInfo(std::vector<AIPath> & paths, const int3 & pos, bool isOnLand) const
void AINodeStorage::calculateChainInfo(std::vector<AIPath> & paths, const int3 & pos, const bool isOnLand) const
{
auto layer = isOnLand ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL;
auto chains = nodes.get(pos);
@@ -1440,7 +1438,6 @@ void AINodeStorage::calculateChainInfo(std::vector<AIPath> & paths, const int3 &
}
AIPath & path = paths.emplace_back();
path.targetHero = node.actor->hero;
path.heroArmy = node.actor->creatureSet;
path.armyLoss = node.armyLoss;
@@ -1473,7 +1470,7 @@ void AINodeStorage::calculateChainInfo(std::vector<AIPath> & paths, const int3 &
}
int fortLevel = 0;
auto visitableObjects = cc->getVisitableObjs(pos);
auto visitableObjects = aiNk->cc->getVisitableObjs(pos);
for (auto obj : visitableObjects)
{
if (objWithID<Obj::TOWN>(obj))

View File

@@ -162,11 +162,8 @@ class AINodeStorage : public INodeStorage
{
private:
int3 sizes;
std::unique_ptr<boost::multi_array<EPathAccessibility, 4>> accessibility;
const CPlayerSpecificInfoCallback * cc;
const Nullkiller * aiNk;
const Nullkiller * aiNk; // TODO: Mircea: Replace with &
AISharedStorage nodes;
std::vector<std::shared_ptr<ChainActor>> actors;
std::vector<CGPathNode *> heroChain;
@@ -265,7 +262,7 @@ public:
bool isDistanceLimitReached(const PathNodeInfo & source, CDestinationNodeInfo & destination) const;
std::optional<AIPathNode *> getOrCreateNode(const int3 & coord, const EPathfindingLayer layer, const ChainActor * actor);
void calculateChainInfo(std::vector<AIPath> & result, const int3 & pos, bool isOnLand) const;
void calculateChainInfo(std::vector<AIPath> & paths, const int3 & pos, bool isOnLand) const;
bool isTileAccessible(const HeroPtr & heroPtr, const int3 & pos, const EPathfindingLayer layer) const;
void setHeroes(std::map<const CGHeroInstance *, HeroRole> heroes);
void setScoutTurnDistanceLimit(uint8_t distanceLimit) { turnDistanceLimit[HeroRole::SCOUT] = distanceLimit; }

View File

@@ -49,27 +49,23 @@ void AIPathfinder::calculateQuickPathsWithBlocker(std::vector<AIPath> & result,
}
}
void AIPathfinder::calculatePathInfo(std::vector<AIPath> & result, const int3 & tile, bool includeGraph) const
void AIPathfinder::calculatePathInfo(std::vector<AIPath> & paths, const int3 & tile, bool includeGraph) const
{
const TerrainTile * tileInfo = cb->getTile(tile, false);
result.clear();
paths.clear();
if(!tileInfo)
{
return;
}
storage->calculateChainInfo(result, tile, !tileInfo->isWater());
storage->calculateChainInfo(paths, tile, !tileInfo->isWater());
if(includeGraph)
{
for(auto hero : cb->getHeroesInfo())
for(const auto * hero : cb->getHeroesInfo())
{
auto graph = heroGraphs.find(hero->id);
if(graph != heroGraphs.end())
graph->second->addChainInfo(result, tile, hero, aiNk);
graph->second->addChainInfo(paths, tile, hero, aiNk);
}
}
}
@@ -118,11 +114,11 @@ void AIPathfinder::updatePaths(const std::map<const CGHeroInstance *, HeroRole>
do
{
aiNk->makingTurnInterrupption.interruptionPoint();
aiNk->makingTurnInterruption.interruptionPoint();
while(storage->calculateHeroChain())
{
aiNk->makingTurnInterrupption.interruptionPoint();
aiNk->makingTurnInterruption.interruptionPoint();
logAi->trace("Recalculate paths pass %d", pass++);
cb->calculatePaths(config);
@@ -131,11 +127,11 @@ void AIPathfinder::updatePaths(const std::map<const CGHeroInstance *, HeroRole>
logAi->trace("Select next actor");
} while(storage->selectNextActor());
aiNk->makingTurnInterrupption.interruptionPoint();
aiNk->makingTurnInterruption.interruptionPoint();
if(storage->calculateHeroChainFinal())
{
aiNk->makingTurnInterrupption.interruptionPoint();
aiNk->makingTurnInterruption.interruptionPoint();
logAi->trace("Recalculate paths pass final");
cb->calculatePaths(config);