mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-23 22:37:55 +02:00
fix: time-of-check-to-time-of-use (TOCTOU) race condition in AIMemory::removeInvisibleObjects; a bit of refactoring in RecruitHeroBehavior and some todo comments for later on
This commit is contained in:
@@ -222,7 +222,7 @@ void AIGateway::tileHidden(const FowTilesType & pos)
|
||||
{
|
||||
LOG_TRACE(logAi);
|
||||
|
||||
nullkiller->memory->removeInvisibleObjects(cc.get());
|
||||
nullkiller->memory->removeInvisibleOrDeletedObjects(*cc);
|
||||
}
|
||||
|
||||
void AIGateway::tileRevealed(const FowTilesType & pos)
|
||||
@@ -1008,9 +1008,10 @@ void AIGateway::waitTillFree()
|
||||
std::vector<const CGObjectInstance *> AIGateway::getFlaggedObjects() const
|
||||
{
|
||||
std::vector<const CGObjectInstance *> ret;
|
||||
for(const CGObjectInstance * obj : nullkiller->memory->visitableObjs)
|
||||
for(const ObjectInstanceID objId : nullkiller->memory->visitableObjs)
|
||||
{
|
||||
if(obj->tempOwner == playerID)
|
||||
const CGObjectInstance * obj = cc->getObj(objId, false);
|
||||
if(obj && obj->tempOwner == playerID)
|
||||
ret.push_back(obj);
|
||||
}
|
||||
return ret;
|
||||
@@ -1628,12 +1629,11 @@ void AIGateway::memorizeRevisitableObjs(const std::unique_ptr<AIMemory> & memory
|
||||
{
|
||||
if(cc->getDate(Date::DAY_OF_WEEK) == 1)
|
||||
{
|
||||
for(const CGObjectInstance * obj : memory->visitableObjs)
|
||||
for(const ObjectInstanceID objId : memory->visitableObjs)
|
||||
{
|
||||
if(isWeeklyRevisitable(playerID, obj))
|
||||
{
|
||||
const CGObjectInstance * obj = cc->getObj(objId, false);
|
||||
if(obj && isWeeklyRevisitable(playerID, obj))
|
||||
memory->markObjectUnvisited(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,8 +88,12 @@ void DangerHitMapAnalyzer::updateHitMap()
|
||||
|
||||
std::map<PlayerColor, std::map<const CGHeroInstance *, HeroRole>> heroes;
|
||||
|
||||
for(const CGObjectInstance * obj : aiNk->memory->visitableObjs)
|
||||
for(const ObjectInstanceID objId : aiNk->memory->visitableObjs)
|
||||
{
|
||||
const CGObjectInstance * obj = cc->getObj(objId, false);
|
||||
if(!obj)
|
||||
continue;
|
||||
|
||||
if(obj->ID == Obj::HERO)
|
||||
{
|
||||
auto hero = dynamic_cast<const CGHeroInstance *>(obj);
|
||||
@@ -236,12 +240,11 @@ void DangerHitMapAnalyzer::calculateTileOwners()
|
||||
townHeroes[townHero] = HeroRole::MAIN;
|
||||
};
|
||||
|
||||
for(const auto *obj : aiNk->memory->visitableObjs)
|
||||
for(const ObjectInstanceID objId : aiNk->memory->visitableObjs)
|
||||
{
|
||||
const CGObjectInstance * obj = cc->getObj(objId, false);
|
||||
if(obj && obj->ID == Obj::TOWN)
|
||||
{
|
||||
addTownHero(dynamic_cast<const CGTownInstance *>(obj));
|
||||
}
|
||||
}
|
||||
|
||||
for(const auto *town : cc->getTownsInfo())
|
||||
@@ -336,7 +339,6 @@ uint64_t DangerHitMapAnalyzer::enemyCanKillOurHeroesAlongThePath(const AIPath &
|
||||
const HitMapNode & DangerHitMapAnalyzer::getObjectThreat(const CGObjectInstance * obj) const
|
||||
{
|
||||
auto tile = obj->visitablePos();
|
||||
|
||||
return getTileThreat(tile);
|
||||
}
|
||||
|
||||
|
||||
@@ -242,7 +242,7 @@ bool ObjectClusterizer::shouldVisitObject(const CGObjectInstance * obj) const
|
||||
|
||||
const int3 pos = obj->visitablePos();
|
||||
|
||||
if((obj->ID != Obj::CREATURE_GENERATOR1 && vstd::contains(aiNk->memory->alreadyVisited, obj))
|
||||
if((obj->ID != Obj::CREATURE_GENERATOR1 && vstd::contains(aiNk->memory->alreadyVisited, obj->id))
|
||||
|| obj->wasVisited(aiNk->playerID))
|
||||
{
|
||||
return false;
|
||||
@@ -323,9 +323,7 @@ void ObjectClusterizer::clusterize()
|
||||
blockedObjects.clear();
|
||||
invalidated.clear();
|
||||
|
||||
objs = std::vector<const CGObjectInstance *>(
|
||||
aiNk->memory->visitableObjs.begin(),
|
||||
aiNk->memory->visitableObjs.end());
|
||||
objs = aiNk->memory->visitableIdsToObjsVector(*aiNk->cc);
|
||||
}
|
||||
|
||||
tbb::parallel_for(
|
||||
|
||||
@@ -55,6 +55,8 @@ Goals::TGoalVec BuyArmyBehavior::decompose(const Nullkiller * aiNk) const
|
||||
&*townArmyAvailableToBuy,
|
||||
TerrainId::NONE);
|
||||
|
||||
// TODO: Mircea: Shouldn't matter if hero is MAIN when buying reinforcements if there's a threat around
|
||||
// Evaluate the entire code with the outside towns loop too.
|
||||
if(reinforcement)
|
||||
vstd::amin(reinforcement, aiNk->armyManager->howManyReinforcementsCanBuy(town->getUpperArmy(), town));
|
||||
|
||||
|
||||
@@ -219,12 +219,7 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose(const Nullkiller * aiNk) const
|
||||
}
|
||||
else if(objectTypes.size())
|
||||
{
|
||||
decomposeObjects(
|
||||
tasks,
|
||||
std::vector<const CGObjectInstance *>(
|
||||
aiNk->memory->visitableObjs.begin(),
|
||||
aiNk->memory->visitableObjs.end()),
|
||||
aiNk);
|
||||
decomposeObjects(tasks, aiNk->memory->visitableIdsToObjsVector(*aiNk->cc), aiNk);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -38,7 +38,7 @@ Goals::TGoalVec DefenceBehavior::decompose(const Nullkiller * aiNk) const
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
|
||||
for(auto town : aiNk->cc->getTownsInfo())
|
||||
for(const auto town : aiNk->cc->getTownsInfo())
|
||||
{
|
||||
evaluateDefence(tasks, town, aiNk);
|
||||
}
|
||||
@@ -95,9 +95,7 @@ void handleCounterAttack(
|
||||
continue;
|
||||
|
||||
Composition composition;
|
||||
|
||||
composition.addNext(DefendTown(town, threat, path, true)).addNext(goal);
|
||||
|
||||
tasks.push_back(Goals::sptr(composition));
|
||||
}
|
||||
}
|
||||
@@ -108,7 +106,6 @@ bool handleGarrisonHeroFromPreviousTurn(const CGTownInstance * town, Goals::TGoa
|
||||
if(aiNk->isHeroLocked(town->getGarrisonHero()))
|
||||
{
|
||||
logAi->trace("Hero %s in garrison of town %s is supposed to defend the town", town->getGarrisonHero()->getNameTranslated(), town->getNameTranslated());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -117,12 +114,11 @@ bool handleGarrisonHeroFromPreviousTurn(const CGTownInstance * town, Goals::TGoa
|
||||
if(aiNk->cc->getHeroCount(aiNk->playerID, false) < GameConstants::MAX_HEROES_PER_PLAYER)
|
||||
{
|
||||
logAi->trace("Extracting hero %s from garrison of town %s", town->getGarrisonHero()->getNameTranslated(), town->getNameTranslated());
|
||||
|
||||
tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, nullptr).setpriority(5)));
|
||||
|
||||
return false;
|
||||
}
|
||||
else if(aiNk->heroManager->getHeroRoleOrDefaultInefficient(town->getGarrisonHero()) == HeroRole::MAIN)
|
||||
|
||||
if(aiNk->heroManager->getHeroRoleOrDefaultInefficient(town->getGarrisonHero()) == HeroRole::MAIN)
|
||||
{
|
||||
auto armyDismissLimit = 1000;
|
||||
auto heroToDismiss = aiNk->heroManager->findWeakHeroToDismiss(armyDismissLimit);
|
||||
@@ -145,13 +141,13 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
|
||||
auto threatNode = aiNk->dangerHitMap->getObjectThreat(town);
|
||||
std::vector<HitMapInfo> threats = aiNk->dangerHitMap->getTownThreats(town);
|
||||
|
||||
// TODO: Mircea: Why don't we check if there's any danger in threadNode? Maybe map is still unexplored and no danger
|
||||
// or simply no one is around
|
||||
threats.push_back(threatNode.fastestDanger); // no guarantee that fastest danger will be there
|
||||
|
||||
if(town->getGarrisonHero() && handleGarrisonHeroFromPreviousTurn(town, tasks, aiNk))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(!threatNode.fastestDanger.heroPtr.isVerified())
|
||||
{
|
||||
#if NK2AI_TRACE_LEVEL >= 1
|
||||
@@ -160,13 +156,14 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t reinforcement = aiNk->armyManager->howManyReinforcementsCanBuy(town->getUpperArmy(), town);
|
||||
|
||||
const uint64_t reinforcement = aiNk->armyManager->howManyReinforcementsCanBuy(town->getUpperArmy(), town);
|
||||
if(reinforcement)
|
||||
{
|
||||
#if NK2AI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Town %s can buy defence army %lld", town->getNameTranslated(), reinforcement);
|
||||
#endif
|
||||
|
||||
// TODO: Mircea: This won't have any money left because BuyArmyBehavior runs first and could have used all resources by now
|
||||
tasks.push_back(Goals::sptr(Goals::BuyArmy(town, reinforcement).setpriority(0.5f)));
|
||||
}
|
||||
|
||||
@@ -205,7 +202,6 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
for(int i = 0; i < paths.size(); i++)
|
||||
{
|
||||
auto & path = paths[i];
|
||||
|
||||
if(!closestWay || path.movementCost() < closestWay->movementCost())
|
||||
closestWay = &path;
|
||||
|
||||
@@ -317,7 +313,6 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
||||
for(int i : pathsToDefend)
|
||||
{
|
||||
AIPath & path = paths[i];
|
||||
|
||||
for(int j : defferedPaths[path.targetHero])
|
||||
{
|
||||
AIPath & defferedPath = paths[j];
|
||||
@@ -419,7 +414,7 @@ void DefenceBehavior::evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitM
|
||||
bool heroAlreadyHiredInOtherTown = false;
|
||||
for(const auto & task : tasks)
|
||||
{
|
||||
if(const auto recruitGoal = dynamic_cast<Goals::RecruitHero *>(task.get()))
|
||||
if(auto * const recruitGoal = dynamic_cast<Goals::RecruitHero *>(task.get()))
|
||||
{
|
||||
if(recruitGoal->getHero() == hero)
|
||||
{
|
||||
|
||||
@@ -33,8 +33,12 @@ Goals::TGoalVec ExplorationBehavior::decompose(const Nullkiller * aiNk) const
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
|
||||
for (auto obj : aiNk->memory->visitableObjs)
|
||||
for (const ObjectInstanceID objId : aiNk->memory->visitableObjs)
|
||||
{
|
||||
const CGObjectInstance * obj = aiNk->cc->getObj(objId, false);
|
||||
if(!obj)
|
||||
continue;
|
||||
|
||||
switch (obj->ID.num)
|
||||
{
|
||||
case Obj::REDWOOD_OBSERVATORY:
|
||||
|
||||
@@ -40,6 +40,9 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * aiNk) const
|
||||
|
||||
for(const auto * town : ourTowns)
|
||||
{
|
||||
// TODO: Mircea: Should consider removing it if necessary
|
||||
// What if under threat and there is a stronger hero available?
|
||||
|
||||
if(town->getVisitingHero() && town->getGarrisonHero())
|
||||
continue;
|
||||
|
||||
@@ -50,36 +53,24 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * aiNk) const
|
||||
}
|
||||
|
||||
float visitabilityRatio = 0;
|
||||
for(const auto hero : ourHeroes)
|
||||
for(const auto * const hero : ourHeroes)
|
||||
{
|
||||
if(aiNk->dangerHitMap->getClosestTown(hero->visitablePos()) == town)
|
||||
visitabilityRatio += 1.0f / ourHeroes.size();
|
||||
}
|
||||
|
||||
// TODO: Mircea: Should check even if hero cap is reached because it should replace heroes if a stronger one appears
|
||||
if(aiNk->heroManager->canRecruitHero(town))
|
||||
{
|
||||
calculateTreasureSources(aiNk->objectClusterizer->getNearbyObjects(), aiNk->playerID, *aiNk->dangerHitMap, treasureSourcesCount, town);
|
||||
calculateBestHero(aiNk->cc->getAvailableHeroes(town), *aiNk->heroManager, bestChoice, town, closestThreatTurn, visitabilityRatio);
|
||||
calculateTreasureSources(aiNk->objectClusterizer->getNearbyObjects(), aiNk->playerID, *aiNk->dangerHitMap, treasureSourcesCount, *town);
|
||||
calculateBestHero(aiNk->cc->getAvailableHeroes(town), *aiNk->heroManager, bestChoice, *town, closestThreatTurn, visitabilityRatio);
|
||||
}
|
||||
|
||||
if(town->hasCapitol())
|
||||
haveCapitol = true;
|
||||
}
|
||||
|
||||
if(!vstd::isAlmostZero(bestChoice.score))
|
||||
{
|
||||
if(ourHeroes.empty()
|
||||
|| treasureSourcesCount > ourHeroes.size() * 5
|
||||
// TODO: Mircea: The next condition should always consider a hero if under attack especially if it has towers
|
||||
|| (bestChoice.hero->getArmyCost() > GameConstants::HERO_GOLD_COST / 2.0
|
||||
&& (bestChoice.closestThreat < 1 || !aiNk->buildAnalyzer->isGoldPressureOverMax()))
|
||||
|| (aiNk->getFreeResources()[EGameResID::GOLD] > 10000 && !aiNk->buildAnalyzer->isGoldPressureOverMax() && haveCapitol)
|
||||
|| (aiNk->getFreeResources()[EGameResID::GOLD] > 30000 && !aiNk->buildAnalyzer->isGoldPressureOverMax()))
|
||||
{
|
||||
tasks.push_back(Goals::sptr(Goals::RecruitHero(bestChoice.town, bestChoice.hero).setpriority((float)3 / (ourHeroes.size() + 1))));
|
||||
}
|
||||
}
|
||||
|
||||
calculateFinalDecision(*aiNk, tasks, ourHeroes, bestChoice, haveCapitol, treasureSourcesCount);
|
||||
return tasks;
|
||||
}
|
||||
|
||||
@@ -88,7 +79,7 @@ void RecruitHeroBehavior::calculateTreasureSources(
|
||||
const PlayerColor & playerID,
|
||||
const DangerHitMapAnalyzer & dangerHitMap,
|
||||
int & treasureSourcesCount,
|
||||
const CGTownInstance * town
|
||||
const CGTownInstance & town
|
||||
)
|
||||
{
|
||||
for(const auto * obj : nearbyObjects)
|
||||
@@ -96,7 +87,7 @@ void RecruitHeroBehavior::calculateTreasureSources(
|
||||
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()))
|
||||
if(&town == dangerHitMap.getClosestTown(obj->visitablePos()))
|
||||
treasureSourcesCount++; // TODO: Mircea: Shouldn't it be used to determine the best town?
|
||||
}
|
||||
}
|
||||
@@ -106,7 +97,7 @@ void RecruitHeroBehavior::calculateBestHero(
|
||||
const std::vector<const CGHeroInstance *> & availableHeroes,
|
||||
const HeroManager & heroManager,
|
||||
const RecruitHeroChoice & bestChoice,
|
||||
const CGTownInstance * town,
|
||||
const CGTownInstance & town,
|
||||
const uint8_t closestThreatTurn,
|
||||
const float visitabilityRatio
|
||||
)
|
||||
@@ -114,7 +105,7 @@ void RecruitHeroBehavior::calculateBestHero(
|
||||
|
||||
for(const auto * const hero : availableHeroes)
|
||||
{
|
||||
if((town->getVisitingHero() || town->getGarrisonHero()) && closestThreatTurn < 1 && hero->getArmyCost() < GameConstants::HERO_GOLD_COST / 3.0)
|
||||
if((town.getVisitingHero() || town.getGarrisonHero()) && closestThreatTurn < 1 && hero->getArmyCost() < GameConstants::HERO_GOLD_COST / 3.0)
|
||||
continue;
|
||||
|
||||
const float heroScore = heroManager.evaluateHero(hero);
|
||||
@@ -123,20 +114,44 @@ void RecruitHeroBehavior::calculateBestHero(
|
||||
// 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())
|
||||
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);
|
||||
totalScore += heroScore * town.getTownLevel() * (1 - visitabilityRatio);
|
||||
|
||||
if(totalScore > bestChoice.score)
|
||||
{
|
||||
bestChoice.score = totalScore;
|
||||
bestChoice.hero = hero;
|
||||
bestChoice.town = town;
|
||||
bestChoice.town = &town;
|
||||
bestChoice.closestThreat = closestThreatTurn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RecruitHeroBehavior::calculateFinalDecision(
|
||||
const Nullkiller & aiNk,
|
||||
Goals::TGoalVec tasks,
|
||||
const std::vector<const CGHeroInstance *> & ourHeroes,
|
||||
const RecruitHeroChoice & bestChoice,
|
||||
const bool haveCapitol,
|
||||
const int treasureSourcesCount
|
||||
)
|
||||
{
|
||||
if(!vstd::isAlmostZero(bestChoice.score))
|
||||
{
|
||||
if(ourHeroes.empty()
|
||||
|| treasureSourcesCount > ourHeroes.size() * 5
|
||||
// TODO: Mircea: The next condition should always consider a hero if under attack especially if it has towers
|
||||
|| (bestChoice.hero->getArmyCost() > GameConstants::HERO_GOLD_COST / 2.0
|
||||
&& (bestChoice.closestThreat < 1 || !aiNk.buildAnalyzer->isGoldPressureOverMax()))
|
||||
|| (aiNk.getFreeResources()[EGameResID::GOLD] > 10000 && !aiNk.buildAnalyzer->isGoldPressureOverMax() && haveCapitol)
|
||||
|| (aiNk.getFreeResources()[EGameResID::GOLD] > 30000 && !aiNk.buildAnalyzer->isGoldPressureOverMax()))
|
||||
{
|
||||
tasks.push_back(Goals::sptr(Goals::RecruitHero(bestChoice.town, bestChoice.hero).setpriority((float)3 / (ourHeroes.size() + 1))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -46,16 +46,24 @@ namespace Goals
|
||||
const PlayerColor & playerID,
|
||||
const DangerHitMapAnalyzer & dangerHitMap,
|
||||
int & treasureSourcesCount,
|
||||
const CGTownInstance * town
|
||||
const CGTownInstance & town
|
||||
);
|
||||
static void calculateBestHero(
|
||||
const std::vector<const CGHeroInstance *> & availableHeroes,
|
||||
const HeroManager & heroManager,
|
||||
const RecruitHeroChoice & bestChoice,
|
||||
const CGTownInstance * town,
|
||||
const CGTownInstance & town,
|
||||
uint8_t closestThreatTurn,
|
||||
float visitabilityRatio
|
||||
);
|
||||
static void calculateFinalDecision(
|
||||
const Nullkiller & aiNk,
|
||||
Goals::TGoalVec tasks,
|
||||
const std::vector<const CGHeroInstance *> & ourHeroes,
|
||||
const RecruitHeroChoice & bestChoice,
|
||||
bool haveCapitol,
|
||||
int treasureSourcesCount
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*
|
||||
*/
|
||||
#include "../StdInc.h"
|
||||
|
||||
#include "AIMemory.h"
|
||||
|
||||
namespace NK2AI
|
||||
@@ -15,25 +16,25 @@ namespace NK2AI
|
||||
|
||||
void AIMemory::removeFromMemory(const CGObjectInstance * obj)
|
||||
{
|
||||
vstd::erase_if_present(visitableObjs, obj);
|
||||
vstd::erase_if_present(alreadyVisited, obj);
|
||||
vstd::erase_if_present(visitableObjs, obj->id);
|
||||
vstd::erase_if_present(alreadyVisited, obj->id);
|
||||
|
||||
//TODO: Find better way to handle hero boat removal
|
||||
if(auto hero = dynamic_cast<const CGHeroInstance *>(obj))
|
||||
if(const auto * hero = dynamic_cast<const CGHeroInstance *>(obj))
|
||||
{
|
||||
if(hero->inBoat())
|
||||
{
|
||||
vstd::erase_if_present(visitableObjs, hero->getBoat());
|
||||
vstd::erase_if_present(alreadyVisited, hero->getBoat());
|
||||
vstd::erase_if_present(visitableObjs, hero->getBoat()->id);
|
||||
vstd::erase_if_present(alreadyVisited, hero->getBoat()->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AIMemory::removeFromMemory(ObjectIdRef obj)
|
||||
void AIMemory::removeFromMemory(const ObjectIdRef obj)
|
||||
{
|
||||
auto matchesId = [&](const CGObjectInstance * hlpObj) -> bool
|
||||
auto matchesId = [&](const ObjectInstanceID & objId) -> bool
|
||||
{
|
||||
return hlpObj->id == obj.id;
|
||||
return objId == obj.id;
|
||||
};
|
||||
|
||||
vstd::erase_if(visitableObjs, matchesId);
|
||||
@@ -45,19 +46,15 @@ void AIMemory::addSubterraneanGate(const CGObjectInstance * entrance, const CGOb
|
||||
knownSubterraneanGates[entrance] = exit;
|
||||
knownSubterraneanGates[exit] = entrance;
|
||||
|
||||
logAi->trace(
|
||||
"Found a pair of subterranean gates between %s and %s!",
|
||||
entrance->visitablePos().toString(),
|
||||
exit->visitablePos().toString());
|
||||
logAi->trace("Found a pair of subterranean gates between %s and %s!", entrance->visitablePos().toString(), exit->visitablePos().toString());
|
||||
}
|
||||
|
||||
void AIMemory::addVisitableObject(const CGObjectInstance * obj)
|
||||
{
|
||||
visitableObjs.insert(obj);
|
||||
visitableObjs.insert(obj->id);
|
||||
|
||||
// All teleport objects seen automatically assigned to appropriate channels
|
||||
auto teleportObj = dynamic_cast<const CGTeleport *>(obj);
|
||||
if(teleportObj)
|
||||
if(const auto teleportObj = dynamic_cast<const CGTeleport *>(obj))
|
||||
{
|
||||
CGTeleport::addToChannel(knownTeleportChannels, teleportObj);
|
||||
}
|
||||
@@ -67,45 +64,64 @@ void AIMemory::markObjectVisited(const CGObjectInstance * obj)
|
||||
{
|
||||
if(!obj)
|
||||
return;
|
||||
|
||||
|
||||
// TODO: maybe this logic belongs to CaptureObjects::shouldVisit
|
||||
if(const auto * rewardable = dynamic_cast<const CRewardableObject *>(obj))
|
||||
{
|
||||
if (rewardable->configuration.getVisitMode() == Rewardable::VISIT_HERO) //we may want to visit it with another hero
|
||||
if(rewardable->configuration.getVisitMode() == Rewardable::VISIT_HERO) //we may want to visit it with another hero
|
||||
return;
|
||||
|
||||
if (rewardable->configuration.getVisitMode() == Rewardable::VISIT_BONUS) //or another time
|
||||
if(rewardable->configuration.getVisitMode() == Rewardable::VISIT_BONUS) //or another time
|
||||
return;
|
||||
}
|
||||
|
||||
if(obj->ID == Obj::MONSTER)
|
||||
return;
|
||||
|
||||
alreadyVisited.insert(obj);
|
||||
alreadyVisited.insert(obj->id);
|
||||
}
|
||||
|
||||
void AIMemory::markObjectUnvisited(const CGObjectInstance * obj)
|
||||
{
|
||||
vstd::erase_if_present(alreadyVisited, obj);
|
||||
vstd::erase_if_present(alreadyVisited, obj->id);
|
||||
}
|
||||
|
||||
bool AIMemory::wasVisited(const CGObjectInstance * obj) const
|
||||
{
|
||||
return vstd::contains(alreadyVisited, obj);
|
||||
return vstd::contains(alreadyVisited, obj->id);
|
||||
}
|
||||
|
||||
void AIMemory::removeInvisibleObjects(CCallback * cb)
|
||||
void AIMemory::removeInvisibleOrDeletedObjects(const CCallback & cb)
|
||||
{
|
||||
auto shouldBeErased = [&](const CGObjectInstance * obj) -> bool
|
||||
auto shouldBeErased = [&](const ObjectInstanceID objId) -> bool
|
||||
{
|
||||
if(obj)
|
||||
return !cb->getObj(obj->id, false); // no verbose output needed as we check object visibility
|
||||
else
|
||||
return true;
|
||||
return !cb.getObj(objId, false);
|
||||
};
|
||||
|
||||
vstd::erase_if(visitableObjs, shouldBeErased);
|
||||
vstd::erase_if(alreadyVisited, shouldBeErased);
|
||||
}
|
||||
|
||||
std::vector<const CGObjectInstance *> AIMemory::visitableIdsToObjsVector(const CCallback & cb) const
|
||||
{
|
||||
auto objs = std::vector<const CGObjectInstance *>();
|
||||
for(const ObjectInstanceID objId : visitableObjs)
|
||||
{
|
||||
if(const auto * obj = cb.getObj(objId, false))
|
||||
objs.push_back(obj);
|
||||
}
|
||||
return objs;
|
||||
}
|
||||
|
||||
std::set<const CGObjectInstance *> AIMemory::visitableIdsToObjsSet(const CCallback & cb) const
|
||||
{
|
||||
auto objs = std::set<const CGObjectInstance *>();
|
||||
for(const ObjectInstanceID objId : visitableObjs)
|
||||
{
|
||||
if(const auto * obj = cb.getObj(objId, false))
|
||||
objs.insert(obj);
|
||||
}
|
||||
return objs;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ namespace NK2AI
|
||||
class AIMemory
|
||||
{
|
||||
public:
|
||||
std::set<const CGObjectInstance *> visitableObjs;
|
||||
std::set<const CGObjectInstance *> alreadyVisited;
|
||||
std::set<ObjectInstanceID> visitableObjs;
|
||||
std::set<ObjectInstanceID> alreadyVisited;
|
||||
std::map<TeleportChannelID, std::shared_ptr<TeleportChannel>> knownTeleportChannels;
|
||||
std::map<const CGObjectInstance *, const CGObjectInstance *> knownSubterraneanGates;
|
||||
|
||||
@@ -30,7 +30,10 @@ public:
|
||||
void markObjectVisited(const CGObjectInstance * obj);
|
||||
void markObjectUnvisited(const CGObjectInstance * obj);
|
||||
bool wasVisited(const CGObjectInstance * obj) const;
|
||||
void removeInvisibleObjects(CCallback * cb);
|
||||
void removeInvisibleOrDeletedObjects(const CCallback & cb);
|
||||
// Utility method to reuse code, use visitableIds directly where possible to a
|
||||
std::vector<const CGObjectInstance *> visitableIdsToObjsVector(const CCallback & cb) const;
|
||||
std::set<const CGObjectInstance *> visitableIdsToObjsSet(const CCallback & cb) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
|
||||
case Obj::ARTIFACT:
|
||||
case Obj::RESOURCE:
|
||||
{
|
||||
if(!vstd::contains(aiNk->memory->alreadyVisited, obj))
|
||||
if(!vstd::contains(aiNk->memory->alreadyVisited, obj->id))
|
||||
return 0;
|
||||
[[fallthrough]];
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ void Nullkiller::init(const std::shared_ptr<CCallback> & cbInput, AIGateway * ai
|
||||
buildAnalyzer.reset(new BuildAnalyzer(this));
|
||||
objectClusterizer.reset(new ObjectClusterizer(this));
|
||||
dangerEvaluator.reset(new FuzzyHelper(this));
|
||||
pathfinder.reset(new AIPathfinder(cc.get(), this));
|
||||
pathfinder.reset(new AIPathfinder(this));
|
||||
armyManager.reset(new ArmyManager(cc.get(), this));
|
||||
heroManager.reset(new HeroManager(cc.get(), this));
|
||||
decomposer.reset(new DeepDecomposer(this));
|
||||
@@ -268,7 +268,7 @@ void Nullkiller::updateState()
|
||||
logAi->trace("Skipping full state regeneration - up to date");
|
||||
else
|
||||
{
|
||||
memory->removeInvisibleObjects(cc.get());
|
||||
memory->removeInvisibleOrDeletedObjects(*cc);
|
||||
dangerHitMap->updateHitMap();
|
||||
dangerHitMap->calculateTileOwners();
|
||||
|
||||
@@ -504,7 +504,9 @@ bool Nullkiller::updateStateAndExecutePriorityPass(Goals::TGoalVec & tempResults
|
||||
{
|
||||
tempResults.clear();
|
||||
|
||||
// TODO: Mircea: Should merge recruiting from DefenseBehavior
|
||||
decompose(tempResults, sptr(RecruitHeroBehavior()), 1);
|
||||
// TODO: Mircea: Should merge recruiting from DefenseBehavior
|
||||
decompose(tempResults, sptr(BuyArmyBehavior()), 1);
|
||||
decompose(tempResults, sptr(BuildingBehavior()), 1);
|
||||
|
||||
|
||||
@@ -20,10 +20,7 @@ namespace NK2AI
|
||||
|
||||
std::map<ObjectInstanceID, std::unique_ptr<GraphPaths>> AIPathfinder::heroGraphs;
|
||||
|
||||
AIPathfinder::AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * aiNk)
|
||||
:cb(cb), aiNk(aiNk)
|
||||
{
|
||||
}
|
||||
AIPathfinder::AIPathfinder(Nullkiller * aiNk) : aiNk(aiNk) {}
|
||||
|
||||
bool AIPathfinder::isTileAccessible(const HeroPtr & hero, const int3 & tile) const
|
||||
{
|
||||
@@ -46,7 +43,7 @@ void AIPathfinder::calculateQuickPathsWithBlocker(std::vector<AIPath> & result,
|
||||
|
||||
void AIPathfinder::calculatePathInfo(std::vector<AIPath> & paths, const int3 & tile, bool includeGraph) const
|
||||
{
|
||||
const TerrainTile * tileInfo = cb->getTile(tile, false);
|
||||
const TerrainTile * tileInfo = aiNk->cc->getTile(tile, false);
|
||||
paths.clear();
|
||||
if(!tileInfo)
|
||||
return;
|
||||
@@ -55,7 +52,7 @@ void AIPathfinder::calculatePathInfo(std::vector<AIPath> & paths, const int3 & t
|
||||
|
||||
if(includeGraph)
|
||||
{
|
||||
for(const auto * hero : cb->getHeroesInfo())
|
||||
for(const auto * hero : aiNk->cc->getHeroesInfo())
|
||||
{
|
||||
auto graph = heroGraphs.find(hero->id);
|
||||
|
||||
@@ -69,7 +66,7 @@ void AIPathfinder::updatePaths(const std::map<const CGHeroInstance *, HeroRole>
|
||||
{
|
||||
if(!storage)
|
||||
{
|
||||
storage.reset(new AINodeStorage(aiNk, cb->getMapSize()));
|
||||
storage.reset(new AINodeStorage(aiNk, aiNk->cc->getMapSize()));
|
||||
}
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
@@ -88,12 +85,12 @@ void AIPathfinder::updatePaths(const std::map<const CGHeroInstance *, HeroRole>
|
||||
|
||||
if(pathfinderSettings.useHeroChain)
|
||||
{
|
||||
storage->setTownsAndDwellings(cb->getTownsInfo(), aiNk->memory->visitableObjs);
|
||||
storage->setTownsAndDwellings(aiNk->cc->getTownsInfo(), aiNk->memory->visitableIdsToObjsSet(*aiNk->cc));
|
||||
}
|
||||
|
||||
const auto config = std::make_shared<AIPathfinding::AIPathfinderConfig>(aiNk, storage, pathfinderSettings.allowBypassObjects);
|
||||
logAi->trace("Recalculate paths pass %d", pass++);
|
||||
cb->calculatePaths(config);
|
||||
aiNk->cc->calculatePaths(config);
|
||||
|
||||
if(!pathfinderSettings.useHeroChain)
|
||||
{
|
||||
@@ -115,7 +112,7 @@ void AIPathfinder::updatePaths(const std::map<const CGHeroInstance *, HeroRole>
|
||||
aiNk->makingTurnInterruption.interruptionPoint();
|
||||
|
||||
logAi->trace("Recalculate paths pass %d", pass++);
|
||||
cb->calculatePaths(config);
|
||||
aiNk->cc->calculatePaths(config);
|
||||
}
|
||||
|
||||
logAi->trace("Select next actor");
|
||||
@@ -128,7 +125,7 @@ void AIPathfinder::updatePaths(const std::map<const CGHeroInstance *, HeroRole>
|
||||
aiNk->makingTurnInterruption.interruptionPoint();
|
||||
|
||||
logAi->trace("Recalculate paths pass final");
|
||||
cb->calculatePaths(config);
|
||||
aiNk->cc->calculatePaths(config);
|
||||
}
|
||||
} while(storage->increaseHeroChainTurnLimit());
|
||||
|
||||
|
||||
@@ -41,12 +41,11 @@ class AIPathfinder
|
||||
{
|
||||
private:
|
||||
std::shared_ptr<AINodeStorage> storage;
|
||||
CPlayerSpecificInfoCallback * cb;
|
||||
Nullkiller * aiNk;
|
||||
static std::map<ObjectInstanceID, std::unique_ptr<GraphPaths>> heroGraphs;
|
||||
|
||||
public:
|
||||
AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * aiNk);
|
||||
explicit AIPathfinder(Nullkiller * aiNk);
|
||||
void calculatePathInfo(std::vector<AIPath> & paths, const int3 & tile, bool includeGraph = false) const;
|
||||
bool isTileAccessible(const HeroPtr & hero, const int3 & tile) const;
|
||||
void updatePaths(const std::map<const CGHeroInstance *, HeroRole> & heroes, PathfinderSettings pathfinderSettings);
|
||||
|
||||
@@ -94,8 +94,9 @@ void ObjectGraph::removeObject(const CGObjectInstance * obj, CCallback & cc)
|
||||
|
||||
void ObjectGraph::connectHeroes(const Nullkiller * aiNk)
|
||||
{
|
||||
for(auto obj : aiNk->memory->visitableObjs)
|
||||
for(const ObjectInstanceID objId : aiNk->memory->visitableObjs)
|
||||
{
|
||||
const CGObjectInstance * obj = aiNk->cc->getObj(objId, false);
|
||||
if(obj && obj->ID == Obj::HERO)
|
||||
{
|
||||
addObject(obj);
|
||||
|
||||
@@ -27,8 +27,9 @@ ObjectGraphCalculator::ObjectGraphCalculator(ObjectGraph * target, const Nullkil
|
||||
|
||||
void ObjectGraphCalculator::setGraphObjects()
|
||||
{
|
||||
for(auto obj : aiNk->memory->visitableObjs)
|
||||
for(const ObjectInstanceID objId : aiNk->memory->visitableObjs)
|
||||
{
|
||||
const CGObjectInstance * obj = aiNk->cc->getObj(objId, false);
|
||||
if(obj && obj->isVisitable() && obj->ID != Obj::HERO && obj->ID != Obj::EVENT)
|
||||
{
|
||||
addObjectActor(obj);
|
||||
|
||||
@@ -145,9 +145,10 @@ namespace AIPathfinding
|
||||
shipyards.push_back(t);
|
||||
}
|
||||
|
||||
for(const CGObjectInstance * obj : aiNk->memory->visitableObjs)
|
||||
for(const ObjectInstanceID objId : aiNk->memory->visitableObjs)
|
||||
{
|
||||
if(obj->ID != Obj::TOWN) //towns were handled in the previous loop
|
||||
const CGObjectInstance * obj = aiNk->cc->getObj(objId, false);
|
||||
if(obj && obj->ID != Obj::TOWN) //towns were handled in the previous loop
|
||||
{
|
||||
if(const auto * shipyard = dynamic_cast<const IShipyard *>(obj))
|
||||
shipyards.push_back(shipyard);
|
||||
|
||||
Reference in New Issue
Block a user