mirror of
https://github.com/vcmi/vcmi.git
synced 2024-11-24 08:32:34 +02:00
Nullkiller: clusterization fixes, heroes clusterization for additional mains in case of locked heroes
This commit is contained in:
parent
645c393e25
commit
1fdf0de75d
@ -18,6 +18,8 @@
|
||||
#include "../../lib/mapObjects/MapObjects.h"
|
||||
#include "../../lib/mapping/CMapDefines.h"
|
||||
|
||||
#include "../../lib/CModHandler.h"
|
||||
|
||||
extern boost::thread_specific_ptr<CCallback> cb;
|
||||
extern boost::thread_specific_ptr<VCAI> ai;
|
||||
|
||||
@ -272,14 +274,6 @@ bool isObjectPassable(const CGObjectInstance * obj, PlayerColor playerColor, Pla
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isBlockedBorderGate(int3 tileToHit) //TODO: is that function needed? should be handled by pathfinder
|
||||
{
|
||||
if(cb->getTile(tileToHit)->topVisitableId() != Obj::BORDER_GATE)
|
||||
return false;
|
||||
auto gate = dynamic_cast<const CGKeys *>(cb->getTile(tileToHit)->topVisitableObj());
|
||||
return !gate->passableFor(ai->playerID);
|
||||
}
|
||||
|
||||
bool isBlockVisitObj(const int3 & pos)
|
||||
{
|
||||
if(auto obj = cb->getTopObj(pos))
|
||||
@ -358,4 +352,124 @@ uint64_t timeElapsed(boost::chrono::time_point<boost::chrono::steady_clock> star
|
||||
auto end = boost::chrono::high_resolution_clock::now();
|
||||
|
||||
return boost::chrono::duration_cast<boost::chrono::milliseconds>(end - start).count();
|
||||
}
|
||||
|
||||
// todo: move to obj manager
|
||||
bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj)
|
||||
{
|
||||
switch(obj->ID)
|
||||
{
|
||||
case Obj::TOWN:
|
||||
case Obj::HERO: //never visit our heroes at random
|
||||
return obj->tempOwner != h->tempOwner; //do not visit our towns at random
|
||||
case Obj::BORDER_GATE:
|
||||
{
|
||||
for(auto q : ai->myCb->getMyQuests())
|
||||
{
|
||||
if(q.obj == obj)
|
||||
{
|
||||
return false; // do not visit guards or gates when wandering
|
||||
}
|
||||
}
|
||||
return true; //we don't have this quest yet
|
||||
}
|
||||
case Obj::BORDERGUARD: //open borderguard if possible
|
||||
return (dynamic_cast<const CGKeys *>(obj))->wasMyColorVisited(ai->playerID);
|
||||
case Obj::SEER_HUT:
|
||||
case Obj::QUEST_GUARD:
|
||||
{
|
||||
for(auto q : ai->myCb->getMyQuests())
|
||||
{
|
||||
if(q.obj == obj)
|
||||
{
|
||||
if(q.quest->checkQuest(h))
|
||||
return true; //we completed the quest
|
||||
else
|
||||
return false; //we can't complete this quest
|
||||
}
|
||||
}
|
||||
return true; //we don't have this quest yet
|
||||
}
|
||||
case Obj::CREATURE_GENERATOR1:
|
||||
{
|
||||
if(obj->tempOwner != h->tempOwner)
|
||||
return true; //flag just in case
|
||||
|
||||
const CGDwelling * d = dynamic_cast<const CGDwelling *>(obj);
|
||||
|
||||
for(auto level : d->creatures)
|
||||
{
|
||||
for(auto c : level.second)
|
||||
{
|
||||
if(level.first
|
||||
&& h->getSlotFor(CreatureID(c)) != SlotID()
|
||||
&& cb->getResourceAmount().canAfford(c.toCreature()->cost))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
case Obj::HILL_FORT:
|
||||
{
|
||||
for(auto slot : h->Slots())
|
||||
{
|
||||
if(slot.second->type->upgrades.size())
|
||||
return true; //TODO: check price?
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case Obj::MONOLITH_ONE_WAY_ENTRANCE:
|
||||
case Obj::MONOLITH_ONE_WAY_EXIT:
|
||||
case Obj::MONOLITH_TWO_WAY:
|
||||
case Obj::WHIRLPOOL:
|
||||
return false;
|
||||
case Obj::SCHOOL_OF_MAGIC:
|
||||
case Obj::SCHOOL_OF_WAR:
|
||||
{
|
||||
if(cb->getResourceAmount(Res::GOLD) < 1000)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case Obj::LIBRARY_OF_ENLIGHTENMENT:
|
||||
if(h->level < 12)
|
||||
return false;
|
||||
break;
|
||||
case Obj::TREE_OF_KNOWLEDGE:
|
||||
{
|
||||
if(ai->nullkiller->heroManager->getHeroRole(h) == HeroRole::SCOUT)
|
||||
return false;
|
||||
|
||||
TResources myRes = cb->getResourceAmount();
|
||||
if(myRes[Res::GOLD] < 2000 || myRes[Res::GEMS] < 10)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
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;
|
||||
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)
|
||||
return false;
|
||||
else if(cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case Obj::BOAT:
|
||||
return false;
|
||||
//Boats are handled by pathfinder
|
||||
case Obj::EYE_OF_MAGI:
|
||||
return false; //this object is useless to visit, but could be visited indefinitely
|
||||
}
|
||||
|
||||
if(obj->wasVisited(h)) //it must pointer to hero instance, heroPtr calls function wasVisited(ui8 player);
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
@ -167,7 +167,6 @@ void foreach_neighbour(const int3 & pos, std::function<void(const int3 & pos)> f
|
||||
void foreach_neighbour(CCallback * cbp, const int3 & pos, std::function<void(CCallback * cbp, const int3 & pos)> foo); // avoid costly retrieval of thread-specific pointer
|
||||
|
||||
bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater);
|
||||
//bool isBlockedBorderGate(int3 tileToHit);
|
||||
bool isObjectPassable(const CGObjectInstance * obj);
|
||||
bool isObjectPassable(const CGObjectInstance * obj, PlayerColor playerColor, PlayerRelations::PlayerRelations objectRelations);
|
||||
bool isBlockVisitObj(const int3 & pos);
|
||||
@ -184,6 +183,9 @@ 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);
|
||||
|
||||
class CDistanceSorter
|
||||
{
|
||||
const CGHeroInstance * hero;
|
||||
|
@ -89,11 +89,42 @@ float HeroManager::evaluateFightingStrength(const CGHeroInstance * hero) const
|
||||
return evaluateSpeciality(hero) + wariorSkillsScores.evaluateSecSkills(hero) + hero->level * 1.5f;
|
||||
}
|
||||
|
||||
std::vector<std::vector<const CGHeroInstance *>> clusterizeHeroes(CCallback * cb, std::vector<const CGHeroInstance *> heroes)
|
||||
{
|
||||
std::vector<std::vector<const CGHeroInstance *>> clusters;
|
||||
|
||||
for(auto hero : heroes)
|
||||
{
|
||||
auto paths = cb->getPathsInfo(hero);
|
||||
std::vector<const CGHeroInstance *> newCluster = {hero};
|
||||
|
||||
for(auto cluster = clusters.begin(); cluster != clusters.end();)
|
||||
{
|
||||
auto hero = std::find_if(cluster->begin(), cluster->end(), [&](const CGHeroInstance * h) -> bool
|
||||
{
|
||||
return paths->getNode(h->visitablePos())->turns <= SCOUT_TURN_DISTANCE_LIMIT;
|
||||
});
|
||||
|
||||
if(hero != cluster->end())
|
||||
{
|
||||
vstd::concatenate(newCluster, *cluster);
|
||||
clusters.erase(cluster);
|
||||
}
|
||||
else
|
||||
cluster++;
|
||||
}
|
||||
|
||||
clusters.push_back(newCluster);
|
||||
}
|
||||
|
||||
return clusters;
|
||||
}
|
||||
|
||||
void HeroManager::update()
|
||||
{
|
||||
logAi->trace("Start analysing our heroes");
|
||||
|
||||
std::map<HeroPtr, float> scores;
|
||||
std::map<const CGHeroInstance *, float> scores;
|
||||
auto myHeroes = cb->getHeroesInfo();
|
||||
|
||||
for(auto & hero : myHeroes)
|
||||
@ -101,16 +132,43 @@ void HeroManager::update()
|
||||
scores[hero] = evaluateFightingStrength(hero);
|
||||
}
|
||||
|
||||
std::sort(myHeroes.begin(), myHeroes.end(), [&](const HeroPtr & h1, const HeroPtr & h2) -> bool
|
||||
auto scoreSort = [&](const CGHeroInstance * h1, const CGHeroInstance * h2) -> bool
|
||||
{
|
||||
return scores.at(h1) > scores.at(h2);
|
||||
});
|
||||
};
|
||||
|
||||
int mainHeroCount = (myHeroes.size() + 2) / 3;
|
||||
int globalMainCount = std::min(((int)myHeroes.size() + 2) / 3, cb->getMapSize().x / 100 + 1);
|
||||
|
||||
std::sort(myHeroes.begin(), myHeroes.end(), scoreSort);
|
||||
|
||||
for(auto hero : myHeroes)
|
||||
{
|
||||
heroRoles[hero] = (globalMainCount--) > 0 ? HeroRole::MAIN : HeroRole::SCOUT;
|
||||
}
|
||||
|
||||
for(auto cluster : clusterizeHeroes(cb, myHeroes))
|
||||
{
|
||||
std::sort(cluster.begin(), cluster.end(), scoreSort);
|
||||
|
||||
auto localMainCountMax = (cluster.size() + 2) / 3;
|
||||
|
||||
for(auto hero : cluster)
|
||||
{
|
||||
if(heroRoles[hero] != HeroRole::MAIN)
|
||||
{
|
||||
heroRoles[hero] = HeroRole::MAIN;
|
||||
break;
|
||||
}
|
||||
|
||||
localMainCountMax--;
|
||||
|
||||
if(localMainCountMax == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for(auto hero : myHeroes)
|
||||
{
|
||||
heroRoles[hero] = (mainHeroCount--) > 0 ? HeroRole::MAIN : HeroRole::SCOUT;
|
||||
logAi->trace("Hero %s has role %s", hero->name, heroRoles[hero] == HeroRole::MAIN ? "main" : "scout");
|
||||
}
|
||||
}
|
||||
|
@ -51,12 +51,12 @@ private:
|
||||
static SecondarySkillEvaluator wariorSkillsScores;
|
||||
static SecondarySkillEvaluator scountSkillsScores;
|
||||
|
||||
CPlayerSpecificInfoCallback * cb; //this is enough, but we downcast from CCallback
|
||||
CCallback * cb; //this is enough, but we downcast from CCallback
|
||||
const Nullkiller * ai;
|
||||
std::map<HeroPtr, HeroRole> heroRoles;
|
||||
|
||||
public:
|
||||
HeroManager(CPlayerSpecificInfoCallback * CB, const Nullkiller * ai) : cb(CB), ai(ai) {}
|
||||
HeroManager(CCallback * CB, const Nullkiller * ai) : cb(CB), ai(ai) {}
|
||||
const std::map<HeroPtr, HeroRole> & getHeroRoles() const override;
|
||||
HeroRole getHeroRole(const HeroPtr & hero) const override;
|
||||
int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const override;
|
||||
|
@ -122,13 +122,17 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons
|
||||
|| blocker->ID == Obj::MONSTER
|
||||
|| blocker->ID == Obj::GARRISON2
|
||||
|| blocker->ID == Obj::BORDERGUARD
|
||||
|| blocker->ID == Obj::QUEST_GUARD
|
||||
|| blocker->ID == Obj::BORDER_GATE
|
||||
|| blocker->ID == Obj::SHIPYARD)
|
||||
{
|
||||
if(!isObjectPassable(blocker))
|
||||
return blocker;
|
||||
}
|
||||
|
||||
if(blocker->ID == Obj::QUEST_GUARD && node->actionIsBlocked)
|
||||
{
|
||||
return blocker;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
@ -179,13 +183,25 @@ void ObjectClusterizer::clusterize()
|
||||
blockedObjects.clear();
|
||||
|
||||
Obj ignoreObjects[] = {
|
||||
Obj::MONSTER,
|
||||
Obj::SIGN,
|
||||
Obj::REDWOOD_OBSERVATORY,
|
||||
Obj::MONOLITH_TWO_WAY,
|
||||
Obj::BOAT,
|
||||
Obj::EYE_OF_MAGI,
|
||||
Obj::MONOLITH_ONE_WAY_ENTRANCE,
|
||||
Obj::MONOLITH_ONE_WAY_EXIT,
|
||||
Obj::BUOY
|
||||
Obj::MONOLITH_TWO_WAY,
|
||||
Obj::SUBTERRANEAN_GATE,
|
||||
Obj::WHIRLPOOL,
|
||||
Obj::BUOY,
|
||||
Obj::SIGN,
|
||||
Obj::SIGN,
|
||||
Obj::GARRISON,
|
||||
Obj::MONSTER,
|
||||
Obj::GARRISON2,
|
||||
Obj::BORDERGUARD,
|
||||
Obj::QUEST_GUARD,
|
||||
Obj::BORDER_GATE,
|
||||
Obj::REDWOOD_OBSERVATORY,
|
||||
Obj::CARTOGRAPHER,
|
||||
Obj::PILLAR_OF_FIRE
|
||||
};
|
||||
|
||||
logAi->debug("Begin object clusterization");
|
||||
@ -195,10 +211,19 @@ void ObjectClusterizer::clusterize()
|
||||
if(!shouldVisitObject(obj))
|
||||
continue;
|
||||
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString());
|
||||
#endif
|
||||
|
||||
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
|
||||
{
|
||||
@ -209,19 +234,29 @@ void ObjectClusterizer::clusterize()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
bool added = false;
|
||||
bool directlyAccessible = false;
|
||||
std::set<const CGHeroInstance *> heroesProcessed;
|
||||
|
||||
for(auto & path : paths)
|
||||
{
|
||||
if(vstd::contains(heroesProcessed, path.targetHero))
|
||||
continue;
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Checking path %s", path.toString());
|
||||
#endif
|
||||
|
||||
heroesProcessed.insert(path.targetHero);
|
||||
if(!shouldVisit(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;
|
||||
}
|
||||
|
||||
if(path.nodes.size() > 1)
|
||||
{
|
||||
@ -229,6 +264,16 @@ void ObjectClusterizer::clusterize()
|
||||
|
||||
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)
|
||||
@ -237,34 +282,64 @@ void ObjectClusterizer::clusterize()
|
||||
blockedObjects[blocker] = cluster;
|
||||
}
|
||||
|
||||
if(!vstd::contains(cluster->objects, obj))
|
||||
{
|
||||
float priority = ai->priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)));
|
||||
float priority = ai->priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj)));
|
||||
|
||||
cluster->addObject(obj, path, priority);
|
||||
if(priority < MIN_PRIORITY)
|
||||
continue;
|
||||
|
||||
added = true;
|
||||
}
|
||||
cluster->addObject(obj, path, priority);
|
||||
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Path added to cluster %s%s", blocker->getObjectName(), blocker->visitablePos().toString());
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
directlyAccessible = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
directlyAccessible = true;
|
||||
}
|
||||
|
||||
heroesProcessed.insert(path.targetHero);
|
||||
}
|
||||
|
||||
if(!added || directlyAccessible)
|
||||
if(directlyAccessible)
|
||||
{
|
||||
AIPath & shortestPath = paths.front();
|
||||
float priority = ai->priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(shortestPath, obj)));
|
||||
|
||||
if(shortestPath.turn() <= 2 || priority > 0.6f)
|
||||
nearObjects.addObject(obj, shortestPath, 0);
|
||||
if(priority < MIN_PRIORITY)
|
||||
continue;
|
||||
|
||||
bool interestingObject = shortestPath.turn() <= 2 || priority > 0.5f;
|
||||
|
||||
if(interestingObject)
|
||||
{
|
||||
nearObjects.addObject(obj, shortestPath, priority);
|
||||
}
|
||||
else
|
||||
farObjects.addObject(obj, shortestPath, 0);
|
||||
{
|
||||
farObjects.addObject(obj, shortestPath, priority);
|
||||
}
|
||||
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Path %s added to %s objects. Turn: %d, priority: %f",
|
||||
shortestPath.toString(),
|
||||
interestingObject ? "near" : "far",
|
||||
shortestPath.turn(),
|
||||
priority);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
vstd::erase_if(blockedObjects, [](std::pair<const CGObjectInstance *, std::shared_ptr<ObjectCluster>> pair) -> bool
|
||||
{
|
||||
return pair.second->objects.empty();
|
||||
});
|
||||
|
||||
logAi->trace("Near objects count: %i", nearObjects.objects.size());
|
||||
logAi->trace("Far objects count: %i", farObjects.objects.size());
|
||||
for(auto pair : blockedObjects)
|
||||
|
@ -14,9 +14,6 @@
|
||||
#include "../Goals/ExecuteHeroChain.h"
|
||||
#include "CaptureObjectsBehavior.h"
|
||||
#include "../AIUtility.h"
|
||||
#include "../../../lib/mapping/CMap.h" //for victory conditions
|
||||
#include "../../../lib/CPathfinder.h"
|
||||
#include "../../../lib/CModHandler.h"
|
||||
|
||||
extern boost::thread_specific_ptr<CCallback> cb;
|
||||
extern boost::thread_specific_ptr<VCAI> ai;
|
||||
@ -163,7 +160,7 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const
|
||||
logAi->trace("Checking object %s, %s", objToVisit->getObjectName(), objToVisit->visitablePos().toString());
|
||||
#endif
|
||||
|
||||
if(!shouldVisitObject(objToVisit))
|
||||
if(!objectMatchesFilter(objToVisit))
|
||||
continue;
|
||||
|
||||
const int3 pos = objToVisit->visitablePos();
|
||||
@ -199,7 +196,7 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const
|
||||
return tasks;
|
||||
}
|
||||
|
||||
bool CaptureObjectsBehavior::shouldVisitObject(const CGObjectInstance * obj) const
|
||||
bool CaptureObjectsBehavior::objectMatchesFilter(const CGObjectInstance * obj) const
|
||||
{
|
||||
if(objectTypes.size() && !vstd::contains(objectTypes, obj->ID.num))
|
||||
{
|
||||
@ -213,122 +210,3 @@ bool CaptureObjectsBehavior::shouldVisitObject(const CGObjectInstance * obj) con
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CaptureObjectsBehavior::shouldVisit(HeroPtr h, const CGObjectInstance * obj)
|
||||
{
|
||||
switch(obj->ID)
|
||||
{
|
||||
case Obj::TOWN:
|
||||
case Obj::HERO: //never visit our heroes at random
|
||||
return obj->tempOwner != h->tempOwner; //do not visit our towns at random
|
||||
case Obj::BORDER_GATE:
|
||||
{
|
||||
for(auto q : ai->myCb->getMyQuests())
|
||||
{
|
||||
if(q.obj == obj)
|
||||
{
|
||||
return false; // do not visit guards or gates when wandering
|
||||
}
|
||||
}
|
||||
return true; //we don't have this quest yet
|
||||
}
|
||||
case Obj::BORDERGUARD: //open borderguard if possible
|
||||
return (dynamic_cast<const CGKeys *>(obj))->wasMyColorVisited(ai->playerID);
|
||||
case Obj::SEER_HUT:
|
||||
case Obj::QUEST_GUARD:
|
||||
{
|
||||
for(auto q : ai->myCb->getMyQuests())
|
||||
{
|
||||
if(q.obj == obj)
|
||||
{
|
||||
if(q.quest->checkQuest(h.h))
|
||||
return true; //we completed the quest
|
||||
else
|
||||
return false; //we can't complete this quest
|
||||
}
|
||||
}
|
||||
return true; //we don't have this quest yet
|
||||
}
|
||||
case Obj::CREATURE_GENERATOR1:
|
||||
{
|
||||
if(obj->tempOwner != h->tempOwner)
|
||||
return true; //flag just in case
|
||||
|
||||
const CGDwelling * d = dynamic_cast<const CGDwelling *>(obj);
|
||||
|
||||
for(auto level : d->creatures)
|
||||
{
|
||||
for(auto c : level.second)
|
||||
{
|
||||
if(level.first
|
||||
&& h->getSlotFor(CreatureID(c)) != SlotID()
|
||||
&& cb->getResourceAmount().canAfford(c.toCreature()->cost))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
case Obj::HILL_FORT:
|
||||
{
|
||||
for(auto slot : h->Slots())
|
||||
{
|
||||
if(slot.second->type->upgrades.size())
|
||||
return true; //TODO: check price?
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case Obj::MONOLITH_ONE_WAY_ENTRANCE:
|
||||
case Obj::MONOLITH_ONE_WAY_EXIT:
|
||||
case Obj::MONOLITH_TWO_WAY:
|
||||
case Obj::WHIRLPOOL:
|
||||
return false;
|
||||
case Obj::SCHOOL_OF_MAGIC:
|
||||
case Obj::SCHOOL_OF_WAR:
|
||||
{
|
||||
if(cb->getResourceAmount(Res::GOLD) < 1000)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case Obj::LIBRARY_OF_ENLIGHTENMENT:
|
||||
if(h->level < 12)
|
||||
return false;
|
||||
break;
|
||||
case Obj::TREE_OF_KNOWLEDGE:
|
||||
{
|
||||
if(ai->nullkiller->heroManager->getHeroRole(h) == HeroRole::SCOUT)
|
||||
return false;
|
||||
|
||||
TResources myRes = cb->getResourceAmount();
|
||||
if(myRes[Res::GOLD] < 2000 || myRes[Res::GEMS] < 10)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
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;
|
||||
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)
|
||||
return false;
|
||||
else if(cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case Obj::BOAT:
|
||||
return false;
|
||||
//Boats are handled by pathfinder
|
||||
case Obj::EYE_OF_MAGI:
|
||||
return false; //this object is useless to visit, but could be visited indefinitely
|
||||
}
|
||||
|
||||
if(obj->wasVisited(*h)) //it must pointer to hero instance, heroPtr calls function wasVisited(ui8 player);
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -68,8 +68,7 @@ namespace Goals
|
||||
static Goals::TGoalVec getVisitGoals(const std::vector<AIPath> & paths, const CGObjectInstance * objToVisit = nullptr);
|
||||
|
||||
private:
|
||||
bool shouldVisitObject(const CGObjectInstance * obj) const;
|
||||
static bool shouldVisit(HeroPtr h, const CGObjectInstance * obj);
|
||||
bool objectMatchesFilter(const CGObjectInstance * obj) const;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -42,13 +42,14 @@ TGoalVec CompleteQuest::decompose() const
|
||||
}
|
||||
|
||||
return solutions;*/
|
||||
logAi->debug("Trying to realize quest: %s", questToString());
|
||||
|
||||
if(q.obj && (q.obj->ID == Obj::BORDER_GATE || q.obj->ID == Obj::BORDERGUARD))
|
||||
{
|
||||
return missionKeymaster();
|
||||
}
|
||||
|
||||
logAi->debug("Trying to realize quest: %s", questToString());
|
||||
|
||||
switch(q.quest->missionType)
|
||||
{
|
||||
case CQuest::MISSION_ART:
|
||||
@ -107,7 +108,7 @@ TGoalVec CompleteQuest::tryCompleteQuest() const
|
||||
{
|
||||
TGoalVec solutions;
|
||||
|
||||
auto tasks = CaptureObjectsBehavior(q.obj).decompose(); //TODO: choose best / free hero from among many possibilities?
|
||||
auto tasks = CaptureObjectsBehavior(q.obj).decompose();
|
||||
|
||||
for(auto task : tasks)
|
||||
{
|
||||
@ -177,14 +178,14 @@ TGoalVec CompleteQuest::missionLevel() const
|
||||
|
||||
TGoalVec CompleteQuest::missionKeymaster() const
|
||||
{
|
||||
TGoalVec solutions = tryCompleteQuest();
|
||||
|
||||
if(solutions.empty())
|
||||
if(isObjectPassable(q.obj))
|
||||
{
|
||||
return CaptureObjectsBehavior(q.obj).decompose();
|
||||
}
|
||||
else
|
||||
{
|
||||
return CaptureObjectsBehavior().ofType(Obj::KEYMASTER, q.obj->subID).decompose();
|
||||
}
|
||||
|
||||
return solutions;
|
||||
}
|
||||
|
||||
TGoalVec CompleteQuest::missionResources() const
|
||||
|
@ -182,6 +182,7 @@ void Nullkiller::updateAiState(int pass)
|
||||
|
||||
PathfinderSettings cfg;
|
||||
cfg.useHeroChain = true;
|
||||
cfg.scoutTurnDistanceLimit = SCOUT_TURN_DISTANCE_LIMIT;
|
||||
|
||||
pathfinder->updatePaths(activeHeroes, cfg);
|
||||
|
||||
|
@ -20,7 +20,7 @@ extern boost::thread_specific_ptr<VCAI> ai;
|
||||
using namespace Goals;
|
||||
|
||||
ExecuteHeroChain::ExecuteHeroChain(const AIPath & path, const CGObjectInstance * obj)
|
||||
:ElementarGoal(Goals::EXECUTE_HERO_CHAIN), chainPath(path)
|
||||
:ElementarGoal(Goals::EXECUTE_HERO_CHAIN), chainPath(path), closestWayRatio(1)
|
||||
{
|
||||
hero = path.targetHero;
|
||||
tile = path.targetTile();
|
||||
|
@ -19,13 +19,30 @@
|
||||
#include "../../../lib/PathfinderUtil.h"
|
||||
#include "../../../lib/CPlayerState.h"
|
||||
|
||||
/// 1-3 - position on map, 4 - layer (air, water, land), 5 - chain (normal, battle, spellcast and combinations)
|
||||
boost::multi_array<AIPathNode, 5> nodes;
|
||||
std::shared_ptr<boost::multi_array<AIPathNode, 5>> AISharedStorage::shared;
|
||||
|
||||
AISharedStorage::AISharedStorage(int3 sizes)
|
||||
{
|
||||
if(!shared){
|
||||
shared.reset(new boost::multi_array<AIPathNode, 5>(
|
||||
boost::extents[sizes.x][sizes.y][sizes.z][EPathfindingLayer::NUM_LAYERS][AINodeStorage::NUM_CHAINS]));
|
||||
}
|
||||
|
||||
nodes = shared;
|
||||
}
|
||||
|
||||
AISharedStorage::~AISharedStorage()
|
||||
{
|
||||
nodes.reset();
|
||||
if(shared && shared.use_count() == 1)
|
||||
{
|
||||
shared.reset();
|
||||
}
|
||||
}
|
||||
|
||||
AINodeStorage::AINodeStorage(const Nullkiller * ai, const int3 & Sizes)
|
||||
: sizes(Sizes), ai(ai), cb(ai->cb.get())
|
||||
: sizes(Sizes), ai(ai), cb(ai->cb.get()), nodes(Sizes)
|
||||
{
|
||||
nodes.resize(boost::extents[sizes.x][sizes.y][sizes.z][EPathfindingLayer::NUM_LAYERS][NUM_CHAINS]);
|
||||
dangerEvaluator.reset(new FuzzyHelper(ai));
|
||||
}
|
||||
|
||||
@ -104,9 +121,7 @@ boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
|
||||
const EPathfindingLayer layer,
|
||||
const ChainActor * actor)
|
||||
{
|
||||
auto chains = nodes[pos.x][pos.y][pos.z][layer];
|
||||
|
||||
for(AIPathNode & node : chains)
|
||||
for(AIPathNode & node : nodes.get(pos, layer))
|
||||
{
|
||||
if(node.actor == actor)
|
||||
{
|
||||
@ -165,10 +180,8 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
|
||||
|
||||
void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility)
|
||||
{
|
||||
for(int i = 0; i < NUM_CHAINS; i++)
|
||||
for(AIPathNode & heroNode : nodes.get(coord, layer))
|
||||
{
|
||||
AIPathNode & heroNode = nodes[coord.x][coord.y][coord.z][layer][i];
|
||||
|
||||
heroNode.actor = nullptr;
|
||||
heroNode.danger = 0;
|
||||
heroNode.manaCost = 0;
|
||||
@ -279,7 +292,7 @@ bool AINodeStorage::calculateHeroChainFinal()
|
||||
{
|
||||
foreach_tile_pos([&](const int3 & pos)
|
||||
{
|
||||
auto chains = nodes[pos.x][pos.y][pos.z][layer];
|
||||
auto chains = nodes.get(pos, layer);
|
||||
|
||||
for(AIPathNode & node : chains)
|
||||
{
|
||||
@ -313,7 +326,7 @@ bool AINodeStorage::calculateHeroChain()
|
||||
{
|
||||
foreach_tile_pos([&](const int3 & pos)
|
||||
{
|
||||
auto chains = nodes[pos.x][pos.y][pos.z][layer];
|
||||
auto chains = nodes.get(pos, layer);
|
||||
|
||||
existingChains.resize(0);
|
||||
newChains.resize(0);
|
||||
@ -394,7 +407,7 @@ void AINodeStorage::cleanupInefectiveChains(std::vector<ExchangeCandidate> & res
|
||||
vstd::erase_if(result, [&](const ExchangeCandidate & chainInfo) -> bool
|
||||
{
|
||||
auto pos = chainInfo.coord;
|
||||
auto chains = nodes[pos.x][pos.y][pos.z][EPathfindingLayer::LAND];
|
||||
auto chains = nodes.get(pos, EPathfindingLayer::LAND);
|
||||
|
||||
return hasBetterChain(chainInfo.carrierParent, &chainInfo, chains)
|
||||
|| hasBetterChain(chainInfo.carrierParent, &chainInfo, result);
|
||||
@ -910,7 +923,7 @@ void AINodeStorage::calculateTownPortalTeleportations(std::vector<CGPathNode *>
|
||||
bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
|
||||
{
|
||||
auto pos = destination.coord;
|
||||
auto chains = nodes[pos.x][pos.y][pos.z][EPathfindingLayer::LAND];
|
||||
auto chains = nodes.get(pos, EPathfindingLayer::LAND);
|
||||
|
||||
return hasBetterChain(source.node, getAINode(destination.node), chains);
|
||||
}
|
||||
@ -950,7 +963,7 @@ bool AINodeStorage::hasBetterChain(
|
||||
}
|
||||
}
|
||||
|
||||
if(candidateActor->chainMask != node.actor->chainMask && heroChainPass == EHeroChainPass::CHAIN)
|
||||
if(candidateActor->chainMask != node.actor->chainMask && heroChainPass != EHeroChainPass::FINAL)
|
||||
continue;
|
||||
|
||||
auto nodeActor = node.actor;
|
||||
@ -1006,7 +1019,7 @@ bool AINodeStorage::hasBetterChain(
|
||||
|
||||
bool AINodeStorage::isTileAccessible(const HeroPtr & hero, const int3 & pos, const EPathfindingLayer layer) const
|
||||
{
|
||||
auto chains = nodes[pos.x][pos.y][pos.z][layer];
|
||||
auto chains = nodes.get(pos, layer);
|
||||
|
||||
for(const AIPathNode & node : chains)
|
||||
{
|
||||
@ -1026,7 +1039,7 @@ std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand)
|
||||
|
||||
paths.reserve(NUM_CHAINS / 4);
|
||||
|
||||
auto chains = nodes[pos.x][pos.y][pos.z][isOnLand ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL];
|
||||
auto chains = nodes.get(pos, isOnLand ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL);
|
||||
|
||||
for(const AIPathNode & node : chains)
|
||||
{
|
||||
@ -1074,10 +1087,13 @@ void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path, int pa
|
||||
pathNode.danger = node->danger;
|
||||
pathNode.coord = node->coord;
|
||||
pathNode.parentIndex = parentIndex;
|
||||
pathNode.actionIsBlocked = false;
|
||||
|
||||
if(pathNode.specialAction)
|
||||
{
|
||||
pathNode.actionIsBlocked = !pathNode.specialAction->canAct(node);
|
||||
auto targetNode =node->theNodeBefore ? getAINode(node->theNodeBefore) : node;
|
||||
|
||||
pathNode.actionIsBlocked = !pathNode.specialAction->canAct(targetNode);
|
||||
}
|
||||
|
||||
parentIndex = path.nodes.size();
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
#define PATHFINDER_TRACE_LEVEL 0
|
||||
#define AI_TRACE_LEVEL 0
|
||||
#define SCOUT_TURN_DISTANCE_LIMIT 3
|
||||
|
||||
#include "../../../lib/CPathfinder.h"
|
||||
#include "../../../lib/mapObjects/CGHeroInstance.h"
|
||||
@ -101,6 +102,28 @@ enum EHeroChainPass
|
||||
FINAL // same as SINGLE but for heroes from CHAIN pass
|
||||
};
|
||||
|
||||
class AISharedStorage
|
||||
{
|
||||
/// 1-3 - position on map, 4 - layer (air, water, land), 5 - chain (normal, battle, spellcast and combinations)
|
||||
static std::shared_ptr<boost::multi_array<AIPathNode, 5>> shared;
|
||||
std::shared_ptr<boost::multi_array<AIPathNode, 5>> nodes;
|
||||
public:
|
||||
AISharedStorage(int3 mapSize);
|
||||
~AISharedStorage();
|
||||
|
||||
/*STRONG_INLINE
|
||||
boost::detail::multi_array::sub_array<AIPathNode, 1> get(int3 tile, EPathfindingLayer layer)
|
||||
{
|
||||
return (*nodes)[tile.x][tile.y][tile.z][layer];
|
||||
}*/
|
||||
|
||||
STRONG_INLINE
|
||||
boost::detail::multi_array::sub_array<AIPathNode, 1> get(int3 tile, EPathfindingLayer layer) const
|
||||
{
|
||||
return (*nodes)[tile.x][tile.y][tile.z][layer];
|
||||
}
|
||||
};
|
||||
|
||||
class AINodeStorage : public INodeStorage
|
||||
{
|
||||
private:
|
||||
@ -109,6 +132,7 @@ private:
|
||||
const CPlayerSpecificInfoCallback * cb;
|
||||
const Nullkiller * ai;
|
||||
std::unique_ptr<FuzzyHelper> dangerEvaluator;
|
||||
AISharedStorage nodes;
|
||||
std::vector<std::shared_ptr<ChainActor>> actors;
|
||||
std::vector<CGPathNode *> heroChain;
|
||||
EHeroChainPass heroChainPass; // true if we need to calculate hero chain
|
||||
|
@ -93,6 +93,55 @@ namespace AIPathfinding
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AIMovementAfterDestinationRule::bypassQuest(
|
||||
const PathNodeInfo & source,
|
||||
CDestinationNodeInfo & destination,
|
||||
const PathfinderConfig * pathfinderConfig,
|
||||
CPathfinderHelper * pathfinderHelper) const
|
||||
{
|
||||
const AIPathNode * destinationNode = nodeStorage->getAINode(destination.node);
|
||||
auto questObj = dynamic_cast<const IQuestObject *>(destination.nodeObject);
|
||||
auto questInfo = QuestInfo(questObj->quest, destination.nodeObject, destination.coord);
|
||||
auto nodeHero = pathfinderHelper->hero;
|
||||
QuestAction questAction(questInfo);
|
||||
|
||||
if(destination.nodeObject->ID == Obj::QUEST_GUARD && questObj->quest->missionType == CQuest::MISSION_NONE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!questAction.canAct(destinationNode))
|
||||
{
|
||||
if(!destinationNode->actor->allowUseResources)
|
||||
{
|
||||
boost::optional<AIPathNode *> questNode = nodeStorage->getOrCreateNode(
|
||||
destination.coord,
|
||||
destination.node->layer,
|
||||
destinationNode->actor->resourceActor);
|
||||
|
||||
if(!questNode || questNode.get()->cost < destination.cost)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
destinationNode = questNode.get();
|
||||
destination.node = questNode.get();
|
||||
|
||||
nodeStorage->commit(destination, source);
|
||||
AIPreviousNodeRule(nodeStorage).process(source, destination, pathfinderConfig, pathfinderHelper);
|
||||
}
|
||||
|
||||
nodeStorage->updateAINode(destination.node, [&](AIPathNode * node)
|
||||
{
|
||||
auto questInfo = QuestInfo(questObj->quest, destination.nodeObject, destination.coord);
|
||||
|
||||
node->specialAction.reset(new QuestAction(questAction));
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AIMovementAfterDestinationRule::bypassRemovableObject(
|
||||
const PathNodeInfo & source,
|
||||
CDestinationNodeInfo & destination,
|
||||
@ -103,26 +152,7 @@ namespace AIPathfinding
|
||||
|| destination.nodeObject->ID == Obj::BORDERGUARD
|
||||
|| destination.nodeObject->ID == Obj::BORDER_GATE)
|
||||
{
|
||||
auto questObj = dynamic_cast<const IQuestObject *>(destination.nodeObject);
|
||||
auto nodeHero = pathfinderHelper->hero;
|
||||
|
||||
if(destination.nodeObject->ID == Obj::QUEST_GUARD && questObj->quest->missionType == CQuest::MISSION_NONE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!destination.nodeObject->wasVisited(nodeHero->tempOwner)
|
||||
|| !questObj->checkQuest(nodeHero))
|
||||
{
|
||||
nodeStorage->updateAINode(destination.node, [&](AIPathNode * node)
|
||||
{
|
||||
auto questInfo = QuestInfo(questObj->quest, destination.nodeObject, destination.coord);
|
||||
|
||||
node->specialAction.reset(new QuestAction(questInfo));
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
return bypassQuest(source, destination, pathfinderConfig, pathfinderHelper);
|
||||
}
|
||||
|
||||
auto enemyHero = destination.nodeHero && destination.heroRelations == PlayerRelations::ENEMIES;
|
||||
|
@ -58,5 +58,11 @@ namespace AIPathfinding
|
||||
CDestinationNodeInfo & destination,
|
||||
const PathfinderConfig * pathfinderConfig,
|
||||
CPathfinderHelper * pathfinderHelper) const;
|
||||
|
||||
bool bypassQuest(
|
||||
const PathNodeInfo & source,
|
||||
CDestinationNodeInfo & destination,
|
||||
const PathfinderConfig * pathfinderConfig,
|
||||
CPathfinderHelper * pathfinderHelper) const;
|
||||
};
|
||||
}
|
||||
|
@ -343,6 +343,9 @@ void VCAI::objectRemoved(const CGObjectInstance * obj)
|
||||
LOG_TRACE(logAi);
|
||||
NET_EVENT_HANDLER;
|
||||
|
||||
if(!nullkiller) // crash protection
|
||||
return;
|
||||
|
||||
nullkiller->memory->removeFromMemory(obj);
|
||||
|
||||
if(obj->ID == Obj::HERO && obj->tempOwner == playerID)
|
||||
@ -441,6 +444,9 @@ void VCAI::objectPropertyChanged(const SetObjectProperty * sop)
|
||||
auto relations = myCb->getPlayerRelations(playerID, (PlayerColor)sop->val);
|
||||
auto obj = myCb->getObj(sop->id, false);
|
||||
|
||||
if(!nullkiller) // crash protection
|
||||
return;
|
||||
|
||||
if(obj)
|
||||
{
|
||||
if(relations == PlayerRelations::ENEMIES)
|
||||
@ -1446,6 +1452,8 @@ void VCAI::finish()
|
||||
makingTurn->join();
|
||||
makingTurn.reset();
|
||||
}
|
||||
|
||||
nullkiller.reset();
|
||||
}
|
||||
|
||||
void VCAI::requestActionASAP(std::function<void()> whatToDo)
|
||||
|
Loading…
Reference in New Issue
Block a user