1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-07-15 01:24:45 +02:00

NKAI: allow multiple tasks to be executed from one calculation

This commit is contained in:
Andrii Danylchenko
2024-04-14 15:23:44 +03:00
parent 9390825ee7
commit ed76d8a652
33 changed files with 559 additions and 219 deletions

View File

@ -251,6 +251,7 @@ void AIGateway::heroVisit(const CGHeroInstance * visitor, const CGObjectInstance
if(start && visitedObj) //we can end visit with null object, anyway if(start && visitedObj) //we can end visit with null object, anyway
{ {
nullkiller->memory->markObjectVisited(visitedObj); nullkiller->memory->markObjectVisited(visitedObj);
nullkiller->objectClusterizer->invalidate(visitedObj->id);
} }
status.heroVisit(visitedObj, start); status.heroVisit(visitedObj, start);
@ -373,6 +374,7 @@ void AIGateway::objectRemoved(const CGObjectInstance * obj, const PlayerColor &
return; return;
nullkiller->memory->removeFromMemory(obj); nullkiller->memory->removeFromMemory(obj);
nullkiller->objectClusterizer->onObjectRemoved(obj->id);
if(nullkiller->baseGraph && nullkiller->settings->isObjectGraphAllowed()) if(nullkiller->baseGraph && nullkiller->settings->isObjectGraphAllowed())
{ {
@ -1152,11 +1154,6 @@ void AIGateway::retrieveVisitableObjs()
{ {
for(const CGObjectInstance * obj : myCb->getVisitableObjs(pos, false)) for(const CGObjectInstance * obj : myCb->getVisitableObjs(pos, false))
{ {
if(!obj->appearance)
{
logAi->error("Bad!");
}
addVisitableObj(obj); addVisitableObj(obj);
} }
}); });
@ -1401,7 +1398,7 @@ void AIGateway::tryRealize(Goals::DigAtTile & g)
assert(g.hero->visitablePos() == g.tile); //surely we want to crash here? assert(g.hero->visitablePos() == g.tile); //surely we want to crash here?
if(g.hero->diggingStatus() == EDiggingStatus::CAN_DIG) if(g.hero->diggingStatus() == EDiggingStatus::CAN_DIG)
{ {
cb->dig(g.hero.get()); cb->dig(g.hero);
} }
else else
{ {

View File

@ -91,33 +91,10 @@ bool HeroPtr::operator<(const HeroPtr & rhs) const
const CGHeroInstance * HeroPtr::get(bool doWeExpectNull) const const CGHeroInstance * HeroPtr::get(bool doWeExpectNull) const
{ {
//TODO? check if these all assertions every time we get info about hero affect efficiency return get(cb);
//
//behave terribly when attempting unauthorized access to hero that is not ours (or was lost)
assert(doWeExpectNull || h);
if(h)
{
auto obj = cb->getObj(hid);
//const bool owned = obj && obj->tempOwner == ai->playerID;
if(doWeExpectNull && !obj)
{
return nullptr;
}
else
{
if (!obj)
logAi->error("Accessing no longer accessible hero %s!", h->getNameTranslated());
//assert(obj);
//assert(owned);
}
}
return h;
} }
const CGHeroInstance * HeroPtr::get(CCallback * cb, bool doWeExpectNull) const const CGHeroInstance * HeroPtr::get(const CPlayerSpecificInfoCallback * cb, bool doWeExpectNull) const
{ {
//TODO? check if these all assertions every time we get info about hero affect efficiency //TODO? check if these all assertions every time we get info about hero affect efficiency
// //

View File

@ -109,7 +109,7 @@ public:
} }
const CGHeroInstance * get(bool doWeExpectNull = false) const; const CGHeroInstance * get(bool doWeExpectNull = false) const;
const CGHeroInstance * get(CCallback * cb, bool doWeExpectNull = false) const; const CGHeroInstance * get(const CPlayerSpecificInfoCallback * cb, bool doWeExpectNull = false) const;
bool validAndSet() const; bool validAndSet() const;

View File

@ -20,7 +20,7 @@ void ObjectCluster::addObject(const CGObjectInstance * obj, const AIPath & path,
{ {
ClusterObjects::accessor info; ClusterObjects::accessor info;
objects.insert(info, ClusterObjects::value_type(obj, ClusterObjectInfo())); objects.insert(info, ClusterObjects::value_type(obj->id, ClusterObjectInfo()));
if(info->second.priority < priority) if(info->second.priority < priority)
{ {
@ -31,15 +31,14 @@ void ObjectCluster::addObject(const CGObjectInstance * obj, const AIPath & path,
} }
} }
const CGObjectInstance * ObjectCluster::calculateCenter() const const CGObjectInstance * ObjectCluster::calculateCenter(const CPlayerSpecificInfoCallback * cb) const
{ {
auto v = getObjects();
auto tile = int3(0); auto tile = int3(0);
float priority = 0; float priority = 0;
for(auto pair : objects) for(auto & pair : objects)
{ {
auto newPoint = pair.first->visitablePos(); auto newPoint = cb->getObj(pair.first)->visitablePos();
float newPriority = std::pow(pair.second.priority, 4); // lets make high priority targets more weghtful float newPriority = std::pow(pair.second.priority, 4); // lets make high priority targets more weghtful
int3 direction = newPoint - tile; int3 direction = newPoint - tile;
float priorityRatio = newPriority / (priority + newPriority); float priorityRatio = newPriority / (priority + newPriority);
@ -48,21 +47,21 @@ const CGObjectInstance * ObjectCluster::calculateCenter() const
priority += newPriority; priority += newPriority;
} }
auto closestPair = *vstd::minElementByFun(objects, [&](const std::pair<const CGObjectInstance *, ClusterObjectInfo> & pair) -> int auto closestPair = *vstd::minElementByFun(objects, [&](const std::pair<ObjectInstanceID, ClusterObjectInfo> & pair) -> int
{ {
return pair.first->visitablePos().dist2dSQ(tile); return cb->getObj(pair.first)->visitablePos().dist2dSQ(tile);
}); });
return closestPair.first; return cb->getObj(closestPair.first);
} }
std::vector<const CGObjectInstance *> ObjectCluster::getObjects() const std::vector<const CGObjectInstance *> ObjectCluster::getObjects(const CPlayerSpecificInfoCallback * cb) const
{ {
std::vector<const CGObjectInstance *> result; std::vector<const CGObjectInstance *> result;
for(auto pair : objects) for(auto & pair : objects)
{ {
result.push_back(pair.first); result.push_back(cb->getObj(pair.first));
} }
return result; return result;
@ -70,19 +69,19 @@ std::vector<const CGObjectInstance *> ObjectCluster::getObjects() const
std::vector<const CGObjectInstance *> ObjectClusterizer::getNearbyObjects() const std::vector<const CGObjectInstance *> ObjectClusterizer::getNearbyObjects() const
{ {
return nearObjects.getObjects(); return nearObjects.getObjects(ai->cb.get());
} }
std::vector<const CGObjectInstance *> ObjectClusterizer::getFarObjects() const std::vector<const CGObjectInstance *> ObjectClusterizer::getFarObjects() const
{ {
return farObjects.getObjects(); return farObjects.getObjects(ai->cb.get());
} }
std::vector<std::shared_ptr<ObjectCluster>> ObjectClusterizer::getLockedClusters() const std::vector<std::shared_ptr<ObjectCluster>> ObjectClusterizer::getLockedClusters() const
{ {
std::vector<std::shared_ptr<ObjectCluster>> result; std::vector<std::shared_ptr<ObjectCluster>> result;
for(auto pair : blockedObjects) for(auto & pair : blockedObjects)
{ {
result.push_back(pair.second); result.push_back(pair.second);
} }
@ -163,6 +162,69 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons
return nullptr; return nullptr;
} }
void ObjectClusterizer::invalidate(ObjectInstanceID id)
{
nearObjects.objects.erase(id);
farObjects.objects.erase(id);
invalidated.push_back(id);
for(auto & c : blockedObjects)
{
c.second->objects.erase(id);
}
}
void ObjectClusterizer::validateObjects()
{
std::vector<ObjectInstanceID> toRemove;
auto scanRemovedObjects = [this, &toRemove](const ObjectCluster & cluster)
{
for(auto & pair : cluster.objects)
{
if(!ai->cb->getObj(pair.first, false))
toRemove.push_back(pair.first);
}
};
scanRemovedObjects(nearObjects);
scanRemovedObjects(farObjects);
for(auto & pair : blockedObjects)
{
if(!ai->cb->getObj(pair.first, false) || pair.second->objects.empty())
toRemove.push_back(pair.first);
else
scanRemovedObjects(*pair.second);
}
vstd::removeDuplicates(toRemove);
for(auto id : toRemove)
{
onObjectRemoved(id);
}
}
void ObjectClusterizer::onObjectRemoved(ObjectInstanceID id)
{
invalidate(id);
vstd::erase_if_present(invalidated, id);
NKAI::ClusterMap::accessor cluster;
if(blockedObjects.find(cluster, id))
{
for(auto & unlocked : cluster->second->objects)
{
invalidated.push_back(unlocked.first);
}
blockedObjects.erase(cluster);
}
}
bool ObjectClusterizer::shouldVisitObject(const CGObjectInstance * obj) const bool ObjectClusterizer::shouldVisitObject(const CGObjectInstance * obj) const
{ {
if(isObjectRemovable(obj)) if(isObjectRemovable(obj))
@ -222,17 +284,45 @@ Obj ObjectClusterizer::IgnoredObjectTypes[] = {
void ObjectClusterizer::clusterize() void ObjectClusterizer::clusterize()
{ {
auto start = std::chrono::high_resolution_clock::now(); if(isUpToDate)
{
validateObjects();
}
nearObjects.reset(); if(isUpToDate && invalidated.empty())
farObjects.reset(); return;
blockedObjects.clear();
auto start = std::chrono::high_resolution_clock::now();
logAi->debug("Begin object clusterization"); logAi->debug("Begin object clusterization");
std::vector<const CGObjectInstance *> objs( std::vector<const CGObjectInstance *> objs;
if(isUpToDate)
{
for(auto id : invalidated)
{
auto obj = cb->getObj(id, false);
if(obj)
{
objs.push_back(obj);
}
}
invalidated.clear();
}
else
{
nearObjects.reset();
farObjects.reset();
blockedObjects.clear();
invalidated.clear();
objs = std::vector<const CGObjectInstance *>(
ai->memory->visitableObjs.begin(), ai->memory->visitableObjs.begin(),
ai->memory->visitableObjs.end()); ai->memory->visitableObjs.end());
}
#if NKAI_TRACE_LEVEL == 0 #if NKAI_TRACE_LEVEL == 0
tbb::parallel_for(tbb::blocked_range<size_t>(0, objs.size()), [&](const tbb::blocked_range<size_t> & r) { tbb::parallel_for(tbb::blocked_range<size_t>(0, objs.size()), [&](const tbb::blocked_range<size_t> & r) {
@ -256,16 +346,20 @@ void ObjectClusterizer::clusterize()
for(auto pair : blockedObjects) for(auto pair : blockedObjects)
{ {
logAi->trace("Cluster %s %s count: %i", pair.first->getObjectName(), pair.first->visitablePos().toString(), pair.second->objects.size()); auto blocker = cb->getObj(pair.first);
logAi->trace("Cluster %s %s count: %i", blocker->getObjectName(), blocker->visitablePos().toString(), pair.second->objects.size());
#if NKAI_TRACE_LEVEL >= 1 #if NKAI_TRACE_LEVEL >= 1
for(auto obj : pair.second->getObjects()) for(auto obj : pair.second->getObjects(ai->cb.get()))
{ {
logAi->trace("Object %s %s", obj->getObjectName(), obj->visitablePos().toString()); logAi->trace("Object %s %s", obj->getObjectName(), obj->visitablePos().toString());
} }
#endif #endif
} }
isUpToDate = true;
logAi->trace("Clusterization complete in %ld", timeElapsed(start)); logAi->trace("Clusterization complete in %ld", timeElapsed(start));
} }
@ -381,7 +475,7 @@ void ObjectClusterizer::clusterizeObject(
ClusterMap::accessor cluster; ClusterMap::accessor cluster;
blockedObjects.insert( blockedObjects.insert(
cluster, cluster,
ClusterMap::value_type(blocker, std::make_shared<ObjectCluster>(blocker))); ClusterMap::value_type(blocker->id, std::make_shared<ObjectCluster>(blocker)));
cluster->second->addObject(obj, path, priority); cluster->second->addObject(obj, path, priority);

View File

@ -23,7 +23,15 @@ struct ClusterObjectInfo
uint8_t turn; uint8_t turn;
}; };
using ClusterObjects = tbb::concurrent_hash_map<const CGObjectInstance *, ClusterObjectInfo>; struct ObjectInstanceIDHash
{
ObjectInstanceID::hash hash;
bool equal(ObjectInstanceID o1, ObjectInstanceID o2) const
{
return o1 == o2;
}
};
using ClusterObjects = tbb::concurrent_hash_map<ObjectInstanceID, ClusterObjectInfo, ObjectInstanceIDHash>;
struct ObjectCluster struct ObjectCluster
{ {
@ -44,11 +52,11 @@ public:
{ {
} }
std::vector<const CGObjectInstance *> getObjects() const; std::vector<const CGObjectInstance *> getObjects(const CPlayerSpecificInfoCallback * cb) const;
const CGObjectInstance * calculateCenter() const; const CGObjectInstance * calculateCenter(const CPlayerSpecificInfoCallback * cb) const;
}; };
using ClusterMap = tbb::concurrent_hash_map<const CGObjectInstance *, std::shared_ptr<ObjectCluster>>; using ClusterMap = tbb::concurrent_hash_map<ObjectInstanceID, std::shared_ptr<ObjectCluster>, ObjectInstanceIDHash>;
class ObjectClusterizer class ObjectClusterizer
{ {
@ -60,6 +68,8 @@ private:
ClusterMap blockedObjects; ClusterMap blockedObjects;
const Nullkiller * ai; const Nullkiller * ai;
RewardEvaluator valueEvaluator; RewardEvaluator valueEvaluator;
bool isUpToDate;
std::vector<ObjectInstanceID> invalidated;
public: public:
void clusterize(); void clusterize();
@ -69,7 +79,16 @@ 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): ai(ai), valueEvaluator(ai) {} ObjectClusterizer(const Nullkiller * ai): ai(ai), valueEvaluator(ai), isUpToDate(false){}
void validateObjects();
void onObjectRemoved(ObjectInstanceID id);
void invalidate(ObjectInstanceID id);
void reset() {
isUpToDate = false;
invalidated.clear();
}
private: private:
bool shouldVisitObject(const CGObjectInstance * obj) const; bool shouldVisitObject(const CGObjectInstance * obj) const;

View File

@ -41,7 +41,7 @@ Goals::TGoalVec ClusterBehavior::decompose(const Nullkiller * ai) const
Goals::TGoalVec ClusterBehavior::decomposeCluster(const Nullkiller * ai, std::shared_ptr<ObjectCluster> cluster) const Goals::TGoalVec ClusterBehavior::decomposeCluster(const Nullkiller * ai, std::shared_ptr<ObjectCluster> cluster) const
{ {
auto center = cluster->calculateCenter(); auto center = cluster->calculateCenter(ai->cb.get());
auto paths = ai->pathfinder->getPathInfo(center->visitablePos(), ai->settings->isObjectGraphAllowed()); auto paths = ai->pathfinder->getPathInfo(center->visitablePos(), ai->settings->isObjectGraphAllowed());
auto blockerPos = cluster->blocker->visitablePos(); auto blockerPos = cluster->blocker->visitablePos();

View File

@ -71,7 +71,15 @@ bool needToRecruitHero(const Nullkiller * ai, const CGTownInstance * startupTown
for(auto obj : ai->objectClusterizer->getNearbyObjects()) for(auto obj : ai->objectClusterizer->getNearbyObjects())
{ {
if((obj->ID == Obj::RESOURCE && dynamic_cast<const CGResource *>(obj)->resourceID() == EGameResID::GOLD) auto armed = dynamic_cast<const CArmedInstance *>(obj);
if(armed && armed->getArmyStrength() > 0)
continue;
bool isGoldPile = dynamic_cast<const CGResource *>(obj)
&& dynamic_cast<const CGResource *>(obj)->resourceID() == EGameResID::GOLD;
if(isGoldPile
|| obj->ID == Obj::TREASURE_CHEST || obj->ID == Obj::TREASURE_CHEST
|| obj->ID == Obj::CAMPFIRE || obj->ID == Obj::CAMPFIRE
|| obj->ID == Obj::WATER_WHEEL) || obj->ID == Obj::WATER_WHEEL)

View File

@ -37,10 +37,8 @@ void DeepDecomposer::reset()
goals.clear(); goals.clear();
} }
Goals::TGoalVec DeepDecomposer::decompose(TSubgoal behavior, int depthLimit) void DeepDecomposer::decompose(TGoalVec & result, TSubgoal behavior, int depthLimit)
{ {
TGoalVec tasks;
goals.clear(); goals.clear();
goals.resize(depthLimit); goals.resize(depthLimit);
decompositionCache.resize(depthLimit); decompositionCache.resize(depthLimit);
@ -79,7 +77,7 @@ Goals::TGoalVec DeepDecomposer::decompose(TSubgoal behavior, int depthLimit)
#endif #endif
if(!isCompositionLoop(subgoal)) if(!isCompositionLoop(subgoal))
{ {
tasks.push_back(task); result.push_back(task);
if(!fromCache) if(!fromCache)
{ {
@ -121,8 +119,6 @@ Goals::TGoalVec DeepDecomposer::decompose(TSubgoal behavior, int depthLimit)
} }
} }
} }
return tasks;
} }
Goals::TSubgoal DeepDecomposer::aggregateGoals(int startDepth, TSubgoal last) Goals::TSubgoal DeepDecomposer::aggregateGoals(int startDepth, TSubgoal last)

View File

@ -35,7 +35,7 @@ private:
public: public:
DeepDecomposer(const Nullkiller * ai); DeepDecomposer(const Nullkiller * ai);
void reset(); void reset();
Goals::TGoalVec decompose(Goals::TSubgoal behavior, int depthLimit); void decompose(Goals::TGoalVec & result, Goals::TSubgoal behavior, int depthLimit);
private: private:
Goals::TSubgoal aggregateGoals(int startDepth, Goals::TSubgoal last); Goals::TSubgoal aggregateGoals(int startDepth, Goals::TSubgoal last);

View File

@ -64,16 +64,104 @@ void Nullkiller::init(std::shared_ptr<CCallback> cb, AIGateway * gateway)
armyFormation.reset(new ArmyFormation(cb, this)); armyFormation.reset(new ArmyFormation(cb, this));
} }
Goals::TTask Nullkiller::choseBestTask(Goals::TTaskVec & tasks) const TaskPlanItem::TaskPlanItem(TSubgoal task)
:task(task), affectedObjects(task->asTask()->getAffectedObjects())
{ {
Goals::TTask bestTask = *vstd::maxElementByFun(tasks, [](Goals::TTask task) -> float{
return task->priority;
});
return bestTask;
} }
Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior, int decompositionMaxDepth) const Goals::TTaskVec TaskPlan::getTasks() const
{
Goals::TTaskVec result;
for(auto & item : tasks)
{
result.push_back(taskptr(*item.task));
}
vstd::removeDuplicates(result);
return result;
}
void TaskPlan::merge(TSubgoal task)
{
TGoalVec blockers;
for(auto & item : tasks)
{
for(auto objid : item.affectedObjects)
{
if(task == item.task || task->asTask()->isObjectAffected(objid))
{
if(item.task->asTask()->priority >= task->asTask()->priority)
return;
blockers.push_back(item.task);
break;
}
}
}
vstd::erase_if(tasks, [&](const TaskPlanItem & task)
{
return vstd::contains(blockers, task.task);
});
tasks.emplace_back(task);
}
Goals::TTask Nullkiller::choseBestTask(Goals::TGoalVec & tasks) const
{
if(tasks.empty())
{
return taskptr(Invalid());
}
for(TSubgoal & task : tasks)
{
if(task->asTask()->priority <= 0)
task->asTask()->priority = priorityEvaluator->evaluate(task);
}
auto bestTask = *vstd::maxElementByFun(tasks, [](Goals::TSubgoal task) -> float
{
return task->asTask()->priority;
});
return taskptr(*bestTask);
}
Goals::TTaskVec Nullkiller::buildPlan(TGoalVec & tasks) const
{
TaskPlan taskPlan;
tbb::parallel_for(tbb::blocked_range<size_t>(0, tasks.size()), [this, &tasks](const tbb::blocked_range<size_t> & r)
{
auto evaluator = this->priorityEvaluators->acquire();
for(size_t i = r.begin(); i != r.end(); i++)
{
auto task = tasks[i];
if(task->asTask()->priority <= 0)
task->asTask()->priority = evaluator->evaluate(task);
}
});
std::sort(tasks.begin(), tasks.end(), [](TSubgoal g1, TSubgoal g2) -> bool
{
return g2->asTask()->priority < g1->asTask()->priority;
});
for(TSubgoal & task : tasks)
{
taskPlan.merge(task);
}
return taskPlan.getTasks();
}
void Nullkiller::decompose(Goals::TGoalVec & result, Goals::TSubgoal behavior, int decompositionMaxDepth) const
{ {
boost::this_thread::interruption_point(); boost::this_thread::interruption_point();
@ -81,38 +169,14 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior, int decompositi
auto start = std::chrono::high_resolution_clock::now(); auto start = std::chrono::high_resolution_clock::now();
Goals::TGoalVec elementarGoals = decomposer->decompose(behavior, decompositionMaxDepth); decomposer->decompose(result, behavior, decompositionMaxDepth);
Goals::TTaskVec tasks;
boost::this_thread::interruption_point(); boost::this_thread::interruption_point();
for(auto goal : elementarGoals)
{
Goals::TTask task = Goals::taskptr(*goal);
if(task->priority <= 0)
task->priority = priorityEvaluator->evaluate(goal);
tasks.push_back(task);
}
if(tasks.empty())
{
logAi->debug("Behavior %s found no tasks. Time taken %ld", behavior->toString(), timeElapsed(start));
return Goals::taskptr(Goals::Invalid());
}
auto task = choseBestTask(tasks);
logAi->debug( logAi->debug(
"Behavior %s returns %s, priority %f. Time taken %ld", "Behavior %s. Time taken %ld",
behavior->toString(), behavior->toString(),
task->toString(),
task->priority,
timeElapsed(start)); timeElapsed(start));
return task;
} }
void Nullkiller::resetAiState() void Nullkiller::resetAiState()
@ -124,6 +188,7 @@ void Nullkiller::resetAiState()
lockedHeroes.clear(); lockedHeroes.clear();
dangerHitMap->reset(); dangerHitMap->reset();
useHeroChain = true; useHeroChain = true;
objectClusterizer->reset();
if(!baseGraph && settings->isObjectGraphAllowed()) if(!baseGraph && settings->isObjectGraphAllowed())
{ {
@ -251,6 +316,8 @@ void Nullkiller::makeTurn()
resetAiState(); resetAiState();
Goals::TGoalVec bestTasks;
for(int i = 1; i <= settings->getMaxPass(); i++) for(int i = 1; i <= settings->getMaxPass(); i++)
{ {
auto start = std::chrono::high_resolution_clock::now(); auto start = std::chrono::high_resolution_clock::now();
@ -260,16 +327,18 @@ void Nullkiller::makeTurn()
for(;i <= settings->getMaxPass(); i++) for(;i <= settings->getMaxPass(); i++)
{ {
Goals::TTaskVec fastTasks = { bestTasks.clear();
choseBestTask(sptr(BuyArmyBehavior()), 1),
choseBestTask(sptr(BuildingBehavior()), 1)
};
bestTask = choseBestTask(fastTasks); decompose(bestTasks, sptr(BuyArmyBehavior()), 1);
decompose(bestTasks, sptr(BuildingBehavior()), 1);
bestTask = choseBestTask(bestTasks);
if(bestTask->priority >= FAST_TASK_MINIMAL_PRIORITY) if(bestTask->priority >= FAST_TASK_MINIMAL_PRIORITY)
{ {
executeTask(bestTask); if(!executeTask(bestTask))
return;
updateAiState(i, true); updateAiState(i, true);
} }
else else
@ -278,23 +347,31 @@ void Nullkiller::makeTurn()
} }
} }
Goals::TTaskVec bestTasks = { decompose(bestTasks, sptr(RecruitHeroBehavior()), 1);
bestTask, decompose(bestTasks, sptr(CaptureObjectsBehavior()), 1);
choseBestTask(sptr(RecruitHeroBehavior()), 1), decompose(bestTasks, sptr(ClusterBehavior()), MAX_DEPTH);
choseBestTask(sptr(CaptureObjectsBehavior()), 1), decompose(bestTasks, sptr(DefenceBehavior()), MAX_DEPTH);
choseBestTask(sptr(ClusterBehavior()), MAX_DEPTH), decompose(bestTasks, sptr(GatherArmyBehavior()), MAX_DEPTH);
choseBestTask(sptr(DefenceBehavior()), MAX_DEPTH), decompose(bestTasks, sptr(StayAtTownBehavior()), MAX_DEPTH);
choseBestTask(sptr(GatherArmyBehavior()), MAX_DEPTH),
choseBestTask(sptr(StayAtTownBehavior()), MAX_DEPTH)
};
if(cb->getDate(Date::DAY) == 1) if(cb->getDate(Date::DAY) == 1)
{ {
bestTasks.push_back(choseBestTask(sptr(StartupBehavior()), 1)); decompose(bestTasks, sptr(StartupBehavior()), 1);
} }
bestTask = choseBestTask(bestTasks); auto selectedTasks = buildPlan(bestTasks);
logAi->debug("Decission madel in %ld", timeElapsed(start));
if(selectedTasks.empty())
{
return;
}
bool hasAnySuccess = false;
for(auto bestTask : selectedTasks)
{
std::string taskDescription = bestTask->toString(); std::string taskDescription = bestTask->toString();
HeroPtr hero = bestTask->getHero(); HeroPtr hero = bestTask->getHero();
HeroRole heroRole = HeroRole::MAIN; HeroRole heroRole = HeroRole::MAIN;
@ -335,26 +412,40 @@ void Nullkiller::makeTurn()
scanDepth = ScanDepth::ALL_FULL; scanDepth = ScanDepth::ALL_FULL;
useHeroChain = false; useHeroChain = false;
hasAnySuccess = true;
break;;
}
logAi->trace("Goal %s has too low priority. It is not worth doing it.", taskDescription);
continue; continue;
} }
logAi->trace("Goal %s has too low priority. It is not worth doing it. Ending turn.", taskDescription); if(!executeTask(bestTask))
{
if(hasAnySuccess)
break;
else
return; return;
} }
logAi->debug("Decission madel in %ld", timeElapsed(start)); hasAnySuccess = true;
}
executeTask(bestTask); if(!hasAnySuccess)
{
logAi->trace("Nothing was done this turn. Ending turn.");
return;
}
if(i == settings->getMaxPass()) if(i == settings->getMaxPass())
{ {
logAi->warn("Goal %s exceeded maxpass. Terminating AI turn.", taskDescription); logAi->warn("Maxpass exceeded. Terminating AI turn.");
} }
} }
} }
void Nullkiller::executeTask(Goals::TTask task) bool Nullkiller::executeTask(Goals::TTask task)
{ {
auto start = std::chrono::high_resolution_clock::now(); auto start = std::chrono::high_resolution_clock::now();
std::string taskDescr = task->toString(); std::string taskDescr = task->toString();
@ -376,10 +467,10 @@ void Nullkiller::executeTask(Goals::TTask task)
logAi->error("Failed to realize subgoal of type %s, I will stop.", taskDescr); logAi->error("Failed to realize subgoal of type %s, I will stop.", taskDescr);
logAi->error("The error message was: %s", e.what()); logAi->error("The error message was: %s", e.what());
#if NKAI_TRACE_LEVEL == 0 return false;
throw; // will be recatched and AI turn ended
#endif
} }
return true;
} }
TResources Nullkiller::getFreeResources() const TResources Nullkiller::getFreeResources() const

View File

@ -47,6 +47,24 @@ enum class ScanDepth
ALL_FULL = 2 ALL_FULL = 2
}; };
struct TaskPlanItem
{
std::vector<ObjectInstanceID> affectedObjects;
Goals::TSubgoal task;
TaskPlanItem(Goals::TSubgoal goal);
};
class TaskPlan
{
private:
std::vector<TaskPlanItem> tasks;
public:
Goals::TTaskVec getTasks() const;
void merge(Goals::TSubgoal task);
};
class Nullkiller class Nullkiller
{ {
private: private:
@ -102,9 +120,10 @@ public:
private: private:
void resetAiState(); void resetAiState();
void updateAiState(int pass, bool fast = false); void updateAiState(int pass, bool fast = false);
Goals::TTask choseBestTask(Goals::TSubgoal behavior, int decompositionMaxDepth) const; void decompose(Goals::TGoalVec & result, Goals::TSubgoal behavior, int decompositionMaxDepth) const;
Goals::TTask choseBestTask(Goals::TTaskVec & tasks) const; Goals::TTask choseBestTask(Goals::TGoalVec & tasks) const;
void executeTask(Goals::TTask task); Goals::TTaskVec buildPlan(Goals::TGoalVec & tasks) const;
bool executeTask(Goals::TTask task);
}; };
} }

View File

@ -655,7 +655,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
case Obj::RESOURCE: case Obj::RESOURCE:
{ {
auto * res = dynamic_cast<const CGResource*>(target); auto * res = dynamic_cast<const CGResource*>(target);
return res->resourceID() == GameResID::GOLD ? 600 : 100; return res && res->resourceID() == GameResID::GOLD ? 600 : 100;
} }
case Obj::TREASURE_CHEST: case Obj::TREASURE_CHEST:
return 1500; return 1500;
@ -711,8 +711,8 @@ public:
uint64_t armyStrength = heroExchange.getReinforcementArmyStrength(evaluationContext.evaluator.ai); uint64_t armyStrength = heroExchange.getReinforcementArmyStrength(evaluationContext.evaluator.ai);
evaluationContext.addNonCriticalStrategicalValue(2.0f * armyStrength / (float)heroExchange.hero.get()->getArmyStrength()); evaluationContext.addNonCriticalStrategicalValue(2.0f * armyStrength / (float)heroExchange.hero->getArmyStrength());
evaluationContext.heroRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(heroExchange.hero.get()); evaluationContext.heroRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(heroExchange.hero);
} }
}; };
@ -743,7 +743,7 @@ public:
Goals::StayAtTown & stayAtTown = dynamic_cast<Goals::StayAtTown &>(*task); Goals::StayAtTown & stayAtTown = dynamic_cast<Goals::StayAtTown &>(*task);
evaluationContext.armyReward += evaluationContext.evaluator.getManaRecoveryArmyReward(stayAtTown.getHero().get()); evaluationContext.armyReward += evaluationContext.evaluator.getManaRecoveryArmyReward(stayAtTown.getHero());
evaluationContext.movementCostByRole[evaluationContext.heroRole] += stayAtTown.getMovementWasted(); evaluationContext.movementCostByRole[evaluationContext.heroRole] += stayAtTown.getMovementWasted();
evaluationContext.movementCost += stayAtTown.getMovementWasted(); evaluationContext.movementCost += stayAtTown.getMovementWasted();
} }
@ -792,7 +792,7 @@ public:
if(defendTown.getTurn() > 0 && defendTown.isCounterAttack()) if(defendTown.getTurn() > 0 && defendTown.isCounterAttack())
{ {
auto ourSpeed = defendTown.hero->movementPointsLimit(true); auto ourSpeed = defendTown.hero->movementPointsLimit(true);
auto enemySpeed = treat.hero->movementPointsLimit(true); auto enemySpeed = treat.hero.get(evaluationContext.evaluator.ai->cb.get())->movementPointsLimit(true);
if(enemySpeed > ourSpeed) multiplier *= 0.7f; if(enemySpeed > ourSpeed) multiplier *= 0.7f;
} }
@ -847,13 +847,12 @@ public:
evaluationContext.movementCostByRole[role] += pair.second; evaluationContext.movementCostByRole[role] += pair.second;
} }
auto heroPtr = task->hero; auto hero = task->hero;
auto hero = heroPtr.get(ai->cb.get());
bool checkGold = evaluationContext.danger == 0; bool checkGold = evaluationContext.danger == 0;
auto army = path.heroArmy; auto army = path.heroArmy;
const CGObjectInstance * target = ai->cb->getObj((ObjectInstanceID)task->objid, false); const CGObjectInstance * target = ai->cb->getObj((ObjectInstanceID)task->objid, false);
auto heroRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(heroPtr); auto heroRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(hero);
if(heroRole == HeroRole::MAIN) if(heroRole == HeroRole::MAIN)
evaluationContext.heroRole = heroRole; evaluationContext.heroRole = heroRole;
@ -887,21 +886,21 @@ public:
Goals::UnlockCluster & clusterGoal = dynamic_cast<Goals::UnlockCluster &>(*task); Goals::UnlockCluster & clusterGoal = dynamic_cast<Goals::UnlockCluster &>(*task);
std::shared_ptr<ObjectCluster> cluster = clusterGoal.getCluster(); std::shared_ptr<ObjectCluster> cluster = clusterGoal.getCluster();
auto hero = clusterGoal.hero.get(); auto hero = clusterGoal.hero;
auto role = evaluationContext.evaluator.ai->heroManager->getHeroRole(clusterGoal.hero); auto role = evaluationContext.evaluator.ai->heroManager->getHeroRole(hero);
std::vector<std::pair<const CGObjectInstance *, ClusterObjectInfo>> objects(cluster->objects.begin(), cluster->objects.end()); std::vector<std::pair<ObjectInstanceID, ClusterObjectInfo>> objects(cluster->objects.begin(), cluster->objects.end());
std::sort(objects.begin(), objects.end(), [](std::pair<const CGObjectInstance *, ClusterObjectInfo> o1, std::pair<const CGObjectInstance *, ClusterObjectInfo> o2) -> bool std::sort(objects.begin(), objects.end(), [](std::pair<ObjectInstanceID, ClusterObjectInfo> o1, std::pair<ObjectInstanceID, ClusterObjectInfo> o2) -> bool
{ {
return o1.second.priority > o2.second.priority; return o1.second.priority > o2.second.priority;
}); });
int boost = 1; int boost = 1;
for(auto objInfo : objects) for(auto & objInfo : objects)
{ {
auto target = objInfo.first; auto target = evaluationContext.evaluator.ai->cb->getObj(objInfo.first);
bool checkGold = objInfo.second.danger == 0; bool checkGold = objInfo.second.danger == 0;
auto army = hero; auto army = hero;
@ -960,7 +959,7 @@ public:
return; return;
Goals::DismissHero & dismissCommand = dynamic_cast<Goals::DismissHero &>(*task); Goals::DismissHero & dismissCommand = dynamic_cast<Goals::DismissHero &>(*task);
const CGHeroInstance * dismissedHero = dismissCommand.getHero().get(); const CGHeroInstance * dismissedHero = dismissCommand.getHero();
auto role = ai->heroManager->getHeroRole(dismissedHero); auto role = ai->heroManager->getHeroRole(dismissedHero);
auto mpLeft = dismissedHero->movementPointsRemaining(); auto mpLeft = dismissedHero->movementPointsRemaining();

View File

@ -31,12 +31,12 @@ TTask Goals::taskptr(const AbstractGoal & tmp)
if(!tmp.isElementar()) if(!tmp.isElementar())
throw cannotFulfillGoalException(tmp.toString() + " is not elementar"); throw cannotFulfillGoalException(tmp.toString() + " is not elementar");
ptr.reset(dynamic_cast<ITask *>(tmp.clone())); ptr.reset(tmp.clone()->asTask());
return ptr; return ptr;
} }
std::string AbstractGoal::toString() const //TODO: virtualize std::string AbstractGoal::toString() const
{ {
std::string desc; std::string desc;
switch(goalType) switch(goalType)
@ -63,8 +63,10 @@ std::string AbstractGoal::toString() const //TODO: virtualize
default: default:
return std::to_string(goalType); return std::to_string(goalType);
} }
if(hero.get(true)) //FIXME: used to crash when we lost hero and failed goal
if(hero)
desc += " (" + hero->getNameTranslated() + ")"; desc += " (" + hero->getNameTranslated() + ")";
return desc; return desc;
} }

View File

@ -10,9 +10,8 @@
#pragma once #pragma once
#include "../../../lib/VCMI_Lib.h" #include "../../../lib/VCMI_Lib.h"
#include "../../../lib/CBuildingHandler.h" #include "../../../lib/mapObjects/CGTownInstance.h"
#include "../../../lib/CCreatureHandler.h" #include "../../../lib/mapObjects/CGHeroInstance.h"
#include "../../../lib/CTownHandler.h"
#include "../AIUtility.h" #include "../AIUtility.h"
namespace NKAI namespace NKAI
@ -106,7 +105,7 @@ namespace Goals
int objid; SETTER(int, objid) int objid; SETTER(int, objid)
int aid; SETTER(int, aid) int aid; SETTER(int, aid)
int3 tile; SETTER(int3, tile) int3 tile; SETTER(int3, tile)
HeroPtr hero; SETTER(HeroPtr, 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)
@ -119,6 +118,7 @@ namespace Goals
objid = -1; objid = -1;
tile = int3(-1, -1, -1); tile = int3(-1, -1, -1);
town = nullptr; town = nullptr;
hero = nullptr;
bid = -1; bid = -1;
goldCost = 0; goldCost = 0;
} }
@ -148,6 +148,11 @@ namespace Goals
virtual uint64_t getHash() const { return 0; } virtual uint64_t getHash() const { return 0; }
virtual ITask * asTask()
{
throw std::runtime_error("Abstract goal is not a task");
}
bool operator!=(const AbstractGoal & g) const bool operator!=(const AbstractGoal & g) const
{ {
return !(*this == g); return !(*this == g);
@ -165,9 +170,11 @@ namespace Goals
//TODO: make accept work for std::shared_ptr... somehow //TODO: make accept work for std::shared_ptr... somehow
virtual void accept(AIGateway * ai) = 0; //unhandled goal will report standard error virtual void accept(AIGateway * ai) = 0; //unhandled goal will report standard error
virtual std::string toString() const = 0; virtual std::string toString() const = 0;
virtual HeroPtr getHero() const = 0; virtual const CGHeroInstance * getHero() const = 0;
virtual ~ITask() {} virtual ~ITask() {}
virtual int getHeroExchangeCount() const = 0; virtual int getHeroExchangeCount() const = 0;
virtual bool isObjectAffected(ObjectInstanceID h) const = 0;
virtual std::vector<ObjectInstanceID> getAffectedObjects() const = 0;
}; };
} }

View File

@ -18,12 +18,12 @@ using namespace Goals;
bool AdventureSpellCast::operator==(const AdventureSpellCast & other) const bool AdventureSpellCast::operator==(const AdventureSpellCast & other) const
{ {
return hero.h == other.hero.h; return hero == other.hero;
} }
void AdventureSpellCast::accept(AIGateway * ai) void AdventureSpellCast::accept(AIGateway * ai)
{ {
if(!hero.validAndSet()) if(!hero)
throw cannotFulfillGoalException("Invalid hero!"); throw cannotFulfillGoalException("Invalid hero!");
auto spell = getSpell(); auto spell = getSpell();
@ -56,7 +56,7 @@ void AdventureSpellCast::accept(AIGateway * ai)
auto wait = cb->waitTillRealize; auto wait = cb->waitTillRealize;
cb->waitTillRealize = true; cb->waitTillRealize = true;
cb->castSpell(hero.h, spellID, tile); cb->castSpell(hero, spellID, tile);
if(town && spellID == SpellID::TOWN_PORTAL) if(town && spellID == SpellID::TOWN_PORTAL)
{ {

View File

@ -22,7 +22,7 @@ namespace Goals
SpellID spellID; SpellID spellID;
public: public:
AdventureSpellCast(HeroPtr hero, SpellID spellID) AdventureSpellCast(const CGHeroInstance * hero, SpellID spellID)
: ElementarGoal(Goals::ADVENTURE_SPELL_CAST), spellID(spellID) : ElementarGoal(Goals::ADVENTURE_SPELL_CAST), spellID(spellID)
{ {
sethero(hero); sethero(hero);

View File

@ -14,7 +14,6 @@
namespace NKAI namespace NKAI
{ {
struct HeroPtr;
class AIGateway; class AIGateway;
namespace Goals namespace Goals
@ -92,9 +91,37 @@ namespace Goals
bool isElementar() const override { return true; } bool isElementar() const override { return true; }
HeroPtr getHero() const override { return AbstractGoal::hero; } const CGHeroInstance * getHero() const override { return AbstractGoal::hero; }
int getHeroExchangeCount() const override { return 0; } int getHeroExchangeCount() const override { return 0; }
bool isObjectAffected(ObjectInstanceID id) const override
{
return (AbstractGoal::hero && AbstractGoal::hero->id == id)
|| AbstractGoal::objid == id
|| (AbstractGoal::town && AbstractGoal::town->id == id);
}
std::vector<ObjectInstanceID> getAffectedObjects() const override
{
auto result = std::vector<ObjectInstanceID>();
if(AbstractGoal::hero)
result.push_back(AbstractGoal::hero->id);
if(AbstractGoal::objid != -1)
result.push_back(ObjectInstanceID(AbstractGoal::objid));
if(AbstractGoal::town)
result.push_back(AbstractGoal::town->id);
return result;
}
ITask * asTask() override
{
return this;
}
}; };
} }

View File

@ -117,4 +117,36 @@ int Composition::getHeroExchangeCount() const
return result; return result;
} }
std::vector<ObjectInstanceID> Composition::getAffectedObjects() const
{
std::vector<ObjectInstanceID> affectedObjects;
for(auto sequence : subtasks)
{
for(auto task : sequence)
{
if(task->isElementar())
vstd::concatenate(affectedObjects, task->asTask()->getAffectedObjects());
}
}
vstd::removeDuplicates(affectedObjects);
return affectedObjects;
}
bool Composition::isObjectAffected(ObjectInstanceID id) const
{
for(auto sequence : subtasks)
{
for(auto task : sequence)
{
if(task->isElementar() && task->asTask()->isObjectAffected(id))
return true;
}
}
return false;
}
} }

View File

@ -35,6 +35,9 @@ namespace Goals
TGoalVec decompose(const Nullkiller * ai) const override; TGoalVec decompose(const Nullkiller * ai) const override;
bool isElementar() const override; bool isElementar() const override;
int getHeroExchangeCount() const override; int getHeroExchangeCount() const override;
std::vector<ObjectInstanceID> getAffectedObjects() const override;
bool isObjectAffected(ObjectInstanceID id) const override;
}; };
} }

View File

@ -20,7 +20,7 @@ using namespace Goals;
bool DigAtTile::operator==(const DigAtTile & other) const bool DigAtTile::operator==(const DigAtTile & other) const
{ {
return other.hero.h == hero.h && other.tile == tile; return other.hero == hero && other.tile == tile;
} }
// //
//TSubgoal DigAtTile::decomposeSingle() const //TSubgoal DigAtTile::decomposeSingle() const

View File

@ -18,22 +18,22 @@ using namespace Goals;
bool DismissHero::operator==(const DismissHero & other) const bool DismissHero::operator==(const DismissHero & other) const
{ {
return hero.h == other.hero.h; return hero == other.hero;
} }
void DismissHero::accept(AIGateway * ai) void DismissHero::accept(AIGateway * ai)
{ {
if(!hero.validAndSet()) if(!hero)
throw cannotFulfillGoalException("Invalid hero!"); throw cannotFulfillGoalException("Invalid hero!");
cb->dismissHero(hero.h); cb->dismissHero(hero);
throw goalFulfilledException(sptr(*this)); throw goalFulfilledException(sptr(*this));
} }
std::string DismissHero::toString() const std::string DismissHero::toString() const
{ {
return "DismissHero " + hero.name; return "DismissHero " + heroName;
} }
} }

View File

@ -17,11 +17,15 @@ namespace Goals
{ {
class DLL_EXPORT DismissHero : public ElementarGoal<DismissHero> class DLL_EXPORT DismissHero : public ElementarGoal<DismissHero>
{ {
private:
std::string heroName;
public: public:
DismissHero(HeroPtr hero) DismissHero(const CGHeroInstance * hero)
: ElementarGoal(Goals::DISMISS_HERO) : ElementarGoal(Goals::DISMISS_HERO)
{ {
sethero(hero); sethero(hero);
heroName = hero->getNameTranslated();
} }
void accept(AIGateway * ai) override; void accept(AIGateway * ai) override;

View File

@ -26,6 +26,26 @@ ExchangeSwapTownHeroes::ExchangeSwapTownHeroes(
{ {
} }
std::vector<ObjectInstanceID> ExchangeSwapTownHeroes::getAffectedObjects() const
{
std::vector<ObjectInstanceID> affectedObjects = { town->id };
if(town->garrisonHero)
affectedObjects.push_back(town->garrisonHero->id);
if(town->visitingHero)
affectedObjects.push_back(town->visitingHero->id);
return affectedObjects;
}
bool ExchangeSwapTownHeroes::isObjectAffected(ObjectInstanceID id) const
{
return town->id == id
|| (town->visitingHero && town->visitingHero->id == id)
|| (town->garrisonHero && town->garrisonHero->id == id);
}
std::string ExchangeSwapTownHeroes::toString() const std::string ExchangeSwapTownHeroes::toString() const
{ {
return "Exchange and swap heroes of " + town->getNameTranslated(); return "Exchange and swap heroes of " + town->getNameTranslated();

View File

@ -35,6 +35,9 @@ namespace Goals
const CGHeroInstance * getGarrisonHero() const { return garrisonHero; } const CGHeroInstance * getGarrisonHero() const { return garrisonHero; }
HeroLockedReason getLockingReason() const { return lockingReason; } HeroLockedReason getLockingReason() const { return lockingReason; }
std::vector<ObjectInstanceID> getAffectedObjects() const override;
bool isObjectAffected(ObjectInstanceID id) const override;
}; };
} }

View File

@ -42,6 +42,38 @@ bool ExecuteHeroChain::operator==(const ExecuteHeroChain & other) const
&& chainPath.chainMask == other.chainPath.chainMask; && chainPath.chainMask == other.chainPath.chainMask;
} }
std::vector<ObjectInstanceID> ExecuteHeroChain::getAffectedObjects() const
{
std::vector<ObjectInstanceID> affectedObjects = { chainPath.targetHero->id };
if(objid != -1)
affectedObjects.push_back(ObjectInstanceID(objid));
for(auto & node : chainPath.nodes)
{
if(node.targetHero)
affectedObjects.push_back(node.targetHero->id);
}
vstd::removeDuplicates(affectedObjects);
return affectedObjects;
}
bool ExecuteHeroChain::isObjectAffected(ObjectInstanceID id) const
{
if(chainPath.targetHero->id == id || objid == id)
return true;
for(auto & node : chainPath.nodes)
{
if(node.targetHero && node.targetHero->id == id)
return true;
}
return false;
}
void ExecuteHeroChain::accept(AIGateway * ai) void ExecuteHeroChain::accept(AIGateway * ai)
{ {
logAi->debug("Executing hero chain towards %s. Path %s", targetName, chainPath.toString()); logAi->debug("Executing hero chain towards %s. Path %s", targetName, chainPath.toString());

View File

@ -34,6 +34,9 @@ namespace Goals
int getHeroExchangeCount() const override { return chainPath.exchangeCount; } int getHeroExchangeCount() const override { return chainPath.exchangeCount; }
std::vector<ObjectInstanceID> getAffectedObjects() const override;
bool isObjectAffected(ObjectInstanceID id) const override;
private: private:
bool moveHeroToTile(AIGateway * ai, const CGHeroInstance * hero, const int3 & tile); bool moveHeroToTile(AIGateway * ai, const CGHeroInstance * hero, const int3 & tile);
}; };

View File

@ -46,7 +46,7 @@ void StayAtTown::accept(AIGateway * ai)
logAi->error("Hero %s expected visiting town %s", hero->getNameTranslated(), town->getNameTranslated()); logAi->error("Hero %s expected visiting town %s", hero->getNameTranslated(), town->getNameTranslated());
} }
ai->nullkiller->lockHero(hero.get(), HeroLockedReason::DEFENCE); ai->nullkiller->lockHero(hero, HeroLockedReason::DEFENCE);
} }
} }

View File

@ -22,7 +22,7 @@ ArmyUpgrade::ArmyUpgrade(const AIPath & upgradePath, const CGObjectInstance * up
: CGoal(Goals::ARMY_UPGRADE), upgrader(upgrader), upgradeValue(upgrade.upgradeValue), : CGoal(Goals::ARMY_UPGRADE), upgrader(upgrader), upgradeValue(upgrade.upgradeValue),
initialValue(upgradePath.heroArmy->getArmyStrength()), goldCost(upgrade.upgradeCost[EGameResID::GOLD]) initialValue(upgradePath.heroArmy->getArmyStrength()), goldCost(upgrade.upgradeCost[EGameResID::GOLD])
{ {
sethero(upgradePath.targetHero); hero = upgradePath.targetHero;
} }
ArmyUpgrade::ArmyUpgrade(const CGHeroInstance * targetMain, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade) ArmyUpgrade::ArmyUpgrade(const CGHeroInstance * targetMain, const CGObjectInstance * upgrader, const ArmyUpgradeInfo & upgrade)

View File

@ -22,7 +22,7 @@ DefendTown::DefendTown(const CGTownInstance * town, const HitMapInfo & treat, co
: CGoal(Goals::DEFEND_TOWN), treat(treat), defenceArmyStrength(defencePath.getHeroStrength()), turn(defencePath.turn()), counterattack(isCounterAttack) : CGoal(Goals::DEFEND_TOWN), treat(treat), defenceArmyStrength(defencePath.getHeroStrength()), turn(defencePath.turn()), counterattack(isCounterAttack)
{ {
settown(town); settown(town);
sethero(defencePath.targetHero); hero = defencePath.targetHero;
} }
DefendTown::DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const CGHeroInstance * defender) DefendTown::DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const CGHeroInstance * defender)

View File

@ -26,12 +26,12 @@ bool HeroExchange::operator==(const HeroExchange & other) const
std::string HeroExchange::toString() const std::string HeroExchange::toString() const
{ {
return "Hero exchange for " +hero.get()->getObjectName() + " by " + exchangePath.toString(); return "Hero exchange for " +hero->getObjectName() + " by " + exchangePath.toString();
} }
uint64_t HeroExchange::getReinforcementArmyStrength(const Nullkiller * ai) const uint64_t HeroExchange::getReinforcementArmyStrength(const Nullkiller * ai) const
{ {
uint64_t armyValue = ai->armyManager->howManyReinforcementsCanGet(hero.get(), exchangePath.heroArmy); uint64_t armyValue = ai->armyManager->howManyReinforcementsCanGet(hero, exchangePath.heroArmy);
return armyValue; return armyValue;
} }

View File

@ -33,7 +33,7 @@ namespace Goals
: CGoal(Goals::UNLOCK_CLUSTER), cluster(cluster), pathToCenter(pathToCenter) : CGoal(Goals::UNLOCK_CLUSTER), cluster(cluster), pathToCenter(pathToCenter)
{ {
tile = cluster->blocker->visitablePos(); tile = cluster->blocker->visitablePos();
sethero(pathToCenter.targetHero); hero = pathToCenter.targetHero;
} }
bool operator==(const UnlockCluster & other) const override; bool operator==(const UnlockCluster & other) const override;

View File

@ -105,7 +105,11 @@ void AIPathfinder::updatePaths(const std::map<const CGHeroInstance *, HeroRole>
cb->calculatePaths(config); cb->calculatePaths(config);
if(!pathfinderSettings.useHeroChain) if(!pathfinderSettings.useHeroChain)
{
logAi->trace("Recalculated paths in %ld", timeElapsed(start));
return; return;
}
do do
{ {

View File

@ -35,7 +35,10 @@ namespace AIPathfinding
return dynamic_cast<const IQuestObject *>(questInfo.obj)->checkQuest(hero); return dynamic_cast<const IQuestObject *>(questInfo.obj)->checkQuest(hero);
} }
return questInfo.quest->activeForPlayers.count(hero->getOwner()) auto notActivated = !questInfo.obj->wasVisited(ai->playerID)
&& !questInfo.quest->activeForPlayers.count(hero->getOwner());
return notActivated
|| questInfo.quest->checkQuest(hero); || questInfo.quest->checkQuest(hero);
} }