mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-24 22:14:36 +02:00
Merge pull request #3770 from vcmi/nkai-multitask
NKAI: allow multiple tasks to be executed from one calculation
This commit is contained in:
commit
70fad45316
@ -251,6 +251,7 @@ void AIGateway::heroVisit(const CGHeroInstance * visitor, const CGObjectInstance
|
||||
if(start && visitedObj) //we can end visit with null object, anyway
|
||||
{
|
||||
nullkiller->memory->markObjectVisited(visitedObj);
|
||||
nullkiller->objectClusterizer->invalidate(visitedObj->id);
|
||||
}
|
||||
|
||||
status.heroVisit(visitedObj, start);
|
||||
@ -373,6 +374,7 @@ void AIGateway::objectRemoved(const CGObjectInstance * obj, const PlayerColor &
|
||||
return;
|
||||
|
||||
nullkiller->memory->removeFromMemory(obj);
|
||||
nullkiller->objectClusterizer->onObjectRemoved(obj->id);
|
||||
|
||||
if(nullkiller->baseGraph && nullkiller->settings->isObjectGraphAllowed())
|
||||
{
|
||||
@ -1109,7 +1111,24 @@ void AIGateway::recruitCreatures(const CGDwelling * d, const CArmedInstance * re
|
||||
|
||||
if(!recruiter->getSlotFor(creID).validSlot())
|
||||
{
|
||||
continue;
|
||||
for(auto stack : recruiter->Slots())
|
||||
{
|
||||
if(!stack.second->type)
|
||||
continue;
|
||||
|
||||
auto duplicatingSlot = recruiter->getSlotFor(stack.second->type);
|
||||
|
||||
if(duplicatingSlot != stack.first)
|
||||
{
|
||||
cb->mergeStacks(recruiter, recruiter, stack.first, duplicatingSlot);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!recruiter->getSlotFor(creID).validSlot())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
vstd::amin(count, cb->getResourceAmount() / creID.toCreature()->getFullRecruitCost());
|
||||
@ -1152,11 +1171,6 @@ void AIGateway::retrieveVisitableObjs()
|
||||
{
|
||||
for(const CGObjectInstance * obj : myCb->getVisitableObjs(pos, false))
|
||||
{
|
||||
if(!obj->appearance)
|
||||
{
|
||||
logAi->error("Bad!");
|
||||
}
|
||||
|
||||
addVisitableObj(obj);
|
||||
}
|
||||
});
|
||||
@ -1401,7 +1415,7 @@ void AIGateway::tryRealize(Goals::DigAtTile & g)
|
||||
assert(g.hero->visitablePos() == g.tile); //surely we want to crash here?
|
||||
if(g.hero->diggingStatus() == EDiggingStatus::CAN_DIG)
|
||||
{
|
||||
cb->dig(g.hero.get());
|
||||
cb->dig(g.hero);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -91,33 +91,10 @@ bool HeroPtr::operator<(const HeroPtr & rhs) const
|
||||
|
||||
const CGHeroInstance * HeroPtr::get(bool doWeExpectNull) const
|
||||
{
|
||||
//TODO? check if these all assertions every time we get info about hero affect efficiency
|
||||
//
|
||||
//behave terribly when attempting unauthorized access to hero that is not ours (or was lost)
|
||||
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;
|
||||
return get(cb, doWeExpectNull);
|
||||
}
|
||||
|
||||
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
|
||||
//
|
||||
@ -323,6 +300,19 @@ uint64_t timeElapsed(std::chrono::time_point<std::chrono::high_resolution_clock>
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
|
||||
}
|
||||
|
||||
int getDuplicatingSlots(const CArmedInstance * army)
|
||||
{
|
||||
int duplicatingSlots = 0;
|
||||
|
||||
for(auto stack : army->Slots())
|
||||
{
|
||||
if(stack.second->type && army->getSlotFor(stack.second->type) != stack.first)
|
||||
duplicatingSlots++;
|
||||
}
|
||||
|
||||
return duplicatingSlots;
|
||||
}
|
||||
|
||||
// todo: move to obj manager
|
||||
bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObjectInstance * obj)
|
||||
{
|
||||
@ -370,13 +360,14 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
|
||||
return false;
|
||||
|
||||
const CGDwelling * d = dynamic_cast<const CGDwelling *>(obj);
|
||||
auto duplicatingSlotsCount = getDuplicatingSlots(h);
|
||||
|
||||
for(auto level : d->creatures)
|
||||
{
|
||||
for(auto c : level.second)
|
||||
{
|
||||
if(level.first
|
||||
&& h->getSlotFor(CreatureID(c)) != SlotID()
|
||||
&& (h->getSlotFor(CreatureID(c)) != SlotID() || duplicatingSlotsCount > 0)
|
||||
&& ai->cb->getResourceAmount().canAfford(c.toCreature()->getFullRecruitCost()))
|
||||
{
|
||||
return true;
|
||||
|
@ -109,7 +109,7 @@ public:
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@ -242,6 +242,7 @@ uint64_t timeElapsed(std::chrono::time_point<std::chrono::high_resolution_clock>
|
||||
|
||||
// todo: move to obj manager
|
||||
bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObjectInstance * obj);
|
||||
int getDuplicatingSlots(const CArmedInstance * army);
|
||||
|
||||
template <class T>
|
||||
class SharedPool
|
||||
|
@ -20,7 +20,7 @@ void ObjectCluster::addObject(const CGObjectInstance * obj, const AIPath & path,
|
||||
{
|
||||
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)
|
||||
{
|
||||
@ -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);
|
||||
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
|
||||
int3 direction = newPoint - tile;
|
||||
float priorityRatio = newPriority / (priority + newPriority);
|
||||
@ -48,21 +47,21 @@ const CGObjectInstance * ObjectCluster::calculateCenter() const
|
||||
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;
|
||||
|
||||
for(auto pair : objects)
|
||||
for(auto & pair : objects)
|
||||
{
|
||||
result.push_back(pair.first);
|
||||
result.push_back(cb->getObj(pair.first));
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -70,19 +69,19 @@ std::vector<const CGObjectInstance *> ObjectCluster::getObjects() const
|
||||
|
||||
std::vector<const CGObjectInstance *> ObjectClusterizer::getNearbyObjects() const
|
||||
{
|
||||
return nearObjects.getObjects();
|
||||
return nearObjects.getObjects(ai->cb.get());
|
||||
}
|
||||
|
||||
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>> result;
|
||||
|
||||
for(auto pair : blockedObjects)
|
||||
for(auto & pair : blockedObjects)
|
||||
{
|
||||
result.push_back(pair.second);
|
||||
}
|
||||
@ -163,6 +162,69 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons
|
||||
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
|
||||
{
|
||||
if(isObjectRemovable(obj))
|
||||
@ -222,17 +284,45 @@ Obj ObjectClusterizer::IgnoredObjectTypes[] = {
|
||||
|
||||
void ObjectClusterizer::clusterize()
|
||||
{
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
if(isUpToDate)
|
||||
{
|
||||
validateObjects();
|
||||
}
|
||||
|
||||
nearObjects.reset();
|
||||
farObjects.reset();
|
||||
blockedObjects.clear();
|
||||
if(isUpToDate && invalidated.empty())
|
||||
return;
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
logAi->debug("Begin object clusterization");
|
||||
|
||||
std::vector<const CGObjectInstance *> objs(
|
||||
ai->memory->visitableObjs.begin(),
|
||||
ai->memory->visitableObjs.end());
|
||||
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.end());
|
||||
}
|
||||
|
||||
#if NKAI_TRACE_LEVEL == 0
|
||||
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)
|
||||
{
|
||||
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
|
||||
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());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
isUpToDate = true;
|
||||
|
||||
logAi->trace("Clusterization complete in %ld", timeElapsed(start));
|
||||
}
|
||||
|
||||
@ -381,7 +475,7 @@ void ObjectClusterizer::clusterizeObject(
|
||||
ClusterMap::accessor cluster;
|
||||
blockedObjects.insert(
|
||||
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);
|
||||
|
||||
|
@ -23,7 +23,15 @@ struct ClusterObjectInfo
|
||||
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
|
||||
{
|
||||
@ -44,11 +52,11 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<const CGObjectInstance *> getObjects() const;
|
||||
const CGObjectInstance * calculateCenter() const;
|
||||
std::vector<const CGObjectInstance *> getObjects(const CPlayerSpecificInfoCallback * cb) 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
|
||||
{
|
||||
@ -60,6 +68,8 @@ private:
|
||||
ClusterMap blockedObjects;
|
||||
const Nullkiller * ai;
|
||||
RewardEvaluator valueEvaluator;
|
||||
bool isUpToDate;
|
||||
std::vector<ObjectInstanceID> invalidated;
|
||||
|
||||
public:
|
||||
void clusterize();
|
||||
@ -69,7 +79,16 @@ public:
|
||||
const CGObjectInstance * getBlocker(const AIPath & path) 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:
|
||||
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
|
||||
{
|
||||
auto center = cluster->calculateCenter();
|
||||
auto center = cluster->calculateCenter(ai->cb.get());
|
||||
auto paths = ai->pathfinder->getPathInfo(center->visitablePos(), ai->settings->isObjectGraphAllowed());
|
||||
|
||||
auto blockerPos = cluster->blocker->visitablePos();
|
||||
|
@ -71,7 +71,15 @@ bool needToRecruitHero(const Nullkiller * ai, const CGTownInstance * startupTown
|
||||
|
||||
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::CAMPFIRE
|
||||
|| obj->ID == Obj::WATER_WHEEL)
|
||||
|
@ -37,10 +37,8 @@ void DeepDecomposer::reset()
|
||||
goals.clear();
|
||||
}
|
||||
|
||||
Goals::TGoalVec DeepDecomposer::decompose(TSubgoal behavior, int depthLimit)
|
||||
void DeepDecomposer::decompose(TGoalVec & result, TSubgoal behavior, int depthLimit)
|
||||
{
|
||||
TGoalVec tasks;
|
||||
|
||||
goals.clear();
|
||||
goals.resize(depthLimit);
|
||||
decompositionCache.resize(depthLimit);
|
||||
@ -79,7 +77,7 @@ Goals::TGoalVec DeepDecomposer::decompose(TSubgoal behavior, int depthLimit)
|
||||
#endif
|
||||
if(!isCompositionLoop(subgoal))
|
||||
{
|
||||
tasks.push_back(task);
|
||||
result.push_back(task);
|
||||
|
||||
if(!fromCache)
|
||||
{
|
||||
@ -121,8 +119,6 @@ Goals::TGoalVec DeepDecomposer::decompose(TSubgoal behavior, int depthLimit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tasks;
|
||||
}
|
||||
|
||||
Goals::TSubgoal DeepDecomposer::aggregateGoals(int startDepth, TSubgoal last)
|
||||
|
@ -35,7 +35,7 @@ private:
|
||||
public:
|
||||
DeepDecomposer(const Nullkiller * ai);
|
||||
void reset();
|
||||
Goals::TGoalVec decompose(Goals::TSubgoal behavior, int depthLimit);
|
||||
void decompose(Goals::TGoalVec & result, Goals::TSubgoal behavior, int depthLimit);
|
||||
|
||||
private:
|
||||
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));
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@ -81,38 +169,14 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior, int decompositi
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
Goals::TGoalVec elementarGoals = decomposer->decompose(behavior, decompositionMaxDepth);
|
||||
Goals::TTaskVec tasks;
|
||||
decomposer->decompose(result, behavior, decompositionMaxDepth);
|
||||
|
||||
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(
|
||||
"Behavior %s returns %s, priority %f. Time taken %ld",
|
||||
"Behavior %s. Time taken %ld",
|
||||
behavior->toString(),
|
||||
task->toString(),
|
||||
task->priority,
|
||||
timeElapsed(start));
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
void Nullkiller::resetAiState()
|
||||
@ -124,6 +188,7 @@ void Nullkiller::resetAiState()
|
||||
lockedHeroes.clear();
|
||||
dangerHitMap->reset();
|
||||
useHeroChain = true;
|
||||
objectClusterizer->reset();
|
||||
|
||||
if(!baseGraph && settings->isObjectGraphAllowed())
|
||||
{
|
||||
@ -251,7 +316,9 @@ void Nullkiller::makeTurn()
|
||||
|
||||
resetAiState();
|
||||
|
||||
for(int i = 1; i <= settings->getMaxPass(); i++)
|
||||
Goals::TGoalVec bestTasks;
|
||||
|
||||
for(int i = 1; i <= settings->getMaxPass() && cb->getPlayerStatus(playerID) == EPlayerStatus::INGAME; i++)
|
||||
{
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
updateAiState(i);
|
||||
@ -260,16 +327,18 @@ void Nullkiller::makeTurn()
|
||||
|
||||
for(;i <= settings->getMaxPass(); i++)
|
||||
{
|
||||
Goals::TTaskVec fastTasks = {
|
||||
choseBestTask(sptr(BuyArmyBehavior()), 1),
|
||||
choseBestTask(sptr(BuildingBehavior()), 1)
|
||||
};
|
||||
bestTasks.clear();
|
||||
|
||||
bestTask = choseBestTask(fastTasks);
|
||||
decompose(bestTasks, sptr(BuyArmyBehavior()), 1);
|
||||
decompose(bestTasks, sptr(BuildingBehavior()), 1);
|
||||
|
||||
bestTask = choseBestTask(bestTasks);
|
||||
|
||||
if(bestTask->priority >= FAST_TASK_MINIMAL_PRIORITY)
|
||||
{
|
||||
executeTask(bestTask);
|
||||
if(!executeTask(bestTask))
|
||||
return;
|
||||
|
||||
updateAiState(i, true);
|
||||
}
|
||||
else
|
||||
@ -278,83 +347,108 @@ void Nullkiller::makeTurn()
|
||||
}
|
||||
}
|
||||
|
||||
Goals::TTaskVec bestTasks = {
|
||||
bestTask,
|
||||
choseBestTask(sptr(RecruitHeroBehavior()), 1),
|
||||
choseBestTask(sptr(CaptureObjectsBehavior()), 1),
|
||||
choseBestTask(sptr(ClusterBehavior()), MAX_DEPTH),
|
||||
choseBestTask(sptr(DefenceBehavior()), MAX_DEPTH),
|
||||
choseBestTask(sptr(GatherArmyBehavior()), MAX_DEPTH),
|
||||
choseBestTask(sptr(StayAtTownBehavior()), MAX_DEPTH)
|
||||
};
|
||||
decompose(bestTasks, sptr(RecruitHeroBehavior()), 1);
|
||||
decompose(bestTasks, sptr(CaptureObjectsBehavior()), 1);
|
||||
decompose(bestTasks, sptr(ClusterBehavior()), MAX_DEPTH);
|
||||
decompose(bestTasks, sptr(DefenceBehavior()), MAX_DEPTH);
|
||||
decompose(bestTasks, sptr(GatherArmyBehavior()), MAX_DEPTH);
|
||||
decompose(bestTasks, sptr(StayAtTownBehavior()), MAX_DEPTH);
|
||||
|
||||
if(cb->getDate(Date::DAY) == 1)
|
||||
{
|
||||
bestTasks.push_back(choseBestTask(sptr(StartupBehavior()), 1));
|
||||
decompose(bestTasks, sptr(StartupBehavior()), 1);
|
||||
}
|
||||
|
||||
bestTask = choseBestTask(bestTasks);
|
||||
|
||||
std::string taskDescription = bestTask->toString();
|
||||
HeroPtr hero = bestTask->getHero();
|
||||
HeroRole heroRole = HeroRole::MAIN;
|
||||
|
||||
if(hero.validAndSet())
|
||||
heroRole = heroManager->getHeroRole(hero);
|
||||
|
||||
if(heroRole != HeroRole::MAIN || bestTask->getHeroExchangeCount() <= 1)
|
||||
useHeroChain = false;
|
||||
|
||||
// TODO: better to check turn distance here instead of priority
|
||||
if((heroRole != HeroRole::MAIN || bestTask->priority < SMALL_SCAN_MIN_PRIORITY)
|
||||
&& scanDepth == ScanDepth::MAIN_FULL)
|
||||
{
|
||||
useHeroChain = false;
|
||||
scanDepth = ScanDepth::SMALL;
|
||||
|
||||
logAi->trace(
|
||||
"Goal %s has low priority %f so decreasing scan depth to gain performance.",
|
||||
taskDescription,
|
||||
bestTask->priority);
|
||||
}
|
||||
|
||||
if(bestTask->priority < MIN_PRIORITY)
|
||||
{
|
||||
auto heroes = cb->getHeroesInfo();
|
||||
auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool
|
||||
{
|
||||
return h->movementPointsRemaining() > 100;
|
||||
});
|
||||
|
||||
if(hasMp && scanDepth != ScanDepth::ALL_FULL)
|
||||
{
|
||||
logAi->trace(
|
||||
"Goal %s has too low priority %f so increasing scan depth to full.",
|
||||
taskDescription,
|
||||
bestTask->priority);
|
||||
|
||||
scanDepth = ScanDepth::ALL_FULL;
|
||||
useHeroChain = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
logAi->trace("Goal %s has too low priority. It is not worth doing it. Ending turn.", taskDescription);
|
||||
|
||||
return;
|
||||
}
|
||||
auto selectedTasks = buildPlan(bestTasks);
|
||||
|
||||
logAi->debug("Decission madel in %ld", timeElapsed(start));
|
||||
|
||||
executeTask(bestTask);
|
||||
if(selectedTasks.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool hasAnySuccess = false;
|
||||
|
||||
for(auto bestTask : selectedTasks)
|
||||
{
|
||||
if(cb->getPlayerStatus(playerID) != EPlayerStatus::INGAME)
|
||||
return;
|
||||
|
||||
std::string taskDescription = bestTask->toString();
|
||||
HeroPtr hero = bestTask->getHero();
|
||||
HeroRole heroRole = HeroRole::MAIN;
|
||||
|
||||
if(hero.validAndSet())
|
||||
heroRole = heroManager->getHeroRole(hero);
|
||||
|
||||
if(heroRole != HeroRole::MAIN || bestTask->getHeroExchangeCount() <= 1)
|
||||
useHeroChain = false;
|
||||
|
||||
// TODO: better to check turn distance here instead of priority
|
||||
if((heroRole != HeroRole::MAIN || bestTask->priority < SMALL_SCAN_MIN_PRIORITY)
|
||||
&& scanDepth == ScanDepth::MAIN_FULL)
|
||||
{
|
||||
useHeroChain = false;
|
||||
scanDepth = ScanDepth::SMALL;
|
||||
|
||||
logAi->trace(
|
||||
"Goal %s has low priority %f so decreasing scan depth to gain performance.",
|
||||
taskDescription,
|
||||
bestTask->priority);
|
||||
}
|
||||
|
||||
if(bestTask->priority < MIN_PRIORITY)
|
||||
{
|
||||
auto heroes = cb->getHeroesInfo();
|
||||
auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool
|
||||
{
|
||||
return h->movementPointsRemaining() > 100;
|
||||
});
|
||||
|
||||
if(hasMp && scanDepth != ScanDepth::ALL_FULL)
|
||||
{
|
||||
logAi->trace(
|
||||
"Goal %s has too low priority %f so increasing scan depth to full.",
|
||||
taskDescription,
|
||||
bestTask->priority);
|
||||
|
||||
scanDepth = ScanDepth::ALL_FULL;
|
||||
useHeroChain = false;
|
||||
hasAnySuccess = true;
|
||||
break;;
|
||||
}
|
||||
|
||||
logAi->trace("Goal %s has too low priority. It is not worth doing it.", taskDescription);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!executeTask(bestTask))
|
||||
{
|
||||
if(hasAnySuccess)
|
||||
break;
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
hasAnySuccess = true;
|
||||
}
|
||||
|
||||
if(!hasAnySuccess)
|
||||
{
|
||||
logAi->trace("Nothing was done this turn. Ending turn.");
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
std::string taskDescr = task->toString();
|
||||
@ -376,10 +470,10 @@ void Nullkiller::executeTask(Goals::TTask task)
|
||||
logAi->error("Failed to realize subgoal of type %s, I will stop.", taskDescr);
|
||||
logAi->error("The error message was: %s", e.what());
|
||||
|
||||
#if NKAI_TRACE_LEVEL == 0
|
||||
throw; // will be recatched and AI turn ended
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TResources Nullkiller::getFreeResources() const
|
||||
|
@ -47,6 +47,24 @@ enum class ScanDepth
|
||||
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
|
||||
{
|
||||
private:
|
||||
@ -102,9 +120,10 @@ public:
|
||||
private:
|
||||
void resetAiState();
|
||||
void updateAiState(int pass, bool fast = false);
|
||||
Goals::TTask choseBestTask(Goals::TSubgoal behavior, int decompositionMaxDepth) const;
|
||||
Goals::TTask choseBestTask(Goals::TTaskVec & tasks) const;
|
||||
void executeTask(Goals::TTask task);
|
||||
void decompose(Goals::TGoalVec & result, Goals::TSubgoal behavior, int decompositionMaxDepth) const;
|
||||
Goals::TTask choseBestTask(Goals::TGoalVec & tasks) const;
|
||||
Goals::TTaskVec buildPlan(Goals::TGoalVec & tasks) const;
|
||||
bool executeTask(Goals::TTask task);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -158,6 +158,8 @@ uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHero
|
||||
|
||||
const auto& slots = hero->Slots();
|
||||
ui64 weakestStackPower = 0;
|
||||
int duplicatingSlots = getDuplicatingSlots(hero);
|
||||
|
||||
if (slots.size() >= GameConstants::ARMY_SIZE)
|
||||
{
|
||||
//No free slot, we might discard our weakest stack
|
||||
@ -172,7 +174,7 @@ uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHero
|
||||
{
|
||||
//Only if hero has slot for this creature in the army
|
||||
auto ccre = dynamic_cast<const CCreature*>(c.data.type);
|
||||
if (hero->getSlotFor(ccre).validSlot())
|
||||
if (hero->getSlotFor(ccre).validSlot() || duplicatingSlots > 0)
|
||||
{
|
||||
result += (c.data.type->getAIValue() * c.data.count) * c.chance;
|
||||
}
|
||||
@ -655,7 +657,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
|
||||
case Obj::RESOURCE:
|
||||
{
|
||||
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:
|
||||
return 1500;
|
||||
@ -711,8 +713,8 @@ public:
|
||||
|
||||
uint64_t armyStrength = heroExchange.getReinforcementArmyStrength(evaluationContext.evaluator.ai);
|
||||
|
||||
evaluationContext.addNonCriticalStrategicalValue(2.0f * armyStrength / (float)heroExchange.hero.get()->getArmyStrength());
|
||||
evaluationContext.heroRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(heroExchange.hero.get());
|
||||
evaluationContext.addNonCriticalStrategicalValue(2.0f * armyStrength / (float)heroExchange.hero->getArmyStrength());
|
||||
evaluationContext.heroRole = evaluationContext.evaluator.ai->heroManager->getHeroRole(heroExchange.hero);
|
||||
}
|
||||
};
|
||||
|
||||
@ -743,7 +745,7 @@ public:
|
||||
|
||||
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.movementCost += stayAtTown.getMovementWasted();
|
||||
}
|
||||
@ -792,7 +794,7 @@ public:
|
||||
if(defendTown.getTurn() > 0 && defendTown.isCounterAttack())
|
||||
{
|
||||
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;
|
||||
}
|
||||
@ -847,13 +849,12 @@ public:
|
||||
evaluationContext.movementCostByRole[role] += pair.second;
|
||||
}
|
||||
|
||||
auto heroPtr = task->hero;
|
||||
auto hero = heroPtr.get(ai->cb.get());
|
||||
auto hero = task->hero;
|
||||
bool checkGold = evaluationContext.danger == 0;
|
||||
auto army = path.heroArmy;
|
||||
|
||||
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)
|
||||
evaluationContext.heroRole = heroRole;
|
||||
@ -887,21 +888,21 @@ public:
|
||||
Goals::UnlockCluster & clusterGoal = dynamic_cast<Goals::UnlockCluster &>(*task);
|
||||
std::shared_ptr<ObjectCluster> cluster = clusterGoal.getCluster();
|
||||
|
||||
auto hero = clusterGoal.hero.get();
|
||||
auto role = evaluationContext.evaluator.ai->heroManager->getHeroRole(clusterGoal.hero);
|
||||
auto hero = 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;
|
||||
});
|
||||
|
||||
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;
|
||||
auto army = hero;
|
||||
|
||||
@ -960,7 +961,7 @@ public:
|
||||
return;
|
||||
|
||||
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 mpLeft = dismissedHero->movementPointsRemaining();
|
||||
@ -1126,7 +1127,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
|
||||
}
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %d, danger: %d, role: %s, strategical value: %f, cwr: %f, fear: %f, result %f",
|
||||
logAi->trace("Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, gold: %f, cost: %d, army gain: %f, danger: %d, role: %s, strategical value: %f, cwr: %f, fear: %f, result %f",
|
||||
task->toString(),
|
||||
evaluationContext.armyLossPersentage,
|
||||
(int)evaluationContext.turn,
|
||||
|
@ -31,12 +31,12 @@ TTask Goals::taskptr(const AbstractGoal & tmp)
|
||||
if(!tmp.isElementar())
|
||||
throw cannotFulfillGoalException(tmp.toString() + " is not elementar");
|
||||
|
||||
ptr.reset(dynamic_cast<ITask *>(tmp.clone()));
|
||||
ptr.reset(tmp.clone()->asTask());
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
std::string AbstractGoal::toString() const //TODO: virtualize
|
||||
std::string AbstractGoal::toString() const
|
||||
{
|
||||
std::string desc;
|
||||
switch(goalType)
|
||||
@ -63,8 +63,10 @@ std::string AbstractGoal::toString() const //TODO: virtualize
|
||||
default:
|
||||
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() + ")";
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
|
@ -10,9 +10,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../../lib/VCMI_Lib.h"
|
||||
#include "../../../lib/CBuildingHandler.h"
|
||||
#include "../../../lib/CCreatureHandler.h"
|
||||
#include "../../../lib/CTownHandler.h"
|
||||
#include "../../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../../../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../AIUtility.h"
|
||||
|
||||
namespace NKAI
|
||||
@ -106,7 +105,7 @@ namespace Goals
|
||||
int objid; SETTER(int, objid)
|
||||
int aid; SETTER(int, aid)
|
||||
int3 tile; SETTER(int3, tile)
|
||||
HeroPtr hero; SETTER(HeroPtr, hero)
|
||||
const CGHeroInstance * hero; SETTER(CGHeroInstance *, hero)
|
||||
const CGTownInstance *town; SETTER(CGTownInstance *, town)
|
||||
int bid; SETTER(int, bid)
|
||||
|
||||
@ -119,6 +118,7 @@ namespace Goals
|
||||
objid = -1;
|
||||
tile = int3(-1, -1, -1);
|
||||
town = nullptr;
|
||||
hero = nullptr;
|
||||
bid = -1;
|
||||
goldCost = 0;
|
||||
}
|
||||
@ -147,6 +147,11 @@ namespace Goals
|
||||
virtual bool hasHash() const { return false; }
|
||||
|
||||
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
|
||||
{
|
||||
@ -165,9 +170,11 @@ namespace Goals
|
||||
//TODO: make accept work for std::shared_ptr... somehow
|
||||
virtual void accept(AIGateway * ai) = 0; //unhandled goal will report standard error
|
||||
virtual std::string toString() const = 0;
|
||||
virtual HeroPtr getHero() const = 0;
|
||||
virtual const CGHeroInstance * getHero() const = 0;
|
||||
virtual ~ITask() {}
|
||||
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
|
||||
{
|
||||
return hero.h == other.hero.h;
|
||||
return hero == other.hero;
|
||||
}
|
||||
|
||||
void AdventureSpellCast::accept(AIGateway * ai)
|
||||
{
|
||||
if(!hero.validAndSet())
|
||||
if(!hero)
|
||||
throw cannotFulfillGoalException("Invalid hero!");
|
||||
|
||||
auto spell = getSpell();
|
||||
@ -56,7 +56,7 @@ void AdventureSpellCast::accept(AIGateway * ai)
|
||||
auto wait = cb->waitTillRealize;
|
||||
|
||||
cb->waitTillRealize = true;
|
||||
cb->castSpell(hero.h, spellID, tile);
|
||||
cb->castSpell(hero, spellID, tile);
|
||||
|
||||
if(town && spellID == SpellID::TOWN_PORTAL)
|
||||
{
|
||||
|
@ -22,7 +22,7 @@ namespace Goals
|
||||
SpellID spellID;
|
||||
|
||||
public:
|
||||
AdventureSpellCast(HeroPtr hero, SpellID spellID)
|
||||
AdventureSpellCast(const CGHeroInstance * hero, SpellID spellID)
|
||||
: ElementarGoal(Goals::ADVENTURE_SPELL_CAST), spellID(spellID)
|
||||
{
|
||||
sethero(hero);
|
||||
|
@ -14,7 +14,6 @@
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
struct HeroPtr;
|
||||
class AIGateway;
|
||||
|
||||
namespace Goals
|
||||
@ -92,9 +91,37 @@ namespace Goals
|
||||
|
||||
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; }
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
bool isElementar() 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
|
||||
{
|
||||
return other.hero.h == hero.h && other.tile == tile;
|
||||
return other.hero == hero && other.tile == tile;
|
||||
}
|
||||
//
|
||||
//TSubgoal DigAtTile::decomposeSingle() const
|
||||
|
@ -18,22 +18,22 @@ using namespace Goals;
|
||||
|
||||
bool DismissHero::operator==(const DismissHero & other) const
|
||||
{
|
||||
return hero.h == other.hero.h;
|
||||
return hero == other.hero;
|
||||
}
|
||||
|
||||
void DismissHero::accept(AIGateway * ai)
|
||||
{
|
||||
if(!hero.validAndSet())
|
||||
if(!hero)
|
||||
throw cannotFulfillGoalException("Invalid hero!");
|
||||
|
||||
cb->dismissHero(hero.h);
|
||||
cb->dismissHero(hero);
|
||||
|
||||
throw goalFulfilledException(sptr(*this));
|
||||
}
|
||||
|
||||
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>
|
||||
{
|
||||
private:
|
||||
std::string heroName;
|
||||
|
||||
public:
|
||||
DismissHero(HeroPtr hero)
|
||||
DismissHero(const CGHeroInstance * hero)
|
||||
: ElementarGoal(Goals::DISMISS_HERO)
|
||||
{
|
||||
sethero(hero);
|
||||
heroName = hero->getNameTranslated();
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
return "Exchange and swap heroes of " + town->getNameTranslated();
|
||||
|
@ -35,6 +35,9 @@ namespace Goals
|
||||
|
||||
const CGHeroInstance * getGarrisonHero() const { return garrisonHero; }
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
logAi->debug("Executing hero chain towards %s. Path %s", targetName, chainPath.toString());
|
||||
@ -72,6 +104,13 @@ void ExecuteHeroChain::accept(AIGateway * ai)
|
||||
const CGHeroInstance * hero = node->targetHero;
|
||||
HeroPtr heroPtr = hero;
|
||||
|
||||
if(!heroPtr.validAndSet())
|
||||
{
|
||||
logAi->error("Hero %s was lost. Exit hero chain.", heroPtr.name);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(node->parentIndex >= i)
|
||||
{
|
||||
logAi->error("Invalid parentIndex while executing node " + node->coord.toString());
|
||||
|
@ -34,6 +34,9 @@ namespace Goals
|
||||
|
||||
int getHeroExchangeCount() const override { return chainPath.exchangeCount; }
|
||||
|
||||
std::vector<ObjectInstanceID> getAffectedObjects() const override;
|
||||
bool isObjectAffected(ObjectInstanceID id) const override;
|
||||
|
||||
private:
|
||||
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());
|
||||
}
|
||||
|
||||
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),
|
||||
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)
|
||||
|
@ -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)
|
||||
{
|
||||
settown(town);
|
||||
sethero(defencePath.targetHero);
|
||||
hero = defencePath.targetHero;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
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 armyValue = ai->armyManager->howManyReinforcementsCanGet(hero.get(), exchangePath.heroArmy);
|
||||
uint64_t armyValue = ai->armyManager->howManyReinforcementsCanGet(hero, exchangePath.heroArmy);
|
||||
|
||||
return armyValue;
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ namespace Goals
|
||||
: CGoal(Goals::UNLOCK_CLUSTER), cluster(cluster), pathToCenter(pathToCenter)
|
||||
{
|
||||
tile = cluster->blocker->visitablePos();
|
||||
sethero(pathToCenter.targetHero);
|
||||
hero = pathToCenter.targetHero;
|
||||
}
|
||||
|
||||
bool operator==(const UnlockCluster & other) const override;
|
||||
|
@ -105,7 +105,11 @@ void AIPathfinder::updatePaths(const std::map<const CGHeroInstance *, HeroRole>
|
||||
cb->calculatePaths(config);
|
||||
|
||||
if(!pathfinderSettings.useHeroChain)
|
||||
{
|
||||
logAi->trace("Recalculated paths in %ld", timeElapsed(start));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
|
@ -35,7 +35,10 @@ namespace AIPathfinding
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -375,6 +375,8 @@ int main(int argc, char * argv[])
|
||||
while(!headlessQuit)
|
||||
boost::this_thread::sleep_for(boost::chrono::milliseconds(200));
|
||||
|
||||
boost::this_thread::sleep_for(boost::chrono::milliseconds(500));
|
||||
|
||||
quitApplication();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user