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>
|
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++)
|
||||||
|
@ -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
|
||||||
|
@ -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())
|
||||||
{
|
{
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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;
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
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 "../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",
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user