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:
parent
a373ec6743
commit
376a17409f
@ -185,8 +185,8 @@ void foreach_tile_pos(const Func & foo)
|
||||
}
|
||||
}
|
||||
|
||||
template<class Func>
|
||||
void foreach_tile_pos(CCallback * cbp, const Func & foo) // avoid costly retrieval of thread-specific pointer
|
||||
template<class Func, class TCallback>
|
||||
void foreach_tile_pos(TCallback * cbp, const Func & foo) // avoid costly retrieval of thread-specific pointer
|
||||
{
|
||||
int3 mapSize = cbp->getMapSize();
|
||||
for(int z = 0; z < mapSize.z; z++)
|
||||
|
@ -75,8 +75,7 @@ void DangerHitMapAnalyzer::updateHitMap()
|
||||
|
||||
PathfinderSettings ps;
|
||||
|
||||
ps.mainTurnDistanceLimit = 10;
|
||||
ps.scoutTurnDistanceLimit = 10;
|
||||
ps.scoutTurnDistanceLimit = ps.mainTurnDistanceLimit = MAIN_TURN_DISTANCE_LIMIT;
|
||||
ps.useHeroChain = false;
|
||||
|
||||
ai->pathfinder->updatePaths(pair.second, ps);
|
||||
@ -160,9 +159,6 @@ void DangerHitMapAnalyzer::calculateTileOwners()
|
||||
|
||||
std::map<const CGHeroInstance *, HeroRole> townHeroes;
|
||||
std::map<const CGHeroInstance *, const CGTownInstance *> heroTownMap;
|
||||
PathfinderSettings pathfinderSettings;
|
||||
|
||||
pathfinderSettings.mainTurnDistanceLimit = 5;
|
||||
|
||||
auto addTownHero = [&](const CGTownInstance * town)
|
||||
{
|
||||
@ -192,7 +188,10 @@ void DangerHitMapAnalyzer::calculateTileOwners()
|
||||
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)
|
||||
{
|
||||
@ -239,6 +238,11 @@ void DangerHitMapAnalyzer::calculateTileOwners()
|
||||
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
|
||||
|
@ -244,7 +244,7 @@ void ObjectClusterizer::clusterize()
|
||||
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(), true);
|
||||
|
||||
if(paths.empty())
|
||||
{
|
||||
|
@ -179,7 +179,7 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const
|
||||
|
||||
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::shared_ptr<ExecuteHeroChain> closestWay;
|
||||
|
||||
@ -210,7 +210,7 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const
|
||||
{
|
||||
captureObjects(ai->nullkiller->objectClusterizer->getNearbyObjects());
|
||||
|
||||
if(tasks.empty() || ai->nullkiller->getScanDepth() != ScanDepth::SMALL)
|
||||
if(tasks.empty())
|
||||
captureObjects(ai->nullkiller->objectClusterizer->getFarObjects());
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ Goals::TGoalVec ClusterBehavior::decompose() const
|
||||
Goals::TGoalVec ClusterBehavior::decomposeCluster(std::shared_ptr<ObjectCluster> cluster) const
|
||||
{
|
||||
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();
|
||||
std::vector<AIPath> blockerPaths;
|
||||
|
||||
|
@ -14,6 +14,7 @@ set(Nullkiller_SRCS
|
||||
Pathfinding/Rules/AIMovementAfterDestinationRule.cpp
|
||||
Pathfinding/Rules/AIMovementToDestinationRule.cpp
|
||||
Pathfinding/Rules/AIPreviousNodeRule.cpp
|
||||
Pathfinding/ObjectGraph.cpp
|
||||
AIUtility.cpp
|
||||
Analyzers/ArmyManager.cpp
|
||||
Analyzers/HeroManager.cpp
|
||||
@ -78,6 +79,7 @@ set(Nullkiller_HEADERS
|
||||
Pathfinding/Rules/AIMovementAfterDestinationRule.h
|
||||
Pathfinding/Rules/AIMovementToDestinationRule.h
|
||||
Pathfinding/Rules/AIPreviousNodeRule.h
|
||||
Pathfinding/ObjectGraph.h
|
||||
AIUtility.h
|
||||
Analyzers/ArmyManager.h
|
||||
Analyzers/HeroManager.h
|
||||
|
@ -27,6 +27,9 @@ namespace NKAI
|
||||
|
||||
using namespace Goals;
|
||||
|
||||
// while we play vcmieagles graph can be shared
|
||||
std::unique_ptr<ObjectGraph> Nullkiller::baseGraph;
|
||||
|
||||
Nullkiller::Nullkiller()
|
||||
:activeHero(nullptr), scanDepth(ScanDepth::MAIN_FULL), useHeroChain(true)
|
||||
{
|
||||
@ -119,6 +122,12 @@ void Nullkiller::resetAiState()
|
||||
lockedHeroes.clear();
|
||||
dangerHitMap->reset();
|
||||
useHeroChain = true;
|
||||
|
||||
if(!baseGraph)
|
||||
{
|
||||
baseGraph = std::make_unique<ObjectGraph>();
|
||||
baseGraph->updateGraph(this);
|
||||
}
|
||||
}
|
||||
|
||||
void Nullkiller::updateAiState(int pass, bool fast)
|
||||
@ -173,6 +182,7 @@ void Nullkiller::updateAiState(int pass, bool fast)
|
||||
boost::this_thread::interruption_point();
|
||||
|
||||
pathfinder->updatePaths(activeHeroes, cfg);
|
||||
pathfinder->updateGraphs(activeHeroes);
|
||||
|
||||
boost::this_thread::interruption_point();
|
||||
|
||||
|
@ -25,7 +25,6 @@ namespace NKAI
|
||||
{
|
||||
|
||||
const float MIN_PRIORITY = 0.01f;
|
||||
const float SMALL_SCAN_MIN_PRIORITY = 0.4f;
|
||||
|
||||
enum class HeroLockedReason
|
||||
{
|
||||
@ -38,15 +37,6 @@ enum class HeroLockedReason
|
||||
HERO_CHAIN = 3
|
||||
};
|
||||
|
||||
enum class ScanDepth
|
||||
{
|
||||
MAIN_FULL = 0,
|
||||
|
||||
SMALL = 1,
|
||||
|
||||
ALL_FULL = 2
|
||||
};
|
||||
|
||||
class Nullkiller
|
||||
{
|
||||
private:
|
||||
@ -54,11 +44,12 @@ private:
|
||||
int3 targetTile;
|
||||
ObjectInstanceID targetObject;
|
||||
std::map<const CGHeroInstance *, HeroLockedReason> lockedHeroes;
|
||||
ScanDepth scanDepth;
|
||||
TResources lockedResources;
|
||||
bool useHeroChain;
|
||||
|
||||
public:
|
||||
static std::unique_ptr<ObjectGraph> baseGraph;
|
||||
|
||||
std::unique_ptr<DangerHitMapAnalyzer> dangerHitMap;
|
||||
std::unique_ptr<BuildAnalyzer> buildAnalyzer;
|
||||
std::unique_ptr<ObjectClusterizer> objectClusterizer;
|
||||
@ -94,7 +85,6 @@ public:
|
||||
int32_t getFreeGold() const { return getFreeResources()[EGameResID::GOLD]; }
|
||||
void lockResources(const TResources & res);
|
||||
const TResources & getLockedResources() const { return lockedResources; }
|
||||
ScanDepth getScanDepth() const { return scanDepth; }
|
||||
|
||||
private:
|
||||
void resetAiState();
|
||||
|
@ -26,13 +26,9 @@ namespace NKAI
|
||||
{
|
||||
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 THREAD_COUNT = 8;
|
||||
const int CHAIN_MAX_DEPTH = 4;
|
||||
|
@ -33,7 +33,7 @@ bool AIPathfinder::isTileAccessible(const HeroPtr & hero, const int3 & tile) con
|
||||
|| 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);
|
||||
|
||||
@ -42,7 +42,19 @@ std::vector<AIPath> AIPathfinder::getPathInfo(const int3 & tile) const
|
||||
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)
|
||||
@ -71,7 +83,7 @@ void AIPathfinder::updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes
|
||||
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++);
|
||||
cb->calculatePaths(config);
|
||||
@ -112,4 +124,33 @@ void AIPathfinder::updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "AINodeStorage.h"
|
||||
#include "ObjectGraph.h"
|
||||
#include "../AIUtility.h"
|
||||
|
||||
namespace NKAI
|
||||
@ -23,11 +24,13 @@ struct PathfinderSettings
|
||||
bool useHeroChain;
|
||||
uint8_t scoutTurnDistanceLimit;
|
||||
uint8_t mainTurnDistanceLimit;
|
||||
bool allowBypassObjects;
|
||||
|
||||
PathfinderSettings()
|
||||
:useHeroChain(false),
|
||||
scoutTurnDistanceLimit(255),
|
||||
mainTurnDistanceLimit(255)
|
||||
mainTurnDistanceLimit(255),
|
||||
allowBypassObjects(true)
|
||||
{ }
|
||||
};
|
||||
|
||||
@ -37,12 +40,14 @@ private:
|
||||
std::shared_ptr<AINodeStorage> storage;
|
||||
CPlayerSpecificInfoCallback * cb;
|
||||
Nullkiller * ai;
|
||||
std::map<ObjectInstanceID, GraphPaths> heroGraphs;
|
||||
|
||||
public:
|
||||
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;
|
||||
void updatePaths(std::map<const CGHeroInstance *, HeroRole> heroes, PathfinderSettings pathfinderSettings);
|
||||
void updateGraphs(std::map<const CGHeroInstance *, HeroRole> heroes);
|
||||
void init();
|
||||
};
|
||||
|
||||
|
@ -24,16 +24,17 @@ namespace AIPathfinding
|
||||
std::vector<std::shared_ptr<IPathfindingRule>> makeRuleset(
|
||||
CPlayerSpecificInfoCallback * cb,
|
||||
Nullkiller * ai,
|
||||
std::shared_ptr<AINodeStorage> nodeStorage)
|
||||
std::shared_ptr<AINodeStorage> nodeStorage,
|
||||
bool allowBypassObjects)
|
||||
{
|
||||
std::vector<std::shared_ptr<IPathfindingRule>> rules = {
|
||||
std::make_shared<AILayerTransitionRule>(cb, ai, nodeStorage),
|
||||
std::make_shared<DestinationActionRule>(),
|
||||
std::make_shared<AIMovementToDestinationRule>(nodeStorage),
|
||||
std::make_shared<MovementCostRule>(),
|
||||
std::make_shared<AIPreviousNodeRule>(nodeStorage),
|
||||
std::make_shared<AIMovementAfterDestinationRule>(cb, nodeStorage)
|
||||
};
|
||||
std::vector<std::shared_ptr<IPathfindingRule>> rules = {
|
||||
std::make_shared<AILayerTransitionRule>(cb, ai, nodeStorage),
|
||||
std::make_shared<DestinationActionRule>(),
|
||||
std::make_shared<AIMovementToDestinationRule>(nodeStorage),
|
||||
std::make_shared<MovementCostRule>(),
|
||||
std::make_shared<AIPreviousNodeRule>(nodeStorage),
|
||||
std::make_shared<AIMovementAfterDestinationRule>(cb, nodeStorage, allowBypassObjects)
|
||||
};
|
||||
|
||||
return rules;
|
||||
}
|
||||
@ -41,8 +42,9 @@ namespace AIPathfinding
|
||||
AIPathfinderConfig::AIPathfinderConfig(
|
||||
CPlayerSpecificInfoCallback * cb,
|
||||
Nullkiller * ai,
|
||||
std::shared_ptr<AINodeStorage> nodeStorage)
|
||||
:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage)), aiNodeStorage(nodeStorage)
|
||||
std::shared_ptr<AINodeStorage> nodeStorage,
|
||||
bool allowBypassObjects)
|
||||
:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage, allowBypassObjects)), aiNodeStorage(nodeStorage)
|
||||
{
|
||||
options.canUseCast = true;
|
||||
}
|
||||
|
@ -30,7 +30,8 @@ namespace AIPathfinding
|
||||
AIPathfinderConfig(
|
||||
CPlayerSpecificInfoCallback * cb,
|
||||
Nullkiller * ai,
|
||||
std::shared_ptr<AINodeStorage> nodeStorage);
|
||||
std::shared_ptr<AINodeStorage> nodeStorage,
|
||||
bool allowBypassObjects);
|
||||
|
||||
~AIPathfinderConfig();
|
||||
|
||||
|
237
AI/Nullkiller/Pathfinding/ObjectGraph.cpp
Normal file
237
AI/Nullkiller/Pathfinding/ObjectGraph.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
129
AI/Nullkiller/Pathfinding/ObjectGraph.h
Normal file
129
AI/Nullkiller/Pathfinding/ObjectGraph.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
@ -13,6 +13,7 @@
|
||||
#include "../Actions/QuestAction.h"
|
||||
#include "../../Goals/Invalid.h"
|
||||
#include "AIPreviousNodeRule.h"
|
||||
#include "../../../../lib/pathfinder/PathfinderOptions.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
@ -20,8 +21,9 @@ namespace AIPathfinding
|
||||
{
|
||||
AIMovementAfterDestinationRule::AIMovementAfterDestinationRule(
|
||||
CPlayerSpecificInfoCallback * cb,
|
||||
std::shared_ptr<AINodeStorage> nodeStorage)
|
||||
:cb(cb), nodeStorage(nodeStorage)
|
||||
std::shared_ptr<AINodeStorage> nodeStorage,
|
||||
bool allowBypassObjects)
|
||||
:cb(cb), nodeStorage(nodeStorage), allowBypassObjects(allowBypassObjects)
|
||||
{
|
||||
}
|
||||
|
||||
@ -47,6 +49,9 @@ namespace AIPathfinding
|
||||
return;
|
||||
}
|
||||
|
||||
if(!allowBypassObjects)
|
||||
return;
|
||||
|
||||
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
"Movement from tile %s is blocked. Try to bypass. Action: %d, blocker: %d",
|
||||
|
@ -25,9 +25,13 @@ namespace AIPathfinding
|
||||
private:
|
||||
CPlayerSpecificInfoCallback * cb;
|
||||
std::shared_ptr<AINodeStorage> nodeStorage;
|
||||
bool allowBypassObjects;
|
||||
|
||||
public:
|
||||
AIMovementAfterDestinationRule(CPlayerSpecificInfoCallback * cb, std::shared_ptr<AINodeStorage> nodeStorage);
|
||||
AIMovementAfterDestinationRule(
|
||||
CPlayerSpecificInfoCallback * cb,
|
||||
std::shared_ptr<AINodeStorage> nodeStorage,
|
||||
bool allowBypassObjects);
|
||||
|
||||
virtual void process(
|
||||
const PathNodeInfo & source,
|
||||
|
Loading…
Reference in New Issue
Block a user