1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-06-23 00:28:08 +02:00

NKAI: remove 5th dimension

This commit is contained in:
Andrii Danylchenko
2024-03-23 11:44:15 +02:00
parent e66ceff154
commit 017fb204a1
10 changed files with 387 additions and 274 deletions

View File

@ -24,7 +24,8 @@
namespace NKAI namespace NKAI
{ {
std::shared_ptr<boost::multi_array<AIPathNode, 5>> AISharedStorage::shared; std::shared_ptr<boost::multi_array<AIPathNode, 4>> AISharedStorage::shared;
uint64_t AISharedStorage::version = 0;
boost::mutex AISharedStorage::locker; boost::mutex AISharedStorage::locker;
std::set<int3> commitedTiles; std::set<int3> commitedTiles;
std::set<int3> commitedTilesInitial; std::set<int3> commitedTilesInitial;
@ -40,11 +41,24 @@ const bool DO_NOT_SAVE_TO_COMMITED_TILES = false;
AISharedStorage::AISharedStorage(int3 sizes) AISharedStorage::AISharedStorage(int3 sizes)
{ {
if(!shared){ if(!shared){
shared.reset(new boost::multi_array<AIPathNode, 5>( shared.reset(new boost::multi_array<AIPathNode, 4>(
boost::extents[EPathfindingLayer::NUM_LAYERS][sizes.z][sizes.x][sizes.y][AIPathfinding::NUM_CHAINS])); boost::extents[sizes.z][sizes.x][sizes.y][AIPathfinding::NUM_CHAINS]));
}
nodes = shared; nodes = shared;
foreach_tile_pos([&](const int3 & pos)
{
for(auto i = 0; i < AIPathfinding::NUM_CHAINS; i++)
{
auto & node = get(pos)[i];
node.version = -1;
node.coord = pos;
}
});
}
else
nodes = shared;
} }
AISharedStorage::~AISharedStorage() AISharedStorage::~AISharedStorage()
@ -80,6 +94,9 @@ void AIPathNode::addSpecialAction(std::shared_ptr<const SpecialAction> action)
AINodeStorage::AINodeStorage(const Nullkiller * ai, const int3 & Sizes) AINodeStorage::AINodeStorage(const Nullkiller * ai, const int3 & Sizes)
: sizes(Sizes), ai(ai), cb(ai->cb.get()), nodes(Sizes) : sizes(Sizes), ai(ai), cb(ai->cb.get()), nodes(Sizes)
{ {
accesibility = std::make_unique<boost::multi_array<EPathAccessibility, 4>>(
boost::extents[sizes.z][sizes.x][sizes.y][EPathfindingLayer::NUM_LAYERS]);
dangerEvaluator.reset(new FuzzyHelper(ai)); dangerEvaluator.reset(new FuzzyHelper(ai));
} }
@ -90,6 +107,8 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
if(heroChainPass) if(heroChainPass)
return; return;
AISharedStorage::version++;
//TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline //TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline
const PlayerColor fowPlayer = ai->playerID; const PlayerColor fowPlayer = ai->playerID;
const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(fowPlayer)->fogOfWarMap; const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(fowPlayer)->fogOfWarMap;
@ -152,9 +171,9 @@ std::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
{ {
int bucketIndex = ((uintptr_t)actor) % AIPathfinding::BUCKET_COUNT; int bucketIndex = ((uintptr_t)actor) % AIPathfinding::BUCKET_COUNT;
int bucketOffset = bucketIndex * AIPathfinding::BUCKET_SIZE; int bucketOffset = bucketIndex * AIPathfinding::BUCKET_SIZE;
auto chains = nodes.get(pos, layer); auto chains = nodes.get(pos);
if(chains[0].blocked()) if(blocked(pos, layer))
{ {
return std::nullopt; return std::nullopt;
} }
@ -163,15 +182,17 @@ std::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
{ {
AIPathNode & node = chains[i + bucketOffset]; AIPathNode & node = chains[i + bucketOffset];
if(node.actor == actor) if(node.version != AISharedStorage::version)
{ {
node.reset(layer, getAccessibility(pos, layer));
node.version = AISharedStorage::version;
node.actor = actor;
return &node; return &node;
} }
if(!node.actor) if(node.actor == actor && node.layer == layer)
{ {
node.actor = actor;
return &node; return &node;
} }
} }
@ -226,21 +247,6 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
return initialNodes; return initialNodes;
} }
void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, EPathAccessibility accessibility)
{
for(AIPathNode & heroNode : nodes.get(coord, layer))
{
heroNode.actor = nullptr;
heroNode.danger = 0;
heroNode.manaCost = 0;
heroNode.specialAction.reset();
heroNode.armyLoss = 0;
heroNode.chainOther = nullptr;
heroNode.dayFlags = DayFlags::NONE;
heroNode.update(coord, layer, accessibility);
}
}
void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInfo & source)
{ {
const AIPathNode * srcNode = getAINode(source.node); const AIPathNode * srcNode = getAINode(source.node);
@ -306,30 +312,31 @@ void AINodeStorage::commit(
} }
} }
std::vector<CGPathNode *> AINodeStorage::calculateNeighbours( void AINodeStorage::calculateNeighbours(
std::vector<CGPathNode *> & result,
const PathNodeInfo & source, const PathNodeInfo & source,
EPathfindingLayer layer,
const PathfinderConfig * pathfinderConfig, const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) const CPathfinderHelper * pathfinderHelper)
{ {
std::vector<CGPathNode *> neighbours; std::vector<int3> accessibleNeighbourTiles;
neighbours.reserve(16);
result.clear();
accessibleNeighbourTiles.reserve(8);
pathfinderHelper->calculateNeighbourTiles(accessibleNeighbourTiles, source);
const AIPathNode * srcNode = getAINode(source.node); const AIPathNode * srcNode = getAINode(source.node);
auto accessibleNeighbourTiles = pathfinderHelper->getNeighbourTiles(source);
for(auto & neighbour : accessibleNeighbourTiles) for(auto & neighbour : accessibleNeighbourTiles)
{ {
for(EPathfindingLayer i = EPathfindingLayer::LAND; i < EPathfindingLayer::NUM_LAYERS; i.advance(1)) auto nextNode = getOrCreateNode(neighbour, layer, srcNode->actor);
{
auto nextNode = getOrCreateNode(neighbour, i, srcNode->actor);
if(!nextNode || nextNode.value()->accessible == EPathAccessibility::NOT_SET) if(!nextNode || nextNode.value()->accessible == EPathAccessibility::NOT_SET)
continue; continue;
neighbours.push_back(nextNode.value()); result.push_back(nextNode.value());
}
} }
return neighbours;
} }
constexpr std::array phisycalLayers = {EPathfindingLayer::LAND, EPathfindingLayer::SAIL}; constexpr std::array phisycalLayers = {EPathfindingLayer::LAND, EPathfindingLayer::SAIL};
@ -346,19 +353,16 @@ bool AINodeStorage::increaseHeroChainTurnLimit()
{ {
foreach_tile_pos([&](const int3 & pos) foreach_tile_pos([&](const int3 & pos)
{ {
auto chains = nodes.get(pos, layer); iterateValidNodesUntil(pos, layer, [&](AIPathNode & node)
if(!chains[0].blocked())
{
for(AIPathNode & node : chains)
{ {
if(node.turns <= heroChainTurn && node.action != EPathNodeAction::UNKNOWN) if(node.turns <= heroChainTurn && node.action != EPathNodeAction::UNKNOWN)
{ {
commitedTiles.insert(pos); commitedTiles.insert(pos);
break; return true;
} }
}
} return false;
});
}); });
} }
@ -374,22 +378,17 @@ bool AINodeStorage::calculateHeroChainFinal()
{ {
foreach_tile_pos([&](const int3 & pos) foreach_tile_pos([&](const int3 & pos)
{ {
auto chains = nodes.get(pos, layer); iterateValidNodes(pos, layer, [&](AIPathNode & node)
if(!chains[0].blocked())
{
for(AIPathNode & node : chains)
{ {
if(node.turns > heroChainTurn if(node.turns > heroChainTurn
&& !node.locked && !node.locked
&& node.action != EPathNodeAction::UNKNOWN && node.action != EPathNodeAction::UNKNOWN
&& node.actor->actorExchangeCount > 1 && node.actor->actorExchangeCount > 1
&& !hasBetterChain(&node, &node, chains)) && !hasBetterChain(&node, node))
{ {
heroChain.push_back(&node); heroChain.push_back(&node);
} }
} });
}
}); });
} }
@ -443,21 +442,19 @@ public:
for(auto layer : phisycalLayers) for(auto layer : phisycalLayers)
{ {
auto chains = nodes.get(pos, layer); existingChains.clear();
// fast cut inactive nodes storage.iterateValidNodes(pos, layer, [this](AIPathNode & node)
if(chains[0].blocked()) {
if(node.turns <= heroChainTurn && node.action != EPathNodeAction::UNKNOWN)
existingChains.push_back(&node);
});
if(existingChains.empty())
continue; continue;
existingChains.clear();
newChains.clear(); newChains.clear();
for(AIPathNode & node : chains)
{
if(node.turns <= heroChainTurn && node.action != EPathNodeAction::UNKNOWN)
existingChains.push_back(&node);
}
std::shuffle(existingChains.begin(), existingChains.end(), randomEngine); std::shuffle(existingChains.begin(), existingChains.end(), randomEngine);
for(AIPathNode * node : existingChains) for(AIPathNode * node : existingChains)
@ -617,9 +614,8 @@ void HeroChainCalculationTask::cleanupInefectiveChains(std::vector<ExchangeCandi
vstd::erase_if(result, [&](const ExchangeCandidate & chainInfo) -> bool vstd::erase_if(result, [&](const ExchangeCandidate & chainInfo) -> bool
{ {
auto pos = chainInfo.coord; auto pos = chainInfo.coord;
auto chains = nodes.get(pos, EPathfindingLayer::LAND); auto isNotEffective = storage.hasBetterChain(chainInfo.carrierParent, chainInfo)
auto isNotEffective = storage.hasBetterChain(chainInfo.carrierParent, &chainInfo, chains) || storage.hasBetterChain(chainInfo.carrierParent, chainInfo, result);
|| storage.hasBetterChain(chainInfo.carrierParent, &chainInfo, result);
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
if(isNotEffective) if(isNotEffective)
@ -645,7 +641,7 @@ void HeroChainCalculationTask::calculateHeroChain(
{ {
for(AIPathNode * node : variants) for(AIPathNode * node : variants)
{ {
if(node == srcNode || !node->actor) if(node == srcNode || !node->actor || node->version != AISharedStorage::version)
continue; continue;
if((node->actor->chainMask & chainMask) == 0 && (srcNode->actor->chainMask & chainMask) == 0) if((node->actor->chainMask & chainMask) == 0 && (srcNode->actor->chainMask & chainMask) == 0)
@ -1195,95 +1191,116 @@ void AINodeStorage::calculateTownPortalTeleportations(std::vector<CGPathNode *>
bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
{ {
auto pos = destination.coord; auto candidateNode = getAINode(destination.node);
auto chains = nodes.get(pos, EPathfindingLayer::LAND);
return hasBetterChain(source.node, getAINode(destination.node), chains); return hasBetterChain(source.node, *candidateNode);
}
bool AINodeStorage::hasBetterChain(
const CGPathNode * source,
const AIPathNode & candidateNode) const
{
return iterateValidNodesUntil(
candidateNode.coord,
candidateNode.layer,
[this, &source, candidateNode](const AIPathNode & node) -> bool
{
return isOtherChainBetter(source, candidateNode, node);
});
} }
template<class NodeRange> template<class NodeRange>
bool AINodeStorage::hasBetterChain( bool AINodeStorage::hasBetterChain(
const CGPathNode * source, const CGPathNode * source,
const AIPathNode * candidateNode, const AIPathNode & candidateNode,
const NodeRange & chains) const const NodeRange & nodes) const
{ {
auto candidateActor = candidateNode->actor; for(const AIPathNode & node : nodes)
for(const AIPathNode & node : chains)
{ {
auto sameNode = node.actor == candidateNode->actor; if(isOtherChainBetter(source, candidateNode, node))
return true;
}
if(sameNode || node.action == EPathNodeAction::UNKNOWN || !node.actor || !node.actor->hero) return false;
{ }
continue;
}
if(node.danger <= candidateNode->danger && candidateNode->actor == node.actor->battleActor) bool AINodeStorage::isOtherChainBetter(
{ const CGPathNode * source,
if(node.getCost() < candidateNode->getCost()) const AIPathNode & candidateNode,
{ const AIPathNode & other) const
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2 {
logAi->trace( auto sameNode = other.actor == candidateNode.actor;
"Block ineficient battle move %s->%s, hero: %s[%X], army %lld, mp diff: %i",
source->coord.toString(),
candidateNode->coord.toString(),
candidateNode->actor->hero->getNameTranslated(),
candidateNode->actor->chainMask,
candidateNode->actor->armyValue,
node.moveRemains - candidateNode->moveRemains);
#endif
return true;
}
}
if(candidateActor->chainMask != node.actor->chainMask && heroChainPass != EHeroChainPass::FINAL) if(sameNode || other.action == EPathNodeAction::UNKNOWN || !other.actor || !other.actor->hero)
continue; {
return false;
}
auto nodeActor = node.actor; if(other.danger <= candidateNode.danger && candidateNode.actor == other.actor->battleActor)
auto nodeArmyValue = nodeActor->armyValue - node.armyLoss; {
auto candidateArmyValue = candidateActor->armyValue - candidateNode->armyLoss; if(other.getCost() < candidateNode.getCost())
if(nodeArmyValue > candidateArmyValue
&& node.getCost() <= candidateNode->getCost())
{ {
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
logAi->trace( logAi->trace(
"Block ineficient move because of stronger army %s->%s, hero: %s[%X], army %lld, mp diff: %i", "Block ineficient battle move %s->%s, hero: %s[%X], army %lld, mp diff: %i",
source->coord.toString(), source->coord.toString(),
candidateNode->coord.toString(), candidateNode.coord.toString(),
candidateNode->actor->hero->getNameTranslated(), candidateNode.actor->hero->getNameTranslated(),
candidateNode->actor->chainMask, candidateNode.actor->chainMask,
candidateNode->actor->armyValue, candidateNode.actor->armyValue,
node.moveRemains - candidateNode->moveRemains); other.moveRemains - candidateNode.moveRemains);
#endif #endif
return true; return true;
} }
}
if(heroChainPass == EHeroChainPass::FINAL) if(candidateNode.actor->chainMask != other.actor->chainMask && heroChainPass != EHeroChainPass::FINAL)
return false;
auto nodeActor = other.actor;
auto nodeArmyValue = nodeActor->armyValue - other.armyLoss;
auto candidateArmyValue = candidateNode.actor->armyValue - candidateNode.armyLoss;
if(nodeArmyValue > candidateArmyValue
&& other.getCost() <= candidateNode.getCost())
{
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
logAi->trace(
"Block ineficient move because of stronger army %s->%s, hero: %s[%X], army %lld, mp diff: %i",
source->coord.toString(),
candidateNode.coord.toString(),
candidateNode.actor->hero->getNameTranslated(),
candidateNode.actor->chainMask,
candidateNode.actor->armyValue,
other.moveRemains - candidateNode.moveRemains);
#endif
return true;
}
if(heroChainPass == EHeroChainPass::FINAL)
{
if(nodeArmyValue == candidateArmyValue
&& nodeActor->heroFightingStrength >= candidateNode.actor->heroFightingStrength
&& other.getCost() <= candidateNode.getCost())
{ {
if(nodeArmyValue == candidateArmyValue if(vstd::isAlmostEqual(nodeActor->heroFightingStrength, candidateNode.actor->heroFightingStrength)
&& nodeActor->heroFightingStrength >= candidateActor->heroFightingStrength && vstd::isAlmostEqual(other.getCost(), candidateNode.getCost())
&& node.getCost() <= candidateNode->getCost()) && &other < &candidateNode)
{ {
if(vstd::isAlmostEqual(nodeActor->heroFightingStrength, candidateActor->heroFightingStrength) return false;
&& vstd::isAlmostEqual(node.getCost(), candidateNode->getCost()) }
&& &node < candidateNode)
{
continue;
}
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2 #if NKAI_PATHFINDER_TRACE_LEVEL >= 2
logAi->trace( logAi->trace(
"Block ineficient move because of stronger hero %s->%s, hero: %s[%X], army %lld, mp diff: %i", "Block ineficient move because of stronger hero %s->%s, hero: %s[%X], army %lld, mp diff: %i",
source->coord.toString(), source->coord.toString(),
candidateNode->coord.toString(), candidateNode.coord.toString(),
candidateNode->actor->hero->getNameTranslated(), candidateNode.actor->hero->getNameTranslated(),
candidateNode->actor->chainMask, candidateNode.actor->chainMask,
candidateNode->actor->armyValue, candidateNode.actor->armyValue,
node.moveRemains - candidateNode->moveRemains); other.moveRemains - candidateNode.moveRemains);
#endif #endif
return true; return true;
}
} }
} }
@ -1292,12 +1309,15 @@ bool AINodeStorage::hasBetterChain(
bool AINodeStorage::isTileAccessible(const HeroPtr & hero, const int3 & pos, const EPathfindingLayer layer) const bool AINodeStorage::isTileAccessible(const HeroPtr & hero, const int3 & pos, const EPathfindingLayer layer) const
{ {
auto chains = nodes.get(pos, layer); auto chains = nodes.get(pos);
for(const AIPathNode & node : chains) for(const AIPathNode & node : chains)
{ {
if(node.action != EPathNodeAction::UNKNOWN if(node.version == AISharedStorage::version
&& node.actor && node.actor->hero == hero.h) && node.layer == layer
&& node.action != EPathNodeAction::UNKNOWN
&& node.actor
&& node.actor->hero == hero.h)
{ {
return true; return true;
} }
@ -1312,11 +1332,16 @@ std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand)
paths.reserve(AIPathfinding::NUM_CHAINS / 4); paths.reserve(AIPathfinding::NUM_CHAINS / 4);
auto chains = nodes.get(pos, isOnLand ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL); auto layer = isOnLand ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL;
auto chains = nodes.get(pos);
for(const AIPathNode & node : chains) for(const AIPathNode & node : chains)
{ {
if(node.action == EPathNodeAction::UNKNOWN || !node.actor || !node.actor->hero) if(node.version != AISharedStorage::version
|| node.layer != layer
|| node.action == EPathNodeAction::UNKNOWN
|| !node.actor
|| !node.actor->hero)
{ {
continue; continue;
} }
@ -1349,33 +1374,29 @@ void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path, int pa
if(node->chainOther) if(node->chainOther)
fillChainInfo(node->chainOther, path, parentIndex); fillChainInfo(node->chainOther, path, parentIndex);
//if(node->actor->hero->visitablePos() != node->coord) AIPathNodeInfo pathNode;
pathNode.cost = node->getCost();
pathNode.targetHero = node->actor->hero;
pathNode.chainMask = node->actor->chainMask;
pathNode.specialAction = node->specialAction;
pathNode.turns = node->turns;
pathNode.danger = node->danger;
pathNode.coord = node->coord;
pathNode.parentIndex = parentIndex;
pathNode.actionIsBlocked = false;
pathNode.layer = node->layer;
if(pathNode.specialAction)
{ {
AIPathNodeInfo pathNode; auto targetNode =node->theNodeBefore ? getAINode(node->theNodeBefore) : node;
pathNode.cost = node->getCost(); pathNode.actionIsBlocked = !pathNode.specialAction->canAct(targetNode);
pathNode.targetHero = node->actor->hero;
pathNode.chainMask = node->actor->chainMask;
pathNode.specialAction = node->specialAction;
pathNode.turns = node->turns;
pathNode.danger = node->danger;
pathNode.coord = node->coord;
pathNode.parentIndex = parentIndex;
pathNode.actionIsBlocked = false;
pathNode.layer = node->layer;
if(pathNode.specialAction)
{
auto targetNode =node->theNodeBefore ? getAINode(node->theNodeBefore) : node;
pathNode.actionIsBlocked = !pathNode.specialAction->canAct(targetNode);
}
parentIndex = path.nodes.size();
path.nodes.push_back(pathNode);
} }
parentIndex = path.nodes.size();
path.nodes.push_back(pathNode);
node = getAINode(node->theNodeBefore); node = getAINode(node->theNodeBefore);
} }
} }

View File

@ -27,8 +27,8 @@ namespace NKAI
{ {
namespace AIPathfinding namespace AIPathfinding
{ {
const int BUCKET_COUNT = 3; const int BUCKET_COUNT = 5;
const int BUCKET_SIZE = 5; const int BUCKET_SIZE = 6;
const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE; const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE;
const int CHAIN_MAX_DEPTH = 4; const int CHAIN_MAX_DEPTH = 4;
} }
@ -49,15 +49,24 @@ struct AIPathNode : public CGPathNode
const AIPathNode * chainOther; const AIPathNode * chainOther;
std::shared_ptr<const SpecialAction> specialAction; std::shared_ptr<const SpecialAction> specialAction;
const ChainActor * actor; const ChainActor * actor;
uint64_t version;
STRONG_INLINE
bool blocked() const
{
return accessible == EPathAccessibility::NOT_SET
|| accessible == EPathAccessibility::BLOCKED;
}
void addSpecialAction(std::shared_ptr<const SpecialAction> action); void addSpecialAction(std::shared_ptr<const SpecialAction> action);
inline void reset(EPathfindingLayer layer, EPathAccessibility accessibility)
{
CGPathNode::reset();
actor = nullptr;
danger = 0;
manaCost = 0;
specialAction.reset();
armyLoss = 0;
chainOther = nullptr;
dayFlags = DayFlags::NONE;
this->layer = layer;
accessible = accessibility;
}
}; };
struct AIPathNodeInfo struct AIPathNodeInfo
@ -133,21 +142,21 @@ enum EHeroChainPass
class AISharedStorage class AISharedStorage
{ {
// 1 - layer (air, water, land) // 1-3 - position on map[z][x][y]
// 2-4 - position on map[z][x][y] // 4 - chain + layer (normal, battle, spellcast and combinations, water, air)
// 5 - chain (normal, battle, spellcast and combinations) static std::shared_ptr<boost::multi_array<AIPathNode, 4>> shared;
static std::shared_ptr<boost::multi_array<AIPathNode, 5>> shared; std::shared_ptr<boost::multi_array<AIPathNode, 4>> nodes;
std::shared_ptr<boost::multi_array<AIPathNode, 5>> nodes;
public: public:
static boost::mutex locker; static boost::mutex locker;
static uint64_t version;
AISharedStorage(int3 mapSize); AISharedStorage(int3 mapSize);
~AISharedStorage(); ~AISharedStorage();
STRONG_INLINE STRONG_INLINE
boost::detail::multi_array::sub_array<AIPathNode, 1> get(int3 tile, EPathfindingLayer layer) const boost::detail::multi_array::sub_array<AIPathNode, 1> get(int3 tile) const
{ {
return (*nodes)[layer][tile.z][tile.x][tile.y]; return (*nodes)[tile.z][tile.x][tile.y];
} }
}; };
@ -156,6 +165,8 @@ class AINodeStorage : public INodeStorage
private: private:
int3 sizes; int3 sizes;
std::unique_ptr<boost::multi_array<EPathAccessibility, 4>> accesibility;
const CPlayerSpecificInfoCallback * cb; const CPlayerSpecificInfoCallback * cb;
const Nullkiller * ai; const Nullkiller * ai;
std::unique_ptr<FuzzyHelper> dangerEvaluator; std::unique_ptr<FuzzyHelper> dangerEvaluator;
@ -182,8 +193,10 @@ public:
std::vector<CGPathNode *> getInitialNodes() override; std::vector<CGPathNode *> getInitialNodes() override;
virtual std::vector<CGPathNode *> calculateNeighbours( virtual void calculateNeighbours(
std::vector<CGPathNode *> & result,
const PathNodeInfo & source, const PathNodeInfo & source,
EPathfindingLayer layer,
const PathfinderConfig * pathfinderConfig, const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) override; const CPathfinderHelper * pathfinderHelper) override;
@ -222,7 +235,27 @@ public:
return aiNode->actor->hero; return aiNode->actor->hero;
} }
inline bool blocked(const int3 & tile, EPathfindingLayer layer) const
{
EPathAccessibility accessible = getAccessibility(tile, layer);
return accessible == EPathAccessibility::NOT_SET
|| accessible == EPathAccessibility::BLOCKED;
}
bool hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const; bool hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const;
bool hasBetterChain(const CGPathNode * source, const AIPathNode & candidateNode) const;
template<class NodeRange>
bool hasBetterChain(
const CGPathNode * source,
const AIPathNode & destinationNode,
const NodeRange & chains) const;
bool isOtherChainBetter(
const CGPathNode * source,
const AIPathNode & candidateNode,
const AIPathNode & other) const;
bool isMovementIneficient(const PathNodeInfo & source, CDestinationNodeInfo & destination) const bool isMovementIneficient(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
{ {
@ -231,12 +264,6 @@ public:
bool isDistanceLimitReached(const PathNodeInfo & source, CDestinationNodeInfo & destination) const; bool isDistanceLimitReached(const PathNodeInfo & source, CDestinationNodeInfo & destination) const;
template<class NodeRange>
bool hasBetterChain(
const CGPathNode * source,
const AIPathNode * destinationNode,
const NodeRange & chains) const;
std::optional<AIPathNode *> getOrCreateNode(const int3 & coord, const EPathfindingLayer layer, const ChainActor * actor); std::optional<AIPathNode *> getOrCreateNode(const int3 & coord, const EPathfindingLayer layer, const ChainActor * actor);
std::vector<AIPath> getChainInfo(const int3 & pos, bool isOnLand) const; std::vector<AIPath> getChainInfo(const int3 & pos, bool isOnLand) const;
bool isTileAccessible(const HeroPtr & hero, const int3 & pos, const EPathfindingLayer layer) const; bool isTileAccessible(const HeroPtr & hero, const int3 & pos, const EPathfindingLayer layer) const;
@ -263,10 +290,17 @@ public:
return (uint64_t)(armyValue * ratio * ratio); return (uint64_t)(armyValue * ratio * ratio);
} }
STRONG_INLINE inline EPathAccessibility getAccessibility(const int3 & tile, EPathfindingLayer layer) const
void resetTile(const int3 & tile, EPathfindingLayer layer, EPathAccessibility accessibility); {
return (*this->accesibility)[tile.z][tile.x][tile.y][layer];
}
STRONG_INLINE int getBucket(const ChainActor * actor) const inline void resetTile(const int3 & tile, EPathfindingLayer layer, EPathAccessibility tileAccessibility)
{
(*this->accesibility)[tile.z][tile.x][tile.y][layer] = tileAccessibility;
}
inline int getBucket(const ChainActor * actor) const
{ {
return ((uintptr_t)actor * 395) % AIPathfinding::BUCKET_COUNT; return ((uintptr_t)actor * 395) % AIPathfinding::BUCKET_COUNT;
} }
@ -274,6 +308,44 @@ public:
void calculateTownPortalTeleportations(std::vector<CGPathNode *> & neighbours); void calculateTownPortalTeleportations(std::vector<CGPathNode *> & neighbours);
void fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const; void fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const;
template<typename Fn>
void iterateValidNodes(const int3 & pos, EPathfindingLayer layer, Fn fn)
{
if(blocked(pos, layer))
return;
auto chains = nodes.get(pos);
for(AIPathNode & node : chains)
{
if(node.version != AISharedStorage::version || node.layer != layer)
continue;
fn(node);
}
}
template<typename Fn>
bool iterateValidNodesUntil(const int3 & pos, EPathfindingLayer layer, Fn predicate) const
{
if(blocked(pos, layer))
return false;
auto chains = nodes.get(pos);
for(AIPathNode & node : chains)
{
if(node.version != AISharedStorage::version || node.layer != layer)
continue;
if(predicate(node))
return true;
}
return false;
}
private: private:
template<class TVector> template<class TVector>
void calculateTownPortal( void calculateTownPortal(

View File

@ -73,6 +73,36 @@ public:
removeExtraConnections(); removeExtraConnections();
} }
float getNeighborConnectionsCost(const int3 & pos)
{
float neighborCost = std::numeric_limits<float>::max();
if(NKAI_GRAPH_TRACE_LEVEL >= 2)
{
logAi->trace("Checking junction %s", pos.toString());
}
foreach_neighbour(
ai->cb.get(),
pos,
[this, &neighborCost](const CPlayerSpecificInfoCallback * cb, const int3 & neighbor)
{
auto costTotal = this->getConnectionsCost(neighbor);
if(costTotal.connectionsCount > 2 && costTotal.avg < neighborCost)
{
neighborCost = costTotal.avg;
if(NKAI_GRAPH_TRACE_LEVEL >= 2)
{
logAi->trace("Better node found at %s", neighbor.toString());
}
}
});
return neighborCost;
}
void addMinimalDistanceJunctions() void addMinimalDistanceJunctions()
{ {
foreach_tile_pos(ai->cb.get(), [this](const CPlayerSpecificInfoCallback * cb, const int3 & pos) foreach_tile_pos(ai->cb.get(), [this](const CPlayerSpecificInfoCallback * cb, const int3 & pos)
@ -88,30 +118,7 @@ public:
if(currentCost.connectionsCount <= 2) if(currentCost.connectionsCount <= 2)
return; return;
float neighborCost = currentCost.avg + 0.001f; float neighborCost = getNeighborConnectionsCost(pos);
if(NKAI_GRAPH_TRACE_LEVEL >= 2)
{
logAi->trace("Checking junction %s", pos.toString());
}
foreach_neighbour(
ai->cb.get(),
pos,
[this, &neighborCost](const CPlayerSpecificInfoCallback * cb, const int3 & neighbor)
{
auto costTotal = this->getConnectionsCost(neighbor);
if(costTotal.avg < neighborCost)
{
neighborCost = costTotal.avg;
if(NKAI_GRAPH_TRACE_LEVEL >= 2)
{
logAi->trace("Better node found at %s", neighbor.toString());
}
}
});
if(currentCost.avg < neighborCost) if(currentCost.avg < neighborCost)
{ {

View File

@ -155,15 +155,21 @@ void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInf
}); });
} }
std::vector<CGPathNode *> AINodeStorage::calculateNeighbours( void AINodeStorage::calculateNeighbours(
std::vector<CGPathNode *> & result,
const PathNodeInfo & source, const PathNodeInfo & source,
EPathfindingLayer layer,
const PathfinderConfig * pathfinderConfig, const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) const CPathfinderHelper * pathfinderHelper)
{ {
std::vector<CGPathNode *> neighbours; std::vector<int3> accessibleNeighbourTiles;
neighbours.reserve(16);
result.clear();
accessibleNeighbourTiles.reserve(8);
pathfinderHelper->calculateNeighbourTiles(accessibleNeighbourTiles, source);
const AIPathNode * srcNode = getAINode(source.node); const AIPathNode * srcNode = getAINode(source.node);
auto accessibleNeighbourTiles = pathfinderHelper->getNeighbourTiles(source);
for(auto & neighbour : accessibleNeighbourTiles) for(auto & neighbour : accessibleNeighbourTiles)
{ {
@ -174,11 +180,9 @@ std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
if(!nextNode || nextNode.value()->accessible == EPathAccessibility::NOT_SET) if(!nextNode || nextNode.value()->accessible == EPathAccessibility::NOT_SET)
continue; continue;
neighbours.push_back(nextNode.value()); result.push_back(nextNode.value());
} }
} }
return neighbours;
} }
void AINodeStorage::setHero(HeroPtr heroPtr, const VCAI * _ai) void AINodeStorage::setHero(HeroPtr heroPtr, const VCAI * _ai)

View File

@ -89,8 +89,10 @@ public:
std::vector<CGPathNode *> getInitialNodes() override; std::vector<CGPathNode *> getInitialNodes() override;
virtual std::vector<CGPathNode *> calculateNeighbours( virtual void calculateNeighbours(
std::vector<CGPathNode *> & result,
const PathNodeInfo & source, const PathNodeInfo & source,
EPathfindingLayer layer,
const PathfinderConfig * pathfinderConfig, const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) override; const CPathfinderHelper * pathfinderHelper) override;

View File

@ -49,30 +49,27 @@ bool CPathfinderHelper::canMoveFromNode(const PathNodeInfo & source) const
return true; return true;
} }
std::vector<int3> CPathfinderHelper::getNeighbourTiles(const PathNodeInfo & source) const void CPathfinderHelper::calculateNeighbourTiles(std::vector<int3> & result, const PathNodeInfo & source) const
{ {
std::vector<int3> neighbourTiles; result.clear();
if (!canMoveFromNode(source)) if (!canMoveFromNode(source))
return neighbourTiles; return;
neighbourTiles.reserve(8);
getNeighbours( getNeighbours(
*source.tile, *source.tile,
source.node->coord, source.node->coord,
neighbourTiles, result,
boost::logic::indeterminate, boost::logic::indeterminate,
source.node->layer == EPathfindingLayer::SAIL); source.node->layer == EPathfindingLayer::SAIL);
if(source.isNodeObjectVisitable()) if(source.isNodeObjectVisitable())
{ {
vstd::erase_if(neighbourTiles, [&](const int3 & tile) -> bool vstd::erase_if(result, [&](const int3 & tile) -> bool
{ {
return !canMoveBetween(tile, source.nodeObject->visitablePos()); return !canMoveBetween(tile, source.nodeObject->visitablePos());
}); });
} }
return neighbourTiles;
} }
CPathfinder::CPathfinder(CGameState * _gs, std::shared_ptr<PathfinderConfig> config): CPathfinder::CPathfinder(CGameState * _gs, std::shared_ptr<PathfinderConfig> config):
@ -129,6 +126,8 @@ void CPathfinder::calculatePaths()
pq.push(initialNode); pq.push(initialNode);
} }
std::vector<CGPathNode *> neighbourNodes;
while(!pq.empty()) while(!pq.empty())
{ {
counter++; counter++;
@ -158,43 +157,47 @@ void CPathfinder::calculatePaths()
source.updateInfo(hlp, gamestate); source.updateInfo(hlp, gamestate);
//add accessible neighbouring nodes to the queue //add accessible neighbouring nodes to the queue
auto neighbourNodes = config->nodeStorage->calculateNeighbours(source, config.get(), hlp); for(EPathfindingLayer layer = EPathfindingLayer::LAND; layer < EPathfindingLayer::NUM_LAYERS; layer.advance(1))
for(CGPathNode * neighbour : neighbourNodes)
{ {
if(neighbour->locked) if(!hlp->isLayerAvailable(layer))
continue; continue;
if(!hlp->isLayerAvailable(neighbour->layer)) config->nodeStorage->calculateNeighbours(neighbourNodes, source, layer, config.get(), hlp);
continue;
destination.setNode(gamestate, neighbour); for(CGPathNode * neighbour : neighbourNodes)
hlp = config->getOrCreatePathfinderHelper(destination, gamestate);
if(!hlp->isPatrolMovementAllowed(neighbour->coord))
continue;
/// Check transition without tile accessability rules
if(source.node->layer != neighbour->layer && !isLayerTransitionPossible())
continue;
destination.turn = turn;
destination.movementLeft = movement;
destination.cost = cost;
destination.updateInfo(hlp, gamestate);
destination.isGuardianTile = destination.guarded && isDestinationGuardian();
for(const auto & rule : config->rules)
{ {
rule->process(source, destination, config.get(), hlp); if(neighbour->locked)
continue;
if(destination.blocked) destination.setNode(gamestate, neighbour);
break; hlp = config->getOrCreatePathfinderHelper(destination, gamestate);
}
if(!destination.blocked) if(!hlp->isPatrolMovementAllowed(neighbour->coord))
push(destination.node); continue;
} //neighbours loop /// Check transition without tile accessability rules
if(source.node->layer != neighbour->layer && !isLayerTransitionPossible())
continue;
destination.turn = turn;
destination.movementLeft = movement;
destination.cost = cost;
destination.updateInfo(hlp, gamestate);
destination.isGuardianTile = destination.guarded && isDestinationGuardian();
for(const auto & rule : config->rules)
{
rule->process(source, destination, config.get(), hlp);
if(destination.blocked)
break;
}
if(!destination.blocked)
push(destination.node);
} //neighbours loop
}
//just add all passable teleport exits //just add all passable teleport exits
hlp = config->getOrCreatePathfinderHelper(source, gamestate); hlp = config->getOrCreatePathfinderHelper(source, gamestate);

View File

@ -96,7 +96,7 @@ public:
bool addTeleportWhirlpool(const CGWhirlpool * obj) const; bool addTeleportWhirlpool(const CGWhirlpool * obj) const;
bool canMoveBetween(const int3 & a, const int3 & b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility) bool canMoveBetween(const int3 & a, const int3 & b) const; //checks only for visitable objects that may make moving between tiles impossible, not other conditions (like tiles itself accessibility)
std::vector<int3> getNeighbourTiles(const PathNodeInfo & source) const; void calculateNeighbourTiles(std::vector<int3> & result, const PathNodeInfo & source) const;
std::vector<int3> getTeleportExits(const PathNodeInfo & source) const; std::vector<int3> getTeleportExits(const PathNodeInfo & source) const;
void getNeighbours( void getNeighbours(

View File

@ -31,8 +31,10 @@ public:
virtual std::vector<CGPathNode *> getInitialNodes() = 0; virtual std::vector<CGPathNode *> getInitialNodes() = 0;
virtual std::vector<CGPathNode *> calculateNeighbours( virtual void calculateNeighbours(
std::vector<CGPathNode *> & result,
const PathNodeInfo & source, const PathNodeInfo & source,
EPathfindingLayer layer,
const PathfinderConfig * pathfinderConfig, const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) = 0; const CPathfinderHelper * pathfinderHelper) = 0;

View File

@ -60,29 +60,29 @@ void NodeStorage::initialize(const PathfinderOptions & options, const CGameState
} }
} }
std::vector<CGPathNode *> NodeStorage::calculateNeighbours( void NodeStorage::calculateNeighbours(
std::vector<CGPathNode *> & result,
const PathNodeInfo & source, const PathNodeInfo & source,
EPathfindingLayer layer,
const PathfinderConfig * pathfinderConfig, const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) const CPathfinderHelper * pathfinderHelper)
{ {
std::vector<CGPathNode *> neighbours; std::vector<int3> accessibleNeighbourTiles;
neighbours.reserve(16);
auto accessibleNeighbourTiles = pathfinderHelper->getNeighbourTiles(source); result.clear();
accessibleNeighbourTiles.reserve(8);
pathfinderHelper->calculateNeighbourTiles(accessibleNeighbourTiles, source);
for(auto & neighbour : accessibleNeighbourTiles) for(auto & neighbour : accessibleNeighbourTiles)
{ {
for(EPathfindingLayer i = EPathfindingLayer::LAND; i < EPathfindingLayer::NUM_LAYERS; i.advance(1)) auto * node = getNode(neighbour, layer);
{
auto * node = getNode(neighbour, i);
if(node->accessible == EPathAccessibility::NOT_SET) if(node->accessible == EPathAccessibility::NOT_SET)
continue; continue;
neighbours.push_back(node); result.push_back(node);
}
} }
return neighbours;
} }
std::vector<CGPathNode *> NodeStorage::calculateTeleportations( std::vector<CGPathNode *> NodeStorage::calculateTeleportations(

View File

@ -36,8 +36,10 @@ public:
std::vector<CGPathNode *> getInitialNodes() override; std::vector<CGPathNode *> getInitialNodes() override;
virtual std::vector<CGPathNode *> calculateNeighbours( virtual void calculateNeighbours(
std::vector<CGPathNode *> & result,
const PathNodeInfo & source, const PathNodeInfo & source,
EPathfindingLayer layer,
const PathfinderConfig * pathfinderConfig, const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) override; const CPathfinderHelper * pathfinderHelper) override;