1
0
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:
Andrii Danylchenko 2021-05-16 14:56:27 +03:00 committed by Andrii Danylchenko
parent 645c393e25
commit 1fdf0de75d
15 changed files with 420 additions and 208 deletions

View File

@ -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;
}

View File

@ -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;

View File

@ -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");
}
}

View File

@ -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;

View File

@ -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)

View File

@ -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;
}

View File

@ -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;
};
}

View File

@ -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

View File

@ -182,6 +182,7 @@ void Nullkiller::updateAiState(int pass)
PathfinderSettings cfg;
cfg.useHeroChain = true;
cfg.scoutTurnDistanceLimit = SCOUT_TURN_DISTANCE_LIMIT;
pathfinder->updatePaths(activeHeroes, cfg);

View File

@ -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();

View File

@ -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();

View File

@ -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

View File

@ -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;

View File

@ -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;
};
}

View File

@ -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)