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:
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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
|
||||||
//
|
//
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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();
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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();
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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());
|
||||||
|
@ -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);
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user