mirror of
https://github.com/vcmi/vcmi.git
synced 2025-04-13 11:40:38 +02:00
Nullkiller: parallel object clusterization, stabilization
This commit is contained in:
parent
66843b22d3
commit
9a203b8af9
@ -115,6 +115,32 @@ const CGHeroInstance * HeroPtr::get(bool doWeExpectNull) const
|
||||
return h;
|
||||
}
|
||||
|
||||
const CGHeroInstance * HeroPtr::get(CCallback * cb, 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
|
||||
{
|
||||
assert(obj);
|
||||
//assert(owned);
|
||||
}
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
const CGHeroInstance * HeroPtr::operator->() const
|
||||
{
|
||||
return get();
|
||||
@ -251,6 +277,11 @@ bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isObjectPassable(const Nullkiller * ai, const CGObjectInstance * obj)
|
||||
{
|
||||
return isObjectPassable(obj, ai->playerID, ai->cb->getPlayerRelations(obj->tempOwner, ai->playerID));
|
||||
}
|
||||
|
||||
bool isObjectPassable(const CGObjectInstance * obj)
|
||||
{
|
||||
return isObjectPassable(obj, ai->playerID, cb->getPlayerRelations(obj->tempOwner, ai->playerID));
|
||||
@ -355,7 +386,7 @@ uint64_t timeElapsed(boost::chrono::time_point<boost::chrono::steady_clock> star
|
||||
}
|
||||
|
||||
// todo: move to obj manager
|
||||
bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj)
|
||||
bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObjectInstance * obj)
|
||||
{
|
||||
switch(obj->ID)
|
||||
{
|
||||
@ -364,7 +395,7 @@ bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj)
|
||||
return obj->tempOwner != h->tempOwner; //do not visit our towns at random
|
||||
case Obj::BORDER_GATE:
|
||||
{
|
||||
for(auto q : ai->myCb->getMyQuests())
|
||||
for(auto q : ai->cb->getMyQuests())
|
||||
{
|
||||
if(q.obj == obj)
|
||||
{
|
||||
@ -378,7 +409,7 @@ bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj)
|
||||
case Obj::SEER_HUT:
|
||||
case Obj::QUEST_GUARD:
|
||||
{
|
||||
for(auto q : ai->myCb->getMyQuests())
|
||||
for(auto q : ai->cb->getMyQuests())
|
||||
{
|
||||
if(q.obj == obj)
|
||||
{
|
||||
@ -403,7 +434,7 @@ bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj)
|
||||
{
|
||||
if(level.first
|
||||
&& h->getSlotFor(CreatureID(c)) != SlotID()
|
||||
&& cb->getResourceAmount().canAfford(c.toCreature()->cost))
|
||||
&& ai->cb->getResourceAmount().canAfford(c.toCreature()->cost))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -429,7 +460,7 @@ bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj)
|
||||
case Obj::SCHOOL_OF_MAGIC:
|
||||
case Obj::SCHOOL_OF_WAR:
|
||||
{
|
||||
if(cb->getResourceAmount(Res::GOLD) < 1000)
|
||||
if(ai->cb->getResourceAmount(Res::GOLD) < 1000)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
@ -439,10 +470,10 @@ bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj)
|
||||
break;
|
||||
case Obj::TREE_OF_KNOWLEDGE:
|
||||
{
|
||||
if(ai->nullkiller->heroManager->getHeroRole(h) == HeroRole::SCOUT)
|
||||
if(ai->heroManager->getHeroRole(h) == HeroRole::SCOUT)
|
||||
return false;
|
||||
|
||||
TResources myRes = cb->getResourceAmount();
|
||||
TResources myRes = ai->cb->getResourceAmount();
|
||||
if(myRes[Res::GOLD] < 2000 || myRes[Res::GEMS] < 10)
|
||||
return false;
|
||||
break;
|
||||
@ -450,14 +481,14 @@ bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj)
|
||||
case Obj::MAGIC_WELL:
|
||||
return h->mana < h->manaLimit();
|
||||
case Obj::PRISON:
|
||||
return ai->myCb->getHeroesInfo().size() < VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER;
|
||||
return ai->cb->getHeroesInfo().size() < VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER;
|
||||
case Obj::TAVERN:
|
||||
{
|
||||
//TODO: make AI actually recruit heroes
|
||||
//TODO: only on request
|
||||
if(ai->myCb->getHeroesInfo().size() >= VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER)
|
||||
if(ai->cb->getHeroesInfo().size() >= VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER)
|
||||
return false;
|
||||
else if(cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST)
|
||||
else if(ai->cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
@ -18,8 +18,12 @@
|
||||
#include "../../lib/mapObjects/CObjectHandler.h"
|
||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../../lib/CPathfinder.h"
|
||||
#include <tbb/tbb.h>
|
||||
|
||||
using namespace tbb;
|
||||
|
||||
class CCallback;
|
||||
class Nullkiller;
|
||||
struct creInfo;
|
||||
|
||||
typedef const int3 & crint3;
|
||||
@ -72,6 +76,7 @@ public:
|
||||
}
|
||||
|
||||
const CGHeroInstance * get(bool doWeExpectNull = false) const;
|
||||
const CGHeroInstance * get(CCallback * cb, bool doWeExpectNull = false) const;
|
||||
bool validAndSet() const;
|
||||
|
||||
|
||||
@ -168,6 +173,7 @@ void foreach_neighbour(CCallback * cbp, const int3 & pos, std::function<void(CCa
|
||||
|
||||
bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater);
|
||||
bool isObjectPassable(const CGObjectInstance * obj);
|
||||
bool isObjectPassable(const Nullkiller * ai, const CGObjectInstance * obj);
|
||||
bool isObjectPassable(const CGObjectInstance * obj, PlayerColor playerColor, PlayerRelations::PlayerRelations objectRelations);
|
||||
bool isBlockVisitObj(const int3 & pos);
|
||||
|
||||
@ -184,7 +190,27 @@ bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2
|
||||
uint64_t timeElapsed(boost::chrono::time_point<boost::chrono::steady_clock> start);
|
||||
|
||||
// todo: move to obj manager
|
||||
bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj);
|
||||
bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObjectInstance * obj);
|
||||
|
||||
template<typename TFunc>
|
||||
void pforeachTilePos(crint3 mapSize, TFunc fn)
|
||||
{
|
||||
parallel_for(blocked_range<size_t>(0, mapSize.x), [&](const blocked_range<size_t>& r)
|
||||
{
|
||||
int3 pos;
|
||||
|
||||
for(pos.x = r.begin(); pos.x != r.end(); ++pos.x)
|
||||
{
|
||||
for(pos.y = 0; pos.y < mapSize.y; ++pos.y)
|
||||
{
|
||||
for(pos.z = 0; pos.z < mapSize.z; ++pos.z)
|
||||
{
|
||||
fn(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class CDistanceSorter
|
||||
{
|
||||
@ -197,3 +223,75 @@ public:
|
||||
}
|
||||
bool operator()(const CGObjectInstance * lhs, const CGObjectInstance * rhs) const;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class SharedPool
|
||||
{
|
||||
public:
|
||||
struct External_Deleter
|
||||
{
|
||||
explicit External_Deleter(std::weak_ptr<SharedPool<T>* > pool)
|
||||
: pool(pool)
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(T * ptr)
|
||||
{
|
||||
std::unique_ptr<T> uptr(ptr);
|
||||
|
||||
if(auto pool_ptr = pool.lock())
|
||||
{
|
||||
(*pool_ptr.get())->add(std::move(uptr));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::weak_ptr<SharedPool<T>* > pool;
|
||||
};
|
||||
|
||||
public:
|
||||
using ptr_type = std::unique_ptr<T, External_Deleter>;
|
||||
|
||||
SharedPool(std::function<std::unique_ptr<T>()> elementFactory)
|
||||
: elementFactory(elementFactory), pool(), sync(), instance_tracker(new SharedPool<T>*(this))
|
||||
{}
|
||||
|
||||
void add(std::unique_ptr<T> t)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(sync);
|
||||
pool.push_back(std::move(t));
|
||||
}
|
||||
|
||||
ptr_type acquire()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(sync);
|
||||
bool poolIsEmpty = pool.empty();
|
||||
T * element = poolIsEmpty
|
||||
? elementFactory().release()
|
||||
: pool.back().release();
|
||||
|
||||
ptr_type tmp(
|
||||
element,
|
||||
External_Deleter(std::weak_ptr<SharedPool<T>*>(instance_tracker)));
|
||||
|
||||
if(!poolIsEmpty) pool.pop_back();
|
||||
|
||||
return std::move(tmp);
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return pool.empty();
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return pool.size();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<T>> pool;
|
||||
std::function<std::unique_ptr<T>()> elementFactory;
|
||||
std::shared_ptr<SharedPool<T> *> instance_tracker;
|
||||
std::mutex sync;
|
||||
};
|
||||
|
@ -12,8 +12,6 @@
|
||||
#include "../../../lib/mapping/CMap.h" //for victory conditions
|
||||
#include "../Engine/Nullkiller.h"
|
||||
|
||||
extern boost::thread_specific_ptr<CCallback> cb;
|
||||
|
||||
void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo)
|
||||
{
|
||||
auto townInfo = developmentInfo.town->town;
|
||||
@ -71,7 +69,7 @@ void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo)
|
||||
{BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL}
|
||||
};
|
||||
|
||||
if(developmentInfo.existingDwellings.size() >= 2 && cb->getDate(Date::DAY_OF_WEEK) > boost::date_time::Friday)
|
||||
if(developmentInfo.existingDwellings.size() >= 2 && ai->cb->getDate(Date::DAY_OF_WEEK) > boost::date_time::Friday)
|
||||
{
|
||||
otherBuildings.push_back({BuildingID::CITADEL, BuildingID::CASTLE});
|
||||
}
|
||||
@ -99,7 +97,7 @@ int32_t convertToGold(const TResources & res)
|
||||
|
||||
TResources BuildAnalyzer::getResourcesRequiredNow() const
|
||||
{
|
||||
auto resourcesAvailable = cb->getResourceAmount();
|
||||
auto resourcesAvailable = ai->cb->getResourceAmount();
|
||||
auto result = requiredResources - resourcesAvailable;
|
||||
|
||||
result.positive();
|
||||
@ -109,7 +107,7 @@ TResources BuildAnalyzer::getResourcesRequiredNow() const
|
||||
|
||||
TResources BuildAnalyzer::getTotalResourcesRequired() const
|
||||
{
|
||||
auto resourcesAvailable = cb->getResourceAmount();
|
||||
auto resourcesAvailable = ai->cb->getResourceAmount();
|
||||
auto result = totalDevelopmentCost - resourcesAvailable;
|
||||
|
||||
result.positive();
|
||||
@ -125,7 +123,7 @@ void BuildAnalyzer::update()
|
||||
|
||||
reset();
|
||||
|
||||
auto towns = cb->getTownsInfo();
|
||||
auto towns = ai->cb->getTownsInfo();
|
||||
|
||||
for(const CGTownInstance* town : towns)
|
||||
{
|
||||
@ -159,7 +157,7 @@ void BuildAnalyzer::update()
|
||||
|
||||
updateDailyIncome();
|
||||
|
||||
goldPreasure = (float)armyCost[Res::GOLD] / (1 + cb->getResourceAmount(Res::GOLD) + (float)dailyIncome[Res::GOLD] * 7.0f);
|
||||
goldPreasure = (float)armyCost[Res::GOLD] / (1 + ai->cb->getResourceAmount(Res::GOLD) + (float)dailyIncome[Res::GOLD] * 7.0f);
|
||||
|
||||
logAi->trace("Gold preasure: %f", goldPreasure);
|
||||
}
|
||||
@ -203,7 +201,7 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
||||
|
||||
if(!town->hasBuilt(building))
|
||||
{
|
||||
auto canBuild = cb->canBuildStructure(town, building);
|
||||
auto canBuild = ai->cb->canBuildStructure(town, building);
|
||||
|
||||
if(canBuild == EBuildingState::ALLOWED)
|
||||
{
|
||||
@ -262,8 +260,8 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
||||
|
||||
void BuildAnalyzer::updateDailyIncome()
|
||||
{
|
||||
auto objects = cb->getMyObjects();
|
||||
auto towns = cb->getTownsInfo();
|
||||
auto objects = ai->cb->getMyObjects();
|
||||
auto towns = ai->cb->getTownsInfo();
|
||||
|
||||
dailyIncome = TResources();
|
||||
|
||||
|
@ -51,7 +51,7 @@ void DangerHitMapAnalyzer::updateHitMap()
|
||||
|
||||
boost::this_thread::interruption_point();
|
||||
|
||||
foreach_tile_pos([&](const int3 & pos)
|
||||
pforeachTilePos(mapSize, [&](const int3 & pos)
|
||||
{
|
||||
for(AIPath & path : ai->pathfinder->getPathInfo(pos))
|
||||
{
|
||||
|
@ -16,14 +16,16 @@
|
||||
|
||||
void ObjectCluster::addObject(const CGObjectInstance * obj, const AIPath & path, float priority)
|
||||
{
|
||||
auto & info = objects[obj];
|
||||
ClusterObjects::accessor info;
|
||||
|
||||
if(info.priority < priority)
|
||||
objects.insert(info, ClusterObjects::value_type(obj, ClusterObjectInfo()));
|
||||
|
||||
if(info->second.priority < priority)
|
||||
{
|
||||
info.priority = priority;
|
||||
info.movementCost = path.movementCost() - path.firstNode().cost;
|
||||
info.danger = path.targetObjectDanger;
|
||||
info.turn = path.turn();
|
||||
info->second.priority = priority;
|
||||
info->second.movementCost = path.movementCost() - path.firstNode().cost;
|
||||
info->second.danger = path.targetObjectDanger;
|
||||
info->second.turn = path.turn();
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,7 +127,7 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons
|
||||
|| blocker->ID == Obj::BORDER_GATE
|
||||
|| blocker->ID == Obj::SHIPYARD)
|
||||
{
|
||||
if(!isObjectPassable(blocker))
|
||||
if(!isObjectPassable(ai, blocker))
|
||||
return blocker;
|
||||
}
|
||||
|
||||
@ -206,130 +208,134 @@ void ObjectClusterizer::clusterize()
|
||||
|
||||
logAi->debug("Begin object clusterization");
|
||||
|
||||
for(const CGObjectInstance * obj : ai->memory->visitableObjs)
|
||||
std::vector<const CGObjectInstance *> objs(
|
||||
ai->memory->visitableObjs.begin(),
|
||||
ai->memory->visitableObjs.end());
|
||||
|
||||
parallel_for(blocked_range<size_t>(0, objs.size()), [&](const blocked_range<size_t> & r)
|
||||
{
|
||||
if(!shouldVisitObject(obj))
|
||||
continue;
|
||||
auto priorityEvaluator = ai->priorityEvaluators->acquire();
|
||||
|
||||
for(int i = r.begin(); i != r.end(); i++)
|
||||
{
|
||||
auto obj = objs[i];
|
||||
|
||||
if(!shouldVisitObject(obj))
|
||||
return;
|
||||
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString());
|
||||
logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString());
|
||||
#endif
|
||||
|
||||
auto paths = ai->pathfinder->getPathInfo(obj->visitablePos());
|
||||
auto paths = ai->pathfinder->getPathInfo(obj->visitablePos());
|
||||
|
||||
if(paths.empty())
|
||||
{
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("No paths found.");
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
std::sort(paths.begin(), paths.end(), [](const AIPath & p1, const AIPath & p2) -> bool
|
||||
{
|
||||
return p1.movementCost() < p2.movementCost();
|
||||
});
|
||||
|
||||
if(vstd::contains(ignoreObjects, obj->ID))
|
||||
{
|
||||
farObjects.addObject(obj, paths.front(), 0);
|
||||
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Object ignored. Moved to far objects with path %s", paths.front().toString());
|
||||
#endif
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
std::set<const CGHeroInstance *> heroesProcessed;
|
||||
|
||||
for(auto & path : paths)
|
||||
{
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Checking path %s", path.toString());
|
||||
#endif
|
||||
|
||||
if(!shouldVisit(path.targetHero, obj))
|
||||
if(paths.empty())
|
||||
{
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Hero %s does not need to visit %s", path.targetHero->name, obj->getObjectName());
|
||||
logAi->trace("No paths found.");
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
if(path.nodes.size() > 1)
|
||||
std::sort(paths.begin(), paths.end(), [](const AIPath & p1, const AIPath & p2) -> bool
|
||||
{
|
||||
auto blocker = getBlocker(path);
|
||||
return p1.movementCost() < p2.movementCost();
|
||||
});
|
||||
|
||||
if(blocker)
|
||||
{
|
||||
if(vstd::contains(heroesProcessed, path.targetHero))
|
||||
{
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Hero %s is already processed.", path.targetHero->name);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
heroesProcessed.insert(path.targetHero);
|
||||
|
||||
auto cluster = blockedObjects[blocker];
|
||||
|
||||
if(!cluster)
|
||||
{
|
||||
cluster.reset(new ObjectCluster(blocker));
|
||||
blockedObjects[blocker] = cluster;
|
||||
}
|
||||
|
||||
float priority = ai->priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)));
|
||||
|
||||
if(priority < MIN_PRIORITY)
|
||||
continue;
|
||||
|
||||
cluster->addObject(obj, path, priority);
|
||||
if(vstd::contains(ignoreObjects, obj->ID))
|
||||
{
|
||||
farObjects.addObject(obj, paths.front(), 0);
|
||||
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Path added to cluster %s%s", blocker->getObjectName(), blocker->visitablePos().toString());
|
||||
logAi->trace("Object ignored. Moved to far objects with path %s", paths.front().toString());
|
||||
#endif
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
std::set<const CGHeroInstance *> heroesProcessed;
|
||||
|
||||
for(auto & path : paths)
|
||||
{
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Checking path %s", path.toString());
|
||||
#endif
|
||||
|
||||
if(!shouldVisit(ai, path.targetHero, obj))
|
||||
{
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Hero %s does not need to visit %s", path.targetHero->name, obj->getObjectName());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
heroesProcessed.insert(path.targetHero);
|
||||
|
||||
float priority = ai->priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)));
|
||||
if(path.nodes.size() > 1)
|
||||
{
|
||||
auto blocker = getBlocker(path);
|
||||
|
||||
if(priority < MIN_PRIORITY)
|
||||
continue;
|
||||
|
||||
bool interestingObject = path.turn() <= 2 || priority > 0.5f;
|
||||
if(blocker)
|
||||
{
|
||||
if(vstd::contains(heroesProcessed, path.targetHero))
|
||||
{
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Hero %s is already processed.", path.targetHero->name);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
if(interestingObject)
|
||||
{
|
||||
nearObjects.addObject(obj, path, priority);
|
||||
}
|
||||
else
|
||||
{
|
||||
farObjects.addObject(obj, path, priority);
|
||||
}
|
||||
heroesProcessed.insert(path.targetHero);
|
||||
|
||||
float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)));
|
||||
|
||||
if(priority < MIN_PRIORITY)
|
||||
continue;
|
||||
|
||||
ClusterMap::accessor cluster;
|
||||
blockedObjects.insert(
|
||||
cluster,
|
||||
ClusterMap::value_type(blocker, std::make_shared<ObjectCluster>(blocker)));
|
||||
|
||||
cluster->second->addObject(obj, path, priority);
|
||||
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Path %s added to %s objects. Turn: %d, priority: %f",
|
||||
path.toString(),
|
||||
interestingObject ? "near" : "far",
|
||||
path.turn(),
|
||||
priority);
|
||||
logAi->trace("Path added to cluster %s%s", blocker->getObjectName(), blocker->visitablePos().toString());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
vstd::erase_if(blockedObjects, [](std::pair<const CGObjectInstance *, std::shared_ptr<ObjectCluster>> pair) -> bool
|
||||
{
|
||||
return pair.second->objects.empty();
|
||||
heroesProcessed.insert(path.targetHero);
|
||||
|
||||
float priority = priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)));
|
||||
|
||||
if(priority < MIN_PRIORITY)
|
||||
continue;
|
||||
|
||||
bool interestingObject = path.turn() <= 2 || priority > 0.5f;
|
||||
|
||||
if(interestingObject)
|
||||
{
|
||||
nearObjects.addObject(obj, path, priority);
|
||||
}
|
||||
else
|
||||
{
|
||||
farObjects.addObject(obj, path, priority);
|
||||
}
|
||||
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Path %s added to %s objects. Turn: %d, priority: %f",
|
||||
path.toString(),
|
||||
interestingObject ? "near" : "far",
|
||||
path.turn(),
|
||||
priority);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
logAi->trace("Near objects count: %i", nearObjects.objects.size());
|
||||
logAi->trace("Far objects count: %i", farObjects.objects.size());
|
||||
|
||||
for(auto pair : blockedObjects)
|
||||
{
|
||||
logAi->trace("Cluster %s %s count: %i", pair.first->getObjectName(), pair.first->visitablePos().toString(), pair.second->objects.size());
|
||||
|
@ -19,10 +19,12 @@ struct ClusterObjectInfo
|
||||
uint8_t turn;
|
||||
};
|
||||
|
||||
typedef tbb::concurrent_hash_map<const CGObjectInstance *, ClusterObjectInfo> ClusterObjects;
|
||||
|
||||
struct ObjectCluster
|
||||
{
|
||||
public:
|
||||
std::map<const CGObjectInstance *, ClusterObjectInfo> objects;
|
||||
ClusterObjects objects;
|
||||
const CGObjectInstance * blocker;
|
||||
|
||||
void reset()
|
||||
@ -45,12 +47,14 @@ public:
|
||||
const CGObjectInstance * calculateCenter() const;
|
||||
};
|
||||
|
||||
typedef tbb::concurrent_hash_map<const CGObjectInstance *, std::shared_ptr<ObjectCluster>> ClusterMap;
|
||||
|
||||
class ObjectClusterizer
|
||||
{
|
||||
private:
|
||||
ObjectCluster nearObjects;
|
||||
ObjectCluster farObjects;
|
||||
std::map<const CGObjectInstance *, std::shared_ptr<ObjectCluster>> blockedObjects;
|
||||
ClusterMap blockedObjects;
|
||||
const Nullkiller * ai;
|
||||
|
||||
public:
|
||||
|
@ -71,7 +71,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
|
||||
continue;
|
||||
}
|
||||
|
||||
if(objToVisit && !shouldVisit(path.targetHero, objToVisit))
|
||||
if(objToVisit && !shouldVisit(ai->nullkiller.get(), path.targetHero, objToVisit))
|
||||
continue;
|
||||
|
||||
auto hero = path.targetHero;
|
||||
|
@ -43,6 +43,13 @@ void Nullkiller::init(std::shared_ptr<CCallback> cb, PlayerColor playerID)
|
||||
this->playerID = playerID;
|
||||
|
||||
priorityEvaluator.reset(new PriorityEvaluator(this));
|
||||
priorityEvaluators.reset(
|
||||
new SharedPool<PriorityEvaluator>(
|
||||
[&]()->std::unique_ptr<PriorityEvaluator>
|
||||
{
|
||||
return std::make_unique<PriorityEvaluator>(this);
|
||||
}));
|
||||
|
||||
dangerHitMap.reset(new DangerHitMapAnalyzer(this));
|
||||
buildAnalyzer.reset(new BuildAnalyzer(this));
|
||||
objectClusterizer.reset(new ObjectClusterizer(this));
|
||||
@ -224,16 +231,23 @@ void Nullkiller::makeTurn()
|
||||
HeroPtr hero = bestTask->getHero();
|
||||
|
||||
if(bestTask->priority < NEXT_SCAN_MIN_PRIORITY
|
||||
&& hero.validAndSet()
|
||||
&& heroManager->getHeroRole(hero) == HeroRole::MAIN
|
||||
&& scanDepth != ScanDepth::FULL)
|
||||
{
|
||||
logAi->trace(
|
||||
"Goal %s has too low priority %f so increasing scan depth",
|
||||
bestTask->toString(),
|
||||
bestTask->priority);
|
||||
scanDepth = (ScanDepth)((int)scanDepth + 1);
|
||||
continue;
|
||||
HeroRole heroRole = HeroRole::MAIN;
|
||||
|
||||
if(hero.validAndSet())
|
||||
heroRole = heroManager->getHeroRole(hero);
|
||||
|
||||
if(heroRole == HeroRole::MAIN)
|
||||
{
|
||||
logAi->trace(
|
||||
"Goal %s has too low priority %f so increasing scan depth",
|
||||
bestTask->toString(),
|
||||
bestTask->priority);
|
||||
scanDepth = (ScanDepth)((int)scanDepth + 1);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if(bestTask->priority < MIN_PRIORITY)
|
||||
|
@ -9,8 +9,6 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
#include "PriorityEvaluator.h"
|
||||
#include "FuzzyHelper.h"
|
||||
#include "AIMemory.h"
|
||||
@ -58,6 +56,7 @@ public:
|
||||
std::unique_ptr<BuildAnalyzer> buildAnalyzer;
|
||||
std::unique_ptr<ObjectClusterizer> objectClusterizer;
|
||||
std::unique_ptr<PriorityEvaluator> priorityEvaluator;
|
||||
std::unique_ptr<SharedPool<PriorityEvaluator>> priorityEvaluators;
|
||||
std::unique_ptr<AIPathfinder> pathfinder;
|
||||
std::unique_ptr<HeroManager> heroManager;
|
||||
std::unique_ptr<ArmyManager> armyManager;
|
||||
|
@ -29,8 +29,6 @@
|
||||
#define MIN_AI_STRENGHT (0.5f) //lower when combat AI gets smarter
|
||||
#define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
|
||||
|
||||
extern boost::thread_specific_ptr<CCallback> cb;
|
||||
|
||||
EvaluationContext::EvaluationContext(const Nullkiller * ai)
|
||||
: movementCost(0.0),
|
||||
manaCost(0),
|
||||
@ -78,7 +76,7 @@ void PriorityEvaluator::initVisitTile()
|
||||
value = engine->getOutputVariable("Value");
|
||||
}
|
||||
|
||||
int32_t estimateTownIncome(const CGObjectInstance * target, const CGHeroInstance * hero)
|
||||
int32_t estimateTownIncome(CCallback * cb, const CGObjectInstance * target, const CGHeroInstance * hero)
|
||||
{
|
||||
auto relations = cb->getPlayerRelations(hero->tempOwner, target->tempOwner);
|
||||
|
||||
@ -116,7 +114,7 @@ uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHero
|
||||
return result;
|
||||
}
|
||||
|
||||
uint64_t getDwellingScore(const CGObjectInstance * target, bool checkGold)
|
||||
uint64_t getDwellingScore(CCallback * cb, const CGObjectInstance * target, bool checkGold)
|
||||
{
|
||||
auto dwelling = dynamic_cast<const CGDwelling *>(target);
|
||||
uint64_t score = 0;
|
||||
@ -207,14 +205,14 @@ uint64_t RewardEvaluator::getArmyReward(
|
||||
case Obj::TOWN:
|
||||
return target->tempOwner == PlayerColor::NEUTRAL ? 1000 : 10000;
|
||||
case Obj::HILL_FORT:
|
||||
return ai->armyManager->calculateCreateresUpgrade(army, target, cb->getResourceAmount()).upgradeValue;
|
||||
return ai->armyManager->calculateCreateresUpgrade(army, target, ai->cb->getResourceAmount()).upgradeValue;
|
||||
case Obj::CREATURE_BANK:
|
||||
return getCreatureBankArmyReward(target, hero);
|
||||
case Obj::CREATURE_GENERATOR1:
|
||||
case Obj::CREATURE_GENERATOR2:
|
||||
case Obj::CREATURE_GENERATOR3:
|
||||
case Obj::CREATURE_GENERATOR4:
|
||||
return getDwellingScore(target, checkGold);
|
||||
return getDwellingScore(ai->cb.get(), target, checkGold);
|
||||
case Obj::CRYPT:
|
||||
case Obj::SHIPWRECK:
|
||||
case Obj::SHIPWRECK_SURVIVOR:
|
||||
@ -225,7 +223,7 @@ uint64_t RewardEvaluator::getArmyReward(
|
||||
case Obj::DRAGON_UTOPIA:
|
||||
return 10000;
|
||||
case Obj::HERO:
|
||||
return cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
|
||||
return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
|
||||
? enemyArmyEliminationRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->getArmyStrength()
|
||||
: 0;
|
||||
default:
|
||||
@ -241,7 +239,7 @@ int RewardEvaluator::getGoldCost(const CGObjectInstance * target, const CGHeroIn
|
||||
switch(target->ID)
|
||||
{
|
||||
case Obj::HILL_FORT:
|
||||
return ai->armyManager->calculateCreateresUpgrade(army, target, cb->getResourceAmount()).upgradeCost[Res::GOLD];
|
||||
return ai->armyManager->calculateCreateresUpgrade(army, target, ai->cb->getResourceAmount()).upgradeCost[Res::GOLD];
|
||||
case Obj::SCHOOL_OF_MAGIC:
|
||||
case Obj::SCHOOL_OF_WAR:
|
||||
return 1000;
|
||||
@ -325,7 +323,7 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
|
||||
: 0.5f;
|
||||
|
||||
case Obj::HERO:
|
||||
return cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
|
||||
return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
|
||||
? getEnemyHeroStrategicalValue(dynamic_cast<const CGHeroInstance *>(target))
|
||||
: 0;
|
||||
|
||||
@ -380,7 +378,7 @@ float RewardEvaluator::getSkillReward(const CGObjectInstance * target, const CGH
|
||||
case Obj::WITCH_HUT:
|
||||
return evaluateWitchHutSkillScore(dynamic_cast<const CGWitchHut *>(target), hero, role);
|
||||
case Obj::HERO:
|
||||
return cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
|
||||
return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
|
||||
? enemyHeroEliminationSkillRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->level
|
||||
: 0;
|
||||
default:
|
||||
@ -438,7 +436,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
|
||||
case Obj::WATER_WHEEL:
|
||||
return 1000;
|
||||
case Obj::TOWN:
|
||||
return dailyIncomeMultiplier * estimateTownIncome(target, hero);
|
||||
return dailyIncomeMultiplier * estimateTownIncome(ai->cb.get(), target, hero);
|
||||
case Obj::MINE:
|
||||
case Obj::ABANDONED_MINE:
|
||||
return dailyIncomeMultiplier * (isGold ? 1000 : 75);
|
||||
@ -459,7 +457,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
|
||||
case Obj::SEA_CHEST:
|
||||
return 1500;
|
||||
case Obj::HERO:
|
||||
return cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
|
||||
return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
|
||||
? heroEliminationBonus + enemyArmyEliminationGoldRewardRatio * getArmyCost(dynamic_cast<const CGHeroInstance *>(target))
|
||||
: 0;
|
||||
default:
|
||||
@ -550,7 +548,12 @@ public:
|
||||
|
||||
class ExecuteHeroChainEvaluationContextBuilder : public IEvaluationContextBuilder
|
||||
{
|
||||
private:
|
||||
const Nullkiller * ai;
|
||||
|
||||
public:
|
||||
ExecuteHeroChainEvaluationContextBuilder(const Nullkiller * ai) : ai(ai) {}
|
||||
|
||||
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||
{
|
||||
if(task->goalType != Goals::EXECUTE_HERO_CHAIN)
|
||||
@ -578,14 +581,14 @@ public:
|
||||
}
|
||||
|
||||
auto heroPtr = task->hero;
|
||||
auto day = cb->getDate(Date::DAY);
|
||||
auto hero = heroPtr.get();
|
||||
auto day = ai->cb->getDate(Date::DAY);
|
||||
auto hero = heroPtr.get(ai->cb.get());
|
||||
bool checkGold = evaluationContext.danger == 0;
|
||||
auto army = path.heroArmy;
|
||||
|
||||
const CGObjectInstance * target = cb->getObj((ObjectInstanceID)task->objid, false);
|
||||
const CGObjectInstance * target = ai->cb->getObj((ObjectInstanceID)task->objid, false);
|
||||
|
||||
if (target && cb->getPlayerRelations(target->tempOwner, hero->tempOwner) == PlayerRelations::ENEMIES)
|
||||
if (target && ai->cb->getPlayerRelations(target->tempOwner, hero->tempOwner) == PlayerRelations::ENEMIES)
|
||||
{
|
||||
evaluationContext.goldReward += evaluationContext.evaluator.getGoldReward(target, hero);
|
||||
evaluationContext.armyReward += evaluationContext.evaluator.getArmyReward(target, hero, army, checkGold);
|
||||
@ -603,7 +606,12 @@ public:
|
||||
|
||||
class ClusterEvaluationContextBuilder : public IEvaluationContextBuilder
|
||||
{
|
||||
private:
|
||||
const Nullkiller * ai;
|
||||
|
||||
public:
|
||||
ClusterEvaluationContextBuilder(const Nullkiller * ai) : ai(ai) {}
|
||||
|
||||
virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
|
||||
{
|
||||
if(task->goalType != Goals::UNLOCK_CLUSTER)
|
||||
@ -627,7 +635,7 @@ public:
|
||||
for(auto objInfo : objects)
|
||||
{
|
||||
auto target = objInfo.first;
|
||||
auto day = cb->getDate(Date::DAY);
|
||||
auto day = ai->cb->getDate(Date::DAY);
|
||||
bool checkGold = objInfo.second.danger == 0;
|
||||
auto army = hero;
|
||||
|
||||
@ -709,9 +717,9 @@ PriorityEvaluator::PriorityEvaluator(const Nullkiller * ai)
|
||||
:ai(ai)
|
||||
{
|
||||
initVisitTile();
|
||||
evaluationContextBuilders.push_back(std::make_shared<ExecuteHeroChainEvaluationContextBuilder>());
|
||||
evaluationContextBuilders.push_back(std::make_shared<ExecuteHeroChainEvaluationContextBuilder>(ai));
|
||||
evaluationContextBuilders.push_back(std::make_shared<BuildThisEvaluationContextBuilder>());
|
||||
evaluationContextBuilders.push_back(std::make_shared<ClusterEvaluationContextBuilder>());
|
||||
evaluationContextBuilders.push_back(std::make_shared<ClusterEvaluationContextBuilder>(ai));
|
||||
evaluationContextBuilders.push_back(std::make_shared<HeroExchangeEvaluator>());
|
||||
evaluationContextBuilders.push_back(std::make_shared<ArmyUpgradeEvaluator>());
|
||||
evaluationContextBuilders.push_back(std::make_shared<DefendTownEvaluator>());
|
||||
@ -769,7 +777,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
|
||||
closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio);
|
||||
strategicalValueVariable->setValue(evaluationContext.strategicalValue);
|
||||
goldPreasureVariable->setValue(ai->buildAnalyzer->getGoldPreasure());
|
||||
goldCostVariable->setValue(evaluationContext.goldCost / ((float)cb->getResourceAmount(Res::GOLD) + (float)ai->buildAnalyzer->getDailyIncome()[Res::GOLD] + 1.0f));
|
||||
goldCostVariable->setValue(evaluationContext.goldCost / ((float)ai->cb->getResourceAmount(Res::GOLD) + (float)ai->buildAnalyzer->getDailyIncome()[Res::GOLD] + 1.0f));
|
||||
turnVariable->setValue(evaluationContext.turn);
|
||||
fearVariable->setValue(evaluationContext.enemyHeroDangerRatio);
|
||||
|
||||
|
@ -8,7 +8,6 @@
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include <tbb/tbb.h>
|
||||
#include "AINodeStorage.h"
|
||||
#include "Actions/TownPortalAction.h"
|
||||
#include "../Goals/Goals.h"
|
||||
@ -20,8 +19,6 @@
|
||||
#include "../../../lib/PathfinderUtil.h"
|
||||
#include "../../../lib/CPlayerState.h"
|
||||
|
||||
using namespace tbb;
|
||||
|
||||
std::shared_ptr<boost::multi_array<AIPathNode, 5>> AISharedStorage::shared;
|
||||
std::set<int3> commitedTiles;
|
||||
std::set<int3> commitedTilesInitial;
|
||||
@ -504,7 +501,7 @@ bool AINodeStorage::calculateHeroChain()
|
||||
{
|
||||
std::mutex resultMutex;
|
||||
|
||||
std::random_shuffle(data.begin(), data.end());
|
||||
std::random_shuffle(data.begin(), data.end());
|
||||
|
||||
parallel_for(blocked_range<size_t>(0, data.size()), [&](const blocked_range<size_t>& r)
|
||||
{
|
||||
@ -526,7 +523,7 @@ bool AINodeStorage::calculateHeroChain()
|
||||
HeroChainCalculationTask task(*this, nodes, data, chainMask, heroChainTurn);
|
||||
|
||||
task.execute(r);
|
||||
task.flushResult(heroChain);\
|
||||
task.flushResult(heroChain);
|
||||
}
|
||||
|
||||
CCreature::DisableChildLinkage = false;
|
||||
|
@ -51,10 +51,11 @@ namespace AIPathfinding
|
||||
{
|
||||
private:
|
||||
const IShipyard * shipyard;
|
||||
const CPlayerSpecificInfoCallback * cb;
|
||||
|
||||
public:
|
||||
BuildBoatAction(const IShipyard * shipyard)
|
||||
: shipyard(shipyard)
|
||||
BuildBoatAction(const CPlayerSpecificInfoCallback * cb, const IShipyard * shipyard)
|
||||
: cb(cb), shipyard(shipyard)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -196,15 +196,26 @@ HeroExchangeMap::HeroExchangeMap(const HeroActor * actor, const Nullkiller * ai)
|
||||
|
||||
HeroExchangeMap::~HeroExchangeMap()
|
||||
{
|
||||
CCreature::DisableChildLinkage = true;
|
||||
|
||||
for(auto & exchange : exchangeMap)
|
||||
{
|
||||
if(!exchange.second) continue;
|
||||
|
||||
delete exchange.second->creatureSet;
|
||||
}
|
||||
|
||||
CCreature::DisableChildLinkage = false;
|
||||
|
||||
for(auto & exchange : exchangeMap)
|
||||
{
|
||||
if(!exchange.second) continue;
|
||||
|
||||
delete exchange.second;
|
||||
}
|
||||
|
||||
exchangeMap.clear();
|
||||
|
||||
}
|
||||
|
||||
ExchangeResult HeroExchangeMap::tryExchangeNoLock(const ChainActor * other)
|
||||
@ -270,7 +281,7 @@ ExchangeResult HeroExchangeMap::tryExchangeNoLock(const ChainActor * other)
|
||||
}
|
||||
|
||||
if(other->isMovable && other->armyValue <= actor->armyValue / 10 && other->armyValue < MIN_ARMY_STRENGTH_FOR_CHAIN)
|
||||
return nullptr;
|
||||
return result;
|
||||
|
||||
TResources availableResources = resources - actor->armyCost - other->armyCost;
|
||||
HeroExchangeArmy * upgradedInitialArmy = tryUpgrade(actor->creatureSet, other->getActorObject(), availableResources);
|
||||
|
@ -69,7 +69,7 @@ namespace AIPathfinding
|
||||
if(shipyard->shipyardStatus() == IShipyard::GOOD)
|
||||
{
|
||||
int3 boatLocation = shipyard->bestLocation();
|
||||
virtualBoats[boatLocation] = std::make_shared<BuildBoatAction>(shipyard);
|
||||
virtualBoats[boatLocation] = std::make_shared<BuildBoatAction>(cb, shipyard);
|
||||
logAi->debug("Virtual boat added at %s", boatLocation.toString());
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user