1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-12 02:28:11 +02:00

NKAI: initial implementation of object graph

This commit is contained in:
Andrii Danylchenko 2024-01-20 22:54:30 +02:00
parent a373ec6743
commit 376a17409f
17 changed files with 476 additions and 50 deletions

View File

@ -185,8 +185,8 @@ void foreach_tile_pos(const Func & foo)
} }
} }
template<class Func> template<class Func, class TCallback>
void foreach_tile_pos(CCallback * cbp, const Func & foo) // avoid costly retrieval of thread-specific pointer void foreach_tile_pos(TCallback * cbp, const Func & foo) // avoid costly retrieval of thread-specific pointer
{ {
int3 mapSize = cbp->getMapSize(); int3 mapSize = cbp->getMapSize();
for(int z = 0; z < mapSize.z; z++) for(int z = 0; z < mapSize.z; z++)

View File

@ -75,8 +75,7 @@ void DangerHitMapAnalyzer::updateHitMap()
PathfinderSettings ps; PathfinderSettings ps;
ps.mainTurnDistanceLimit = 10; ps.scoutTurnDistanceLimit = ps.mainTurnDistanceLimit = MAIN_TURN_DISTANCE_LIMIT;
ps.scoutTurnDistanceLimit = 10;
ps.useHeroChain = false; ps.useHeroChain = false;
ai->pathfinder->updatePaths(pair.second, ps); ai->pathfinder->updatePaths(pair.second, ps);
@ -160,9 +159,6 @@ void DangerHitMapAnalyzer::calculateTileOwners()
std::map<const CGHeroInstance *, HeroRole> townHeroes; std::map<const CGHeroInstance *, HeroRole> townHeroes;
std::map<const CGHeroInstance *, const CGTownInstance *> heroTownMap; std::map<const CGHeroInstance *, const CGTownInstance *> heroTownMap;
PathfinderSettings pathfinderSettings;
pathfinderSettings.mainTurnDistanceLimit = 5;
auto addTownHero = [&](const CGTownInstance * town) auto addTownHero = [&](const CGTownInstance * town)
{ {
@ -192,7 +188,10 @@ void DangerHitMapAnalyzer::calculateTileOwners()
addTownHero(town); addTownHero(town);
} }
ai->pathfinder->updatePaths(townHeroes, PathfinderSettings()); PathfinderSettings ps;
ps.mainTurnDistanceLimit = ps.scoutTurnDistanceLimit = MAIN_TURN_DISTANCE_LIMIT;
ai->pathfinder->updatePaths(townHeroes, ps);
pforeachTilePos(mapSize, [&](const int3 & pos) pforeachTilePos(mapSize, [&](const int3 & pos)
{ {
@ -239,6 +238,11 @@ void DangerHitMapAnalyzer::calculateTileOwners()
hitMap[pos.x][pos.y][pos.z].closestTown = enemyTown; hitMap[pos.x][pos.y][pos.z].closestTown = enemyTown;
} }
}); });
for(auto h : townHeroes)
{
delete h.first;
}
} }
const std::vector<HitMapInfo> & DangerHitMapAnalyzer::getTownThreats(const CGTownInstance * town) const const std::vector<HitMapInfo> & DangerHitMapAnalyzer::getTownThreats(const CGTownInstance * town) const

View File

@ -244,7 +244,7 @@ void ObjectClusterizer::clusterize()
logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString()); logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString());
#endif #endif
auto paths = ai->pathfinder->getPathInfo(obj->visitablePos()); auto paths = ai->pathfinder->getPathInfo(obj->visitablePos(), true);
if(paths.empty()) if(paths.empty())
{ {

View File

@ -179,7 +179,7 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const
const int3 pos = objToVisit->visitablePos(); const int3 pos = objToVisit->visitablePos();
auto paths = ai->nullkiller->pathfinder->getPathInfo(pos); auto paths = ai->nullkiller->pathfinder->getPathInfo(pos, true);
std::vector<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj; std::vector<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj;
std::shared_ptr<ExecuteHeroChain> closestWay; std::shared_ptr<ExecuteHeroChain> closestWay;
@ -210,7 +210,7 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const
{ {
captureObjects(ai->nullkiller->objectClusterizer->getNearbyObjects()); captureObjects(ai->nullkiller->objectClusterizer->getNearbyObjects());
if(tasks.empty() || ai->nullkiller->getScanDepth() != ScanDepth::SMALL) if(tasks.empty())
captureObjects(ai->nullkiller->objectClusterizer->getFarObjects()); captureObjects(ai->nullkiller->objectClusterizer->getFarObjects());
} }

View File

@ -42,7 +42,7 @@ Goals::TGoalVec ClusterBehavior::decompose() const
Goals::TGoalVec ClusterBehavior::decomposeCluster(std::shared_ptr<ObjectCluster> cluster) const Goals::TGoalVec ClusterBehavior::decomposeCluster(std::shared_ptr<ObjectCluster> cluster) const
{ {
auto center = cluster->calculateCenter(); auto center = cluster->calculateCenter();
auto paths = ai->nullkiller->pathfinder->getPathInfo(center->visitablePos()); auto paths = ai->nullkiller->pathfinder->getPathInfo(center->visitablePos(), true);
auto blockerPos = cluster->blocker->visitablePos(); auto blockerPos = cluster->blocker->visitablePos();
std::vector<AIPath> blockerPaths; std::vector<AIPath> blockerPaths;

View File

@ -14,6 +14,7 @@ set(Nullkiller_SRCS
Pathfinding/Rules/AIMovementAfterDestinationRule.cpp Pathfinding/Rules/AIMovementAfterDestinationRule.cpp
Pathfinding/Rules/AIMovementToDestinationRule.cpp Pathfinding/Rules/AIMovementToDestinationRule.cpp
Pathfinding/Rules/AIPreviousNodeRule.cpp Pathfinding/Rules/AIPreviousNodeRule.cpp
Pathfinding/ObjectGraph.cpp
AIUtility.cpp AIUtility.cpp
Analyzers/ArmyManager.cpp Analyzers/ArmyManager.cpp
Analyzers/HeroManager.cpp Analyzers/HeroManager.cpp
@ -78,6 +79,7 @@ set(Nullkiller_HEADERS
Pathfinding/Rules/AIMovementAfterDestinationRule.h Pathfinding/Rules/AIMovementAfterDestinationRule.h
Pathfinding/Rules/AIMovementToDestinationRule.h Pathfinding/Rules/AIMovementToDestinationRule.h
Pathfinding/Rules/AIPreviousNodeRule.h Pathfinding/Rules/AIPreviousNodeRule.h
Pathfinding/ObjectGraph.h
AIUtility.h AIUtility.h
Analyzers/ArmyManager.h Analyzers/ArmyManager.h
Analyzers/HeroManager.h Analyzers/HeroManager.h

View File

@ -27,6 +27,9 @@ namespace NKAI
using namespace Goals; using namespace Goals;
// while we play vcmieagles graph can be shared
std::unique_ptr<ObjectGraph> Nullkiller::baseGraph;
Nullkiller::Nullkiller() Nullkiller::Nullkiller()
:activeHero(nullptr), scanDepth(ScanDepth::MAIN_FULL), useHeroChain(true) :activeHero(nullptr), scanDepth(ScanDepth::MAIN_FULL), useHeroChain(true)
{ {
@ -119,6 +122,12 @@ void Nullkiller::resetAiState()
lockedHeroes.clear(); lockedHeroes.clear();
dangerHitMap->reset(); dangerHitMap->reset();
useHeroChain = true; useHeroChain = true;
if(!baseGraph)
{
baseGraph = std::make_unique<ObjectGraph>();
baseGraph->updateGraph(this);
}
} }
void Nullkiller::updateAiState(int pass, bool fast) void Nullkiller::updateAiState(int pass, bool fast)
@ -173,6 +182,7 @@ void Nullkiller::updateAiState(int pass, bool fast)
boost::this_thread::interruption_point(); boost::this_thread::interruption_point();
pathfinder->updatePaths(activeHeroes, cfg); pathfinder->updatePaths(activeHeroes, cfg);
pathfinder->updateGraphs(activeHeroes);
boost::this_thread::interruption_point(); boost::this_thread::interruption_point();

View File

@ -25,7 +25,6 @@ namespace NKAI
{ {
const float MIN_PRIORITY = 0.01f; const float MIN_PRIORITY = 0.01f;
const float SMALL_SCAN_MIN_PRIORITY = 0.4f;
enum class HeroLockedReason enum class HeroLockedReason
{ {
@ -38,15 +37,6 @@ enum class HeroLockedReason
HERO_CHAIN = 3 HERO_CHAIN = 3
}; };
enum class ScanDepth
{
MAIN_FULL = 0,
SMALL = 1,
ALL_FULL = 2
};
class Nullkiller class Nullkiller
{ {
private: private:
@ -54,11 +44,12 @@ private:
int3 targetTile; int3 targetTile;
ObjectInstanceID targetObject; ObjectInstanceID targetObject;
std::map<const CGHeroInstance *, HeroLockedReason> lockedHeroes; std::map<const CGHeroInstance *, HeroLockedReason> lockedHeroes;
ScanDepth scanDepth;
TResources lockedResources; TResources lockedResources;
bool useHeroChain; bool useHeroChain;
public: public:
static std::unique_ptr<ObjectGraph> baseGraph;
std::unique_ptr<DangerHitMapAnalyzer> dangerHitMap; std::unique_ptr<DangerHitMapAnalyzer> dangerHitMap;
std::unique_ptr<BuildAnalyzer> buildAnalyzer; std::unique_ptr<BuildAnalyzer> buildAnalyzer;
std::unique_ptr<ObjectClusterizer> objectClusterizer; std::unique_ptr<ObjectClusterizer> objectClusterizer;
@ -94,7 +85,6 @@ public:
int32_t getFreeGold() const { return getFreeResources()[EGameResID::GOLD]; } int32_t getFreeGold() const { return getFreeResources()[EGameResID::GOLD]; }
void lockResources(const TResources & res); void lockResources(const TResources & res);
const TResources & getLockedResources() const { return lockedResources; } const TResources & getLockedResources() const { return lockedResources; }
ScanDepth getScanDepth() const { return scanDepth; }
private: private:
void resetAiState(); void resetAiState();

View File

@ -26,13 +26,9 @@ namespace NKAI
{ {
namespace AIPathfinding namespace AIPathfinding
{ {
#ifdef ENVIRONMENT64
const int BUCKET_COUNT = 7;
#else
const int BUCKET_COUNT = 5;
#endif // ENVIRONMENT64
const int BUCKET_SIZE = 5; const int BUCKET_COUNT = 5;
const int BUCKET_SIZE = 3;
const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE; const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE;
const int THREAD_COUNT = 8; const int THREAD_COUNT = 8;
const int CHAIN_MAX_DEPTH = 4; const int CHAIN_MAX_DEPTH = 4;

View File

@ -33,7 +33,7 @@ bool AIPathfinder::isTileAccessible(const HeroPtr & hero, const int3 & tile) con
|| storage->isTileAccessible(hero, tile, EPathfindingLayer::SAIL); || storage->isTileAccessible(hero, tile, EPathfindingLayer::SAIL);
} }
std::vector<AIPath> AIPathfinder::getPathInfo(const int3 & tile) const std::vector<AIPath> AIPathfinder::getPathInfo(const int3 & tile, bool includeGraph) const
{ {
const TerrainTile * tileInfo = cb->getTile(tile, false); const TerrainTile * tileInfo = cb->getTile(tile, false);
@ -42,7 +42,19 @@ std::vector<AIPath> AIPathfinder::getPathInfo(const int3 & tile) const
return std::vector<AIPath>(); return std::vector<AIPath>();
} }
return storage->getChainInfo(tile, !tileInfo->isWater()); auto info = storage->getChainInfo(tile, !tileInfo->isWater());
if(includeGraph)
{
for(auto hero : cb->getHeroesInfo())
{
auto & graph = heroGraphs.at(hero->id);
graph.addChainInfo(info, tile, hero, ai);
}
}
return info;
} }
void AIPathfinder::updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes, PathfinderSettings pathfinderSettings) void AIPathfinder::updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes, PathfinderSettings pathfinderSettings)
@ -71,7 +83,7 @@ void AIPathfinder::updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes
storage->setTownsAndDwellings(cb->getTownsInfo(), ai->memory->visitableObjs); storage->setTownsAndDwellings(cb->getTownsInfo(), ai->memory->visitableObjs);
} }
auto config = std::make_shared<AIPathfinding::AIPathfinderConfig>(cb, ai, storage); auto config = std::make_shared<AIPathfinding::AIPathfinderConfig>(cb, ai, storage, pathfinderSettings.allowBypassObjects);
logAi->trace("Recalculate paths pass %d", pass++); logAi->trace("Recalculate paths pass %d", pass++);
cb->calculatePaths(config); cb->calculatePaths(config);
@ -112,4 +124,33 @@ void AIPathfinder::updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes
logAi->trace("Recalculated paths in %ld", timeElapsed(start)); logAi->trace("Recalculated paths in %ld", timeElapsed(start));
} }
void AIPathfinder::updateGraphs(std::map<const CGHeroInstance *, HeroRole> heroes)
{
auto start = std::chrono::high_resolution_clock::now();
std::vector<const CGHeroInstance *> heroesVector;
heroGraphs.clear();
for(auto hero : heroes)
{
heroGraphs.emplace(hero.first->id, GraphPaths());
heroesVector.push_back(hero.first);
}
parallel_for(blocked_range<size_t>(0, heroesVector.size()), [&](const blocked_range<size_t> & r)
{
for(auto i = r.begin(); i != r.end(); i++)
heroGraphs[heroesVector[i]->id].calculatePaths(heroesVector[i], ai);
});
#if NKAI_TRACE_LEVEL >= 2
for(auto hero : heroes)
{
heroGraphs[hero.first->id].dumpToLog();
}
#endif
logAi->trace("Graph paths updated in %lld", timeElapsed(start));
}
} }

View File

@ -11,6 +11,7 @@
#pragma once #pragma once
#include "AINodeStorage.h" #include "AINodeStorage.h"
#include "ObjectGraph.h"
#include "../AIUtility.h" #include "../AIUtility.h"
namespace NKAI namespace NKAI
@ -23,11 +24,13 @@ struct PathfinderSettings
bool useHeroChain; bool useHeroChain;
uint8_t scoutTurnDistanceLimit; uint8_t scoutTurnDistanceLimit;
uint8_t mainTurnDistanceLimit; uint8_t mainTurnDistanceLimit;
bool allowBypassObjects;
PathfinderSettings() PathfinderSettings()
:useHeroChain(false), :useHeroChain(false),
scoutTurnDistanceLimit(255), scoutTurnDistanceLimit(255),
mainTurnDistanceLimit(255) mainTurnDistanceLimit(255),
allowBypassObjects(true)
{ } { }
}; };
@ -37,12 +40,14 @@ private:
std::shared_ptr<AINodeStorage> storage; std::shared_ptr<AINodeStorage> storage;
CPlayerSpecificInfoCallback * cb; CPlayerSpecificInfoCallback * cb;
Nullkiller * ai; Nullkiller * ai;
std::map<ObjectInstanceID, GraphPaths> heroGraphs;
public: public:
AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * ai); AIPathfinder(CPlayerSpecificInfoCallback * cb, Nullkiller * ai);
std::vector<AIPath> getPathInfo(const int3 & tile) const; std::vector<AIPath> getPathInfo(const int3 & tile, bool includeGraph = false) const;
bool isTileAccessible(const HeroPtr & hero, const int3 & tile) const; bool isTileAccessible(const HeroPtr & hero, const int3 & tile) const;
void updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes, PathfinderSettings pathfinderSettings); void updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes, PathfinderSettings pathfinderSettings);
void updateGraphs(std::map<const CGHeroInstance *, HeroRole> heroes);
void init(); void init();
}; };

View File

@ -24,16 +24,17 @@ namespace AIPathfinding
std::vector<std::shared_ptr<IPathfindingRule>> makeRuleset( std::vector<std::shared_ptr<IPathfindingRule>> makeRuleset(
CPlayerSpecificInfoCallback * cb, CPlayerSpecificInfoCallback * cb,
Nullkiller * ai, Nullkiller * ai,
std::shared_ptr<AINodeStorage> nodeStorage) std::shared_ptr<AINodeStorage> nodeStorage,
bool allowBypassObjects)
{ {
std::vector<std::shared_ptr<IPathfindingRule>> rules = { std::vector<std::shared_ptr<IPathfindingRule>> rules = {
std::make_shared<AILayerTransitionRule>(cb, ai, nodeStorage), std::make_shared<AILayerTransitionRule>(cb, ai, nodeStorage),
std::make_shared<DestinationActionRule>(), std::make_shared<DestinationActionRule>(),
std::make_shared<AIMovementToDestinationRule>(nodeStorage), std::make_shared<AIMovementToDestinationRule>(nodeStorage),
std::make_shared<MovementCostRule>(), std::make_shared<MovementCostRule>(),
std::make_shared<AIPreviousNodeRule>(nodeStorage), std::make_shared<AIPreviousNodeRule>(nodeStorage),
std::make_shared<AIMovementAfterDestinationRule>(cb, nodeStorage) std::make_shared<AIMovementAfterDestinationRule>(cb, nodeStorage, allowBypassObjects)
}; };
return rules; return rules;
} }
@ -41,8 +42,9 @@ namespace AIPathfinding
AIPathfinderConfig::AIPathfinderConfig( AIPathfinderConfig::AIPathfinderConfig(
CPlayerSpecificInfoCallback * cb, CPlayerSpecificInfoCallback * cb,
Nullkiller * ai, Nullkiller * ai,
std::shared_ptr<AINodeStorage> nodeStorage) std::shared_ptr<AINodeStorage> nodeStorage,
:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage)), aiNodeStorage(nodeStorage) bool allowBypassObjects)
:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage, allowBypassObjects)), aiNodeStorage(nodeStorage)
{ {
options.canUseCast = true; options.canUseCast = true;
} }

View File

@ -30,7 +30,8 @@ namespace AIPathfinding
AIPathfinderConfig( AIPathfinderConfig(
CPlayerSpecificInfoCallback * cb, CPlayerSpecificInfoCallback * cb,
Nullkiller * ai, Nullkiller * ai,
std::shared_ptr<AINodeStorage> nodeStorage); std::shared_ptr<AINodeStorage> nodeStorage,
bool allowBypassObjects);
~AIPathfinderConfig(); ~AIPathfinderConfig();

View File

@ -0,0 +1,237 @@
/*
* ObjectGraph.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "ObjectGraph.h"
#include "AIPathfinderConfig.h"
#include "../../../CCallback.h"
#include "../../../lib/mapping/CMap.h"
#include "../Engine/Nullkiller.h"
namespace NKAI
{
void ObjectGraph::updateGraph(const Nullkiller * ai)
{
auto cb = ai->cb;
auto mapSize = cb->getMapSize();
std::map<const CGHeroInstance *, HeroRole> actors;
std::map<const CGHeroInstance *, const CGObjectInstance *> actorObjectMap;
auto addObjectActor = [&](const CGObjectInstance * obj)
{
auto objectActor = new CGHeroInstance(obj->cb);
CRandomGenerator rng;
auto visitablePos = obj->visitablePos();
objectActor->setOwner(ai->playerID); // lets avoid having multiple colors
objectActor->initHero(rng, static_cast<HeroTypeID>(0));
objectActor->pos = objectActor->convertFromVisitablePos(visitablePos);
objectActor->initObj(rng);
actorObjectMap[objectActor] = obj;
actors[objectActor] = obj->ID == Obj::TOWN ? HeroRole::MAIN : HeroRole::SCOUT;
addObject(obj);
};
for(auto obj : ai->memory->visitableObjs)
{
if(obj && obj->isVisitable() && obj->ID != Obj::HERO)
{
addObjectActor(obj);
}
}
for(auto town : cb->getTownsInfo())
{
addObjectActor(town);
}
PathfinderSettings ps;
ps.mainTurnDistanceLimit = 5;
ps.scoutTurnDistanceLimit = 1;
ps.allowBypassObjects = false;
ai->pathfinder->updatePaths(actors, ps);
foreach_tile_pos(cb.get(), [&](const CPlayerSpecificInfoCallback * cb, const int3 & pos)
{
auto paths = ai->pathfinder->getPathInfo(pos);
for(AIPath & path1 : paths)
{
for(AIPath & path2 : paths)
{
if(path1.targetHero == path2.targetHero)
continue;
auto obj1 = actorObjectMap[path1.targetHero];
auto obj2 = actorObjectMap[path2.targetHero];
nodes[obj1->visitablePos()].connections[obj2->visitablePos()].update(
path1.movementCost() + path2.movementCost(),
path1.getPathDanger() + path2.getPathDanger());
}
}
});
for(auto h : actors)
{
delete h.first;
}
}
void ObjectGraph::addObject(const CGObjectInstance * obj)
{
nodes[obj->visitablePos()].init(obj);
}
void ObjectGraph::connectHeroes(const Nullkiller * ai)
{
for(auto obj : ai->memory->visitableObjs)
{
if(obj && obj->ID == Obj::HERO)
{
addObject(obj);
}
}
for(auto node : nodes)
{
auto pos = node.first;
auto paths = ai->pathfinder->getPathInfo(pos);
for(AIPath & path : paths)
{
if(path.turn() == 0)
continue;
auto heroPos = path.targetHero->visitablePos();
nodes[pos].connections[heroPos].update(
path.movementCost(),
path.getPathDanger());
nodes[heroPos].connections[pos].update(
path.movementCost(),
path.getPathDanger());
}
}
}
bool GraphNodeComparer::operator()(int3 lhs, int3 rhs) const
{
return pathNodes.at(lhs).cost > pathNodes.at(rhs).cost;
}
void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkiller * ai)
{
graph = *ai->baseGraph;
graph.connectHeroes(ai);
pathNodes.clear();
GraphNodeComparer cmp(pathNodes);
GraphPathNode::TFibHeap pq(cmp);
pathNodes[targetHero->visitablePos()].cost = 0;
pq.emplace(targetHero->visitablePos());
while(!pq.empty())
{
int3 pos = pq.top();
pq.pop();
auto node = pathNodes[pos];
node.isInQueue = false;
graph.iterateConnections(pos, [&](int3 target, ObjectLink o)
{
auto & targetNode = pathNodes[target];
if(targetNode.tryUpdate(pos, node, o))
{
if(targetNode.isInQueue)
{
pq.increase(targetNode.handle);
}
else
{
targetNode.handle = pq.emplace(target);
targetNode.isInQueue = true;
}
}
});
}
}
void GraphPaths::dumpToLog() const
{
for(auto & node : pathNodes)
{
logAi->trace(
"%s -> %s: %f !%d",
node.second.previous.toString(),
node.first.toString(),
node.second.cost,
node.second.danger);
}
}
void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHeroInstance * hero, const Nullkiller * ai) const
{
auto node = pathNodes.find(tile);
if(node == pathNodes.end() || !node->second.reachable())
return;
std::vector<int3> tilesToPass;
uint64_t danger = node->second.danger;
float cost = node->second.cost;;
while(node != pathNodes.end() && node->second.cost > 1)
{
vstd::amax(danger, node->second.danger);
tilesToPass.push_back(node->first);
node = pathNodes.find(node->second.previous);
}
if(tilesToPass.empty())
return;
auto entryPaths = ai->pathfinder->getPathInfo(tilesToPass.back());
for(auto & path : entryPaths)
{
if(path.targetHero != hero)
continue;
for(auto graphTile : tilesToPass)
{
AIPathNodeInfo n;
n.coord = graphTile;
n.cost = cost;
n.turns = static_cast<ui8>(cost) + 1; // just in case lets select worst scenario
n.danger = danger;
n.targetHero = hero;
path.nodes.insert(path.nodes.begin(), n);
}
paths.push_back(path);
}
}
}

View File

@ -0,0 +1,129 @@
/*
* ObjectGraph.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "AINodeStorage.h"
#include "../AIUtility.h"
namespace NKAI
{
class Nullkiller;
struct ObjectLink
{
float cost = 100000; // some big number
uint64_t danger = 0;
void update(float newCost, uint64_t newDanger)
{
if(cost > newCost)
{
cost = newCost;
danger = newDanger;
}
}
};
struct ObjectNode
{
ObjectInstanceID objID;
bool objectExists;
std::unordered_map<int3, ObjectLink> connections;
void init(const CGObjectInstance * obj)
{
objectExists = true;
objID = obj->id;
}
};
class ObjectGraph
{
std::unordered_map<int3, ObjectNode> nodes;
public:
void updateGraph(const Nullkiller * ai);
void addObject(const CGObjectInstance * obj);
void connectHeroes(const Nullkiller * ai);
template<typename Func>
void iterateConnections(const int3 & pos, Func fn)
{
for(auto connection : nodes.at(pos).connections)
{
fn(connection.first, connection.second);
}
}
};
struct GraphPathNode;
class GraphNodeComparer
{
const std::unordered_map<int3, GraphPathNode> & pathNodes;
public:
GraphNodeComparer(const std::unordered_map<int3, GraphPathNode> & pathNodes)
:pathNodes(pathNodes)
{
}
bool operator()(int3 lhs, int3 rhs) const;
};
struct GraphPathNode
{
const float BAD_COST = 100000;
int3 previous = int3(-1);
float cost = BAD_COST;
uint64_t danger = 0;
using TFibHeap = boost::heap::fibonacci_heap<int3, boost::heap::compare<GraphNodeComparer>>;
TFibHeap::handle_type handle;
bool isInQueue = false;
bool reachable() const
{
return cost < BAD_COST;
}
bool tryUpdate(const int3 & pos, const GraphPathNode & prev, const ObjectLink & link)
{
auto newCost = prev.cost + link.cost;
if(newCost < cost)
{
previous = pos;
danger = prev.danger + link.danger;
cost = newCost;
return true;
}
return false;
}
};
class GraphPaths
{
ObjectGraph graph;
std::unordered_map<int3, GraphPathNode> pathNodes;
public:
void calculatePaths(const CGHeroInstance * targetHero, const Nullkiller * ai);
void addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHeroInstance * hero, const Nullkiller * ai) const;
void dumpToLog() const;
};
}

View File

@ -13,6 +13,7 @@
#include "../Actions/QuestAction.h" #include "../Actions/QuestAction.h"
#include "../../Goals/Invalid.h" #include "../../Goals/Invalid.h"
#include "AIPreviousNodeRule.h" #include "AIPreviousNodeRule.h"
#include "../../../../lib/pathfinder/PathfinderOptions.h"
namespace NKAI namespace NKAI
{ {
@ -20,8 +21,9 @@ namespace AIPathfinding
{ {
AIMovementAfterDestinationRule::AIMovementAfterDestinationRule( AIMovementAfterDestinationRule::AIMovementAfterDestinationRule(
CPlayerSpecificInfoCallback * cb, CPlayerSpecificInfoCallback * cb,
std::shared_ptr<AINodeStorage> nodeStorage) std::shared_ptr<AINodeStorage> nodeStorage,
:cb(cb), nodeStorage(nodeStorage) bool allowBypassObjects)
:cb(cb), nodeStorage(nodeStorage), allowBypassObjects(allowBypassObjects)
{ {
} }
@ -47,6 +49,9 @@ namespace AIPathfinding
return; return;
} }
if(!allowBypassObjects)
return;
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
logAi->trace( logAi->trace(
"Movement from tile %s is blocked. Try to bypass. Action: %d, blocker: %d", "Movement from tile %s is blocked. Try to bypass. Action: %d, blocker: %d",

View File

@ -25,9 +25,13 @@ namespace AIPathfinding
private: private:
CPlayerSpecificInfoCallback * cb; CPlayerSpecificInfoCallback * cb;
std::shared_ptr<AINodeStorage> nodeStorage; std::shared_ptr<AINodeStorage> nodeStorage;
bool allowBypassObjects;
public: public:
AIMovementAfterDestinationRule(CPlayerSpecificInfoCallback * cb, std::shared_ptr<AINodeStorage> nodeStorage); AIMovementAfterDestinationRule(
CPlayerSpecificInfoCallback * cb,
std::shared_ptr<AINodeStorage> nodeStorage,
bool allowBypassObjects);
virtual void process( virtual void process(
const PathNodeInfo & source, const PathNodeInfo & source,