mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-06 09:09:40 +02:00
AI: replace SectorMap with new PathfinderManager
This commit is contained in:
234
AI/VCAI/Pathfinding/AINodeStorage.cpp
Normal file
234
AI/VCAI/Pathfinding/AINodeStorage.cpp
Normal file
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* AIhelper.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
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "AINodeStorage.h"
|
||||
|
||||
|
||||
AINodeStorage::AINodeStorage(const int3 & Sizes)
|
||||
: sizes(Sizes)
|
||||
{
|
||||
nodes.resize(boost::extents[sizes.x][sizes.y][sizes.z][EPathfindingLayer::NUM_LAYERS][NUM_CHAINS]);
|
||||
}
|
||||
|
||||
AINodeStorage::~AINodeStorage()
|
||||
{
|
||||
}
|
||||
|
||||
AIPathNode * AINodeStorage::getAINode(CPathNodeInfo & nodeInfo) const
|
||||
{
|
||||
return static_cast<AIPathNode *>(nodeInfo.node);
|
||||
}
|
||||
|
||||
AIPathNode * AINodeStorage::getAINode(CGPathNode * node) const
|
||||
{
|
||||
return static_cast<AIPathNode *>(node);
|
||||
}
|
||||
|
||||
bool AINodeStorage::isBattleNode(CGPathNode * node) const
|
||||
{
|
||||
return getAINode(node)->chainMask & BATTLE_CHAIN > 0;
|
||||
}
|
||||
|
||||
AIPathNode * AINodeStorage::getNode(const int3 & coord, const EPathfindingLayer layer, int chainNumber)
|
||||
{
|
||||
return &nodes[coord.x][coord.y][coord.z][layer][chainNumber];
|
||||
}
|
||||
|
||||
CGPathNode * AINodeStorage::getInitialNode()
|
||||
{
|
||||
auto hpos = hero->getPosition(false);
|
||||
auto initialNode = getNode(hpos, hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND, 0);
|
||||
|
||||
initialNode->turns = 0;
|
||||
initialNode->moveRemains = hero->movement;
|
||||
initialNode->danger = 0;
|
||||
|
||||
return initialNode;
|
||||
}
|
||||
|
||||
void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility)
|
||||
{
|
||||
for(int i = 0; i < NUM_CHAINS; i++)
|
||||
{
|
||||
AIPathNode & heroNode = nodes[coord.x][coord.y][coord.z][layer][i];
|
||||
|
||||
heroNode.chainMask = i;
|
||||
heroNode.update(coord, layer, accessibility);
|
||||
}
|
||||
}
|
||||
|
||||
void AINodeStorage::commit(CDestinationNodeInfo & destination, CPathNodeInfo & source)
|
||||
{
|
||||
auto dstNode = getAINode(destination);
|
||||
auto srcNode = getAINode(source);
|
||||
|
||||
dstNode->moveRemains = destination.movementLeft;
|
||||
dstNode->turns = destination.turn;
|
||||
dstNode->danger = srcNode->danger;
|
||||
dstNode->action = destination.action;
|
||||
dstNode->theNodeBefore = srcNode->theNodeBefore;
|
||||
}
|
||||
|
||||
std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
|
||||
CPathNodeInfo & source,
|
||||
CPathfinderConfig * pathfinderConfig,
|
||||
CPathfinderHelper * pathfinderHelper)
|
||||
{
|
||||
std::vector<CGPathNode *> neighbours;
|
||||
auto srcNode = getAINode(source);
|
||||
auto accessibleNeighbourTiles = pathfinderHelper->getNeighbourTiles(source);
|
||||
|
||||
for(auto & neighbour : accessibleNeighbourTiles)
|
||||
{
|
||||
for(EPathfindingLayer i = EPathfindingLayer::LAND; i <= EPathfindingLayer::AIR; i.advance(1))
|
||||
{
|
||||
auto nextNode = getNode(neighbour, i, srcNode->chainMask);
|
||||
|
||||
if(nextNode->accessible == CGPathNode::NOT_SET)
|
||||
continue;
|
||||
|
||||
neighbours.push_back(nextNode);
|
||||
}
|
||||
}
|
||||
|
||||
return neighbours;
|
||||
}
|
||||
|
||||
std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
|
||||
CPathNodeInfo & source,
|
||||
CPathfinderConfig * pathfinderConfig,
|
||||
CPathfinderHelper * pathfinderHelper)
|
||||
{
|
||||
std::vector<CGPathNode *> neighbours;
|
||||
auto accessibleExits = pathfinderHelper->getTeleportExits(source);
|
||||
auto srcNode = getAINode(source);
|
||||
|
||||
for(auto & neighbour : accessibleExits)
|
||||
{
|
||||
auto node = getNode(neighbour, source.node->layer, srcNode->chainMask);
|
||||
|
||||
neighbours.push_back(node);
|
||||
}
|
||||
|
||||
return neighbours;
|
||||
}
|
||||
|
||||
bool AINodeStorage::hasBetterChain(CPathNodeInfo & source, CDestinationNodeInfo & destination) const
|
||||
{
|
||||
auto pos = destination.coord;
|
||||
auto chains = nodes[pos.x][pos.y][pos.z][EPathfindingLayer::LAND];
|
||||
auto destinationNode = getAINode(destination);
|
||||
|
||||
for(const AIPathNode & node : chains)
|
||||
{
|
||||
auto sameNode = node.chainMask == destinationNode->chainMask;
|
||||
if(sameNode || node.action == CGPathNode::ENodeAction::UNKNOWN)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(node.danger <= destinationNode->danger && destinationNode->chainMask == 1 && node.chainMask == 0)
|
||||
{
|
||||
if(node.turns < destinationNode->turns
|
||||
|| node.turns == destinationNode->turns && node.moveRemains >= destinationNode->moveRemains)
|
||||
{
|
||||
logAi->trace(
|
||||
"Block ineficient move %s:->%s, mask=%i, mp diff: %i",
|
||||
source.coord.toString(),
|
||||
destination.coord.toString(),
|
||||
destinationNode->chainMask,
|
||||
node.moveRemains - destinationNode->moveRemains);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<AIPath> AINodeStorage::getChainInfo(int3 pos)
|
||||
{
|
||||
std::vector<AIPath> paths;
|
||||
auto chains = nodes[pos.x][pos.y][pos.z][EPathfindingLayer::LAND];
|
||||
|
||||
for(const AIPathNode & node : chains)
|
||||
{
|
||||
if(node.action == CGPathNode::ENodeAction::UNKNOWN)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AIPath path;
|
||||
const AIPathNode * current = &node;
|
||||
|
||||
while(current != nullptr)
|
||||
{
|
||||
AIPathNodeInfo pathNode;
|
||||
|
||||
pathNode.movementPointsLeft = current->moveRemains;
|
||||
pathNode.movementPointsUsed = (int)(current->turns * hero->maxMovePoints(true) + hero->movement) - (int)current->moveRemains;
|
||||
pathNode.turns = current->turns;
|
||||
pathNode.danger = current->danger;
|
||||
pathNode.coord = current->coord;
|
||||
|
||||
path.nodes.push_back(pathNode);
|
||||
current = getAINode(current->theNodeBefore);
|
||||
}
|
||||
paths.push_back(path);
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
AIPath::AIPath()
|
||||
: nodes({})
|
||||
{
|
||||
}
|
||||
|
||||
int3 AIPath::firstTileToGet() const
|
||||
{
|
||||
if(nodes.size())
|
||||
{
|
||||
return nodes.back().coord;
|
||||
}
|
||||
|
||||
return int3(-1, -1, -1);
|
||||
}
|
||||
|
||||
uint64_t AIPath::getPathDanger() const
|
||||
{
|
||||
if(nodes.size())
|
||||
{
|
||||
return nodes.front().danger;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t AIPath::movementCost() const
|
||||
{
|
||||
if(nodes.size())
|
||||
{
|
||||
return nodes.front().movementPointsUsed;
|
||||
}
|
||||
|
||||
// TODO: boost:optional?
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t AIPath::getTotalDanger(HeroPtr hero) const
|
||||
{
|
||||
uint64_t pathDanger = getPathDanger();
|
||||
uint64_t objDanger = evaluateDanger(nodes.front().coord, hero.get()); // bank danger is not checked by pathfinder
|
||||
uint64_t danger = pathDanger > objDanger ? pathDanger : objDanger;
|
||||
|
||||
return danger;
|
||||
}
|
||||
104
AI/VCAI/Pathfinding/AINodeStorage.h
Normal file
104
AI/VCAI/Pathfinding/AINodeStorage.h
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* AIhelper.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 "../../../lib/CPathfinder.h"
|
||||
#include "../../../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "AIUtility.h"
|
||||
|
||||
class IVirtualObject
|
||||
{
|
||||
public:
|
||||
virtual void materialize();
|
||||
};
|
||||
|
||||
struct AIPathNode : public CGPathNode
|
||||
{
|
||||
uint32_t chainMask;
|
||||
uint64_t danger;
|
||||
};
|
||||
|
||||
struct AIPathNodeInfo
|
||||
{
|
||||
uint32_t movementPointsLeft;
|
||||
uint32_t movementPointsUsed;
|
||||
int turns;
|
||||
int3 coord;
|
||||
uint64_t danger;
|
||||
};
|
||||
|
||||
struct AIPath
|
||||
{
|
||||
std::vector<AIPathNodeInfo> nodes;
|
||||
|
||||
AIPath();
|
||||
|
||||
/// Gets danger of path excluding danger of visiting the target object like creature bank
|
||||
uint64_t getPathDanger() const;
|
||||
|
||||
/// Gets danger of path including danger of visiting the target object like creature bank
|
||||
uint64_t getTotalDanger(HeroPtr hero) const;
|
||||
|
||||
int3 firstTileToGet() const;
|
||||
|
||||
uint32_t movementCost() const;
|
||||
};
|
||||
|
||||
class AINodeStorage : public INodeStorage
|
||||
{
|
||||
private:
|
||||
int3 sizes;
|
||||
|
||||
/// 1-3 - position on map, 4 - layer (air, water, land), 5 - chain (normal, battle, spellcast and combinations)
|
||||
boost::multi_array<AIPathNode, 5> nodes;
|
||||
const CGHeroInstance * hero;
|
||||
|
||||
public:
|
||||
/// more than 1 chain layer allows us to have more than 1 path to each tile so we can chose more optimal one.
|
||||
static const int NUM_CHAINS = 2;
|
||||
static const int NORMAL_CHAIN = 0;
|
||||
static const int BATTLE_CHAIN = 1;
|
||||
|
||||
AINodeStorage(const int3 & sizes);
|
||||
~AINodeStorage();
|
||||
|
||||
virtual CGPathNode * getInitialNode() override;
|
||||
virtual void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility) override;
|
||||
|
||||
virtual std::vector<CGPathNode *> calculateNeighbours(
|
||||
CPathNodeInfo & source,
|
||||
CPathfinderConfig * pathfinderConfig,
|
||||
CPathfinderHelper * pathfinderHelper) override;
|
||||
|
||||
virtual std::vector<CGPathNode *> calculateTeleportations(
|
||||
CPathNodeInfo & source,
|
||||
CPathfinderConfig * pathfinderConfig,
|
||||
CPathfinderHelper * pathfinderHelper) override;
|
||||
|
||||
virtual void commit(CDestinationNodeInfo & destination, CPathNodeInfo & source) override;
|
||||
|
||||
AIPathNode * getAINode(CPathNodeInfo & nodeInfo) const;
|
||||
AIPathNode * getAINode(CGPathNode * node) const;
|
||||
bool isBattleNode(CGPathNode * node) const;
|
||||
bool hasBetterChain(CPathNodeInfo & source, CDestinationNodeInfo & destination) const;
|
||||
AIPathNode * getNode(const int3 & coord, const EPathfindingLayer layer, int chainNumber);
|
||||
std::vector<AIPath> getChainInfo(int3 pos);
|
||||
|
||||
void setHero(HeroPtr heroPtr)
|
||||
{
|
||||
hero = heroPtr.get();
|
||||
}
|
||||
|
||||
const CGHeroInstance * getHero() const
|
||||
{
|
||||
return hero;
|
||||
}
|
||||
};
|
||||
62
AI/VCAI/Pathfinding/AIPathfinder.cpp
Normal file
62
AI/VCAI/Pathfinding/AIPathfinder.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* AIhelper.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
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "AIPathfinder.h"
|
||||
#include "AIPathfinderConfig.h"
|
||||
#include "../../../CCallback.h"
|
||||
|
||||
std::vector<std::shared_ptr<AINodeStorage>> AIPathfinder::storagePool;
|
||||
std::map<HeroPtr, std::shared_ptr<AINodeStorage>> AIPathfinder::storageMap;
|
||||
boost::mutex AIPathfinder::storageMutex;
|
||||
|
||||
AIPathfinder::AIPathfinder(CPlayerSpecificInfoCallback * cb)
|
||||
:cb(cb)
|
||||
{
|
||||
}
|
||||
|
||||
void AIPathfinder::clear()
|
||||
{
|
||||
boost::unique_lock<boost::mutex> storageLock(storageMutex);
|
||||
storageMap.clear();
|
||||
}
|
||||
|
||||
std::vector<AIPath> AIPathfinder::getPathInfo(HeroPtr hero, int3 tile)
|
||||
{
|
||||
boost::unique_lock<boost::mutex> storageLock(storageMutex);
|
||||
std::shared_ptr<AINodeStorage> nodeStorage;
|
||||
|
||||
if(!vstd::contains(storageMap, hero))
|
||||
{
|
||||
logAi->debug("Recalculate paths for %s", hero->name);
|
||||
|
||||
if(storageMap.size() < storagePool.size())
|
||||
{
|
||||
nodeStorage = storagePool.at(storageMap.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
nodeStorage = std::make_shared<AINodeStorage>(cb->getMapSize());
|
||||
storagePool.push_back(nodeStorage);
|
||||
}
|
||||
|
||||
storageMap[hero] = nodeStorage;
|
||||
|
||||
auto config = std::make_shared<AIPathfinderConfig>(cb, nodeStorage);
|
||||
|
||||
nodeStorage->setHero(hero.get());
|
||||
cb->calculatePaths(config, hero.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
nodeStorage = storageMap.at(hero);
|
||||
}
|
||||
|
||||
return nodeStorage->getChainInfo(tile);
|
||||
}
|
||||
28
AI/VCAI/Pathfinding/AIPathfinder.h
Normal file
28
AI/VCAI/Pathfinding/AIPathfinder.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* AIhelper.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 "AIUtility.h"
|
||||
#include "AINodeStorage.h"
|
||||
|
||||
class AIPathfinder
|
||||
{
|
||||
private:
|
||||
static std::vector<std::shared_ptr<AINodeStorage>> storagePool;
|
||||
static std::map<HeroPtr, std::shared_ptr<AINodeStorage>> storageMap;
|
||||
static boost::mutex storageMutex;
|
||||
CPlayerSpecificInfoCallback * cb;
|
||||
|
||||
public:
|
||||
AIPathfinder(CPlayerSpecificInfoCallback * cb);
|
||||
std::vector<AIPath> getPathInfo(HeroPtr hero, int3 tile);
|
||||
void clear();
|
||||
};
|
||||
280
AI/VCAI/Pathfinding/AIPathfinderConfig.cpp
Normal file
280
AI/VCAI/Pathfinding/AIPathfinderConfig.cpp
Normal file
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* AIhelper.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
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "AIPathfinderConfig.h"
|
||||
#include "../../../CCallback.h"
|
||||
|
||||
class AILayerTransitionRule : public CLayerTransitionRule
|
||||
{
|
||||
public:
|
||||
virtual void process(
|
||||
CPathNodeInfo & source,
|
||||
CDestinationNodeInfo & destination,
|
||||
CPathfinderConfig * pathfinderConfig,
|
||||
CPathfinderHelper * pathfinderHelper) const override
|
||||
{
|
||||
CLayerTransitionRule::process(source, destination, pathfinderConfig, pathfinderHelper);
|
||||
|
||||
if(!destination.blocked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::SAIL)
|
||||
{
|
||||
logAi->debug("Check virtual boat!");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class AIMovementAfterDestinationRule : public CMovementAfterDestinationRule
|
||||
{
|
||||
private:
|
||||
CPlayerSpecificInfoCallback * cb;
|
||||
std::shared_ptr<AINodeStorage> nodeStorage;
|
||||
|
||||
public:
|
||||
AIMovementAfterDestinationRule(CPlayerSpecificInfoCallback * cb, std::shared_ptr<AINodeStorage> nodeStorage)
|
||||
:cb(cb), nodeStorage(nodeStorage)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void process(
|
||||
CPathNodeInfo & source,
|
||||
CDestinationNodeInfo & destination,
|
||||
CPathfinderConfig * pathfinderConfig,
|
||||
CPathfinderHelper * pathfinderHelper) const override
|
||||
{
|
||||
if(nodeStorage->hasBetterChain(source, destination))
|
||||
{
|
||||
destination.blocked = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper);
|
||||
|
||||
if(blocker == BlockingReason::NONE)
|
||||
return;
|
||||
|
||||
auto srcNode = nodeStorage->getAINode(source);
|
||||
|
||||
if(blocker == BlockingReason::DESTINATION_BLOCKVIS && destination.nodeObject)
|
||||
{
|
||||
auto objID = destination.nodeObject->ID;
|
||||
if(objID == Obj::HERO && destination.objectRelations != PlayerRelations::ENEMIES
|
||||
|| objID == Obj::SUBTERRANEAN_GATE || objID == Obj::MONOLITH_TWO_WAY
|
||||
|| objID == Obj::MONOLITH_ONE_WAY_ENTRANCE || objID == Obj::MONOLITH_ONE_WAY_EXIT
|
||||
|| objID == Obj::WHIRLPOOL)
|
||||
{
|
||||
destination.blocked = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(blocker == BlockingReason::DESTINATION_VISIT)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(blocker == BlockingReason::DESTINATION_GUARDED)
|
||||
{
|
||||
auto srcGuardians = cb->getGuardingCreatures(source.coord);
|
||||
auto destGuardians = cb->getGuardingCreatures(destination.coord);
|
||||
|
||||
if(destGuardians.empty())
|
||||
{
|
||||
destination.blocked = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
vstd::erase_if(destGuardians, [&](const CGObjectInstance * destGuard) -> bool
|
||||
{
|
||||
return vstd::contains(srcGuardians, destGuard);
|
||||
});
|
||||
|
||||
auto guardsAlreadyBypassed = destGuardians.empty() && srcGuardians.size();
|
||||
if(guardsAlreadyBypassed && nodeStorage->isBattleNode(source.node))
|
||||
{
|
||||
logAi->trace(
|
||||
"Bypass guard at destination while moving %s -> %s",
|
||||
source.coord.toString(),
|
||||
destination.coord.toString());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto destNode = nodeStorage->getAINode(destination);
|
||||
auto battleNode = nodeStorage->getNode(destination.coord, destination.node->layer, destNode->chainMask | AINodeStorage::BATTLE_CHAIN);
|
||||
|
||||
if(battleNode->locked)
|
||||
{
|
||||
logAi->trace(
|
||||
"Block bypass guard at destination while moving %s -> %s",
|
||||
source.coord.toString(),
|
||||
destination.coord.toString());
|
||||
|
||||
destination.blocked = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto hero = nodeStorage->getHero();
|
||||
auto danger = evaluateDanger(destination.coord, hero);
|
||||
|
||||
destination.node = battleNode;
|
||||
nodeStorage->commit(destination, source);
|
||||
|
||||
if(battleNode->danger < danger)
|
||||
{
|
||||
battleNode->danger = danger;
|
||||
}
|
||||
|
||||
logAi->trace(
|
||||
"Begin bypass guard at destination with danger %s while moving %s -> %s",
|
||||
std::to_string(danger),
|
||||
source.coord.toString(),
|
||||
destination.coord.toString());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
destination.blocked = true;
|
||||
}
|
||||
};
|
||||
|
||||
class AIMovementToDestinationRule : public CMovementToDestinationRule
|
||||
{
|
||||
private:
|
||||
CPlayerSpecificInfoCallback * cb;
|
||||
std::shared_ptr<AINodeStorage> nodeStorage;
|
||||
|
||||
public:
|
||||
AIMovementToDestinationRule(CPlayerSpecificInfoCallback * cb, std::shared_ptr<AINodeStorage> nodeStorage)
|
||||
:cb(cb), nodeStorage(nodeStorage)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void process(
|
||||
CPathNodeInfo & source,
|
||||
CDestinationNodeInfo & destination,
|
||||
CPathfinderConfig * pathfinderConfig,
|
||||
CPathfinderHelper * pathfinderHelper) const override
|
||||
{
|
||||
auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper);
|
||||
|
||||
if(blocker == BlockingReason::NONE)
|
||||
return;
|
||||
|
||||
if(blocker == BlockingReason::SOURCE_GUARDED && nodeStorage->isBattleNode(source.node))
|
||||
{
|
||||
auto srcGuardians = cb->getGuardingCreatures(source.coord);
|
||||
auto destGuardians = cb->getGuardingCreatures(destination.coord);
|
||||
|
||||
for(auto srcGuard : srcGuardians)
|
||||
{
|
||||
if(!vstd::contains(destGuardians, srcGuard))
|
||||
continue;
|
||||
|
||||
auto guardPos = srcGuard->visitablePos();
|
||||
if(guardPos != source.coord && guardPos != destination.coord)
|
||||
{
|
||||
destination.blocked = true; // allow to pass monster only through guard tile
|
||||
}
|
||||
}
|
||||
|
||||
if(!destination.blocked)
|
||||
{
|
||||
logAi->trace(
|
||||
"Bypass src guard while moving from %s to %s",
|
||||
source.coord.toString(),
|
||||
destination.coord.toString());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
destination.blocked = true;
|
||||
}
|
||||
};
|
||||
|
||||
class AIPreviousNodeRule : public CMovementToDestinationRule
|
||||
{
|
||||
private:
|
||||
CPlayerSpecificInfoCallback * cb;
|
||||
std::shared_ptr<AINodeStorage> nodeStorage;
|
||||
|
||||
public:
|
||||
AIPreviousNodeRule(CPlayerSpecificInfoCallback * cb, std::shared_ptr<AINodeStorage> nodeStorage)
|
||||
:cb(cb), nodeStorage(nodeStorage)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void process(
|
||||
CPathNodeInfo & source,
|
||||
CDestinationNodeInfo & destination,
|
||||
CPathfinderConfig * pathfinderConfig,
|
||||
CPathfinderHelper * pathfinderHelper) const override
|
||||
{
|
||||
auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper);
|
||||
|
||||
if(source.guarded)
|
||||
{
|
||||
auto srcGuardian = cb->guardingCreaturePosition(source.node->coord);
|
||||
|
||||
if(srcGuardian == source.node->coord)
|
||||
{
|
||||
// guardian tile is used as chain junction
|
||||
destination.node->theNodeBefore = source.node;
|
||||
|
||||
logAi->trace(
|
||||
"Link src node %s to destination node %s while bypassing guard",
|
||||
source.coord.toString(),
|
||||
destination.coord.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if(source.node->action == CGPathNode::ENodeAction::BLOCKING_VISIT || source.node->action == CGPathNode::ENodeAction::VISIT)
|
||||
{
|
||||
// we can not directly bypass objects, we need to interact with them first
|
||||
destination.node->theNodeBefore = source.node;
|
||||
|
||||
logAi->trace(
|
||||
"Link src node %s to destination node %s while bypassing visitable obj",
|
||||
source.coord.toString(),
|
||||
destination.coord.toString());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<std::shared_ptr<IPathfindingRule>> makeRuleset(
|
||||
CPlayerSpecificInfoCallback * cb,
|
||||
std::shared_ptr<AINodeStorage> nodeStorage)
|
||||
{
|
||||
std::vector<std::shared_ptr<IPathfindingRule>> rules = {
|
||||
std::make_shared<AILayerTransitionRule>(),
|
||||
std::make_shared<CDestinationActionRule>(),
|
||||
std::make_shared<AIMovementToDestinationRule>(cb, nodeStorage),
|
||||
std::make_shared<CMovementCostRule>(),
|
||||
std::make_shared<AIPreviousNodeRule>(cb, nodeStorage),
|
||||
std::make_shared<AIMovementAfterDestinationRule>(cb, nodeStorage)
|
||||
};
|
||||
|
||||
return rules;
|
||||
}
|
||||
|
||||
AIPathfinderConfig::AIPathfinderConfig(
|
||||
CPlayerSpecificInfoCallback * cb,
|
||||
std::shared_ptr<AINodeStorage> nodeStorage)
|
||||
:CPathfinderConfig(nodeStorage, makeRuleset(cb, nodeStorage))
|
||||
{
|
||||
}
|
||||
19
AI/VCAI/Pathfinding/AIPathfinderConfig.h
Normal file
19
AI/VCAI/Pathfinding/AIPathfinderConfig.h
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* AIhelper.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"
|
||||
|
||||
class AIPathfinderConfig : public CPathfinderConfig
|
||||
{
|
||||
public:
|
||||
AIPathfinderConfig(CPlayerSpecificInfoCallback * cb, std::shared_ptr<AINodeStorage> nodeStorage);
|
||||
};
|
||||
214
AI/VCAI/Pathfinding/CPathfindingManager.cpp
Normal file
214
AI/VCAI/Pathfinding/CPathfindingManager.cpp
Normal file
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* AIhelper.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
|
||||
*
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "CPathfindingManager.h"
|
||||
#include "AIPathfinder.h"
|
||||
#include "AIPathfinderConfig.h"
|
||||
#include "../../../lib/CGameInfoCallback.h"
|
||||
#include "../../../lib/mapping/CMap.h"
|
||||
|
||||
CPathfindingManager::CPathfindingManager(CPlayerSpecificInfoCallback * CB, VCAI * AI)
|
||||
: ai(AI), cb(CB)
|
||||
{
|
||||
}
|
||||
|
||||
void CPathfindingManager::setCB(CPlayerSpecificInfoCallback * CB)
|
||||
{
|
||||
cb = CB;
|
||||
pathfinder.reset(new AIPathfinder(cb));
|
||||
}
|
||||
|
||||
void CPathfindingManager::setAI(VCAI * AI)
|
||||
{
|
||||
ai = AI;
|
||||
}
|
||||
|
||||
Goals::TGoalVec CPathfindingManager::howToVisitTile(int3 tile)
|
||||
{
|
||||
Goals::TGoalVec result;
|
||||
|
||||
auto heroes = cb->getHeroesInfo();
|
||||
|
||||
for(auto hero : heroes)
|
||||
{
|
||||
vstd::concatenate(result, howToVisitTile(hero, tile));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Goals::TGoalVec CPathfindingManager::howToVisitObj(ObjectIdRef obj)
|
||||
{
|
||||
Goals::TGoalVec result;
|
||||
|
||||
auto heroes = cb->getHeroesInfo();
|
||||
|
||||
for(auto hero : heroes)
|
||||
{
|
||||
vstd::concatenate(result, howToVisitObj(hero, obj));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Goals::TGoalVec CPathfindingManager::howToVisitTile(HeroPtr hero, int3 tile, bool allowGatherArmy)
|
||||
{
|
||||
return findPath(hero, tile, allowGatherArmy, [&](int3 firstTileToGet) -> Goals::TSubgoal
|
||||
{
|
||||
return sptr(Goals::VisitTile(firstTileToGet).sethero(hero).setisAbstract(true));
|
||||
});
|
||||
}
|
||||
|
||||
Goals::TGoalVec CPathfindingManager::howToVisitObj(HeroPtr hero, ObjectIdRef obj, bool allowGatherArmy)
|
||||
{
|
||||
if(!obj)
|
||||
{
|
||||
return Goals::TGoalVec();
|
||||
}
|
||||
|
||||
int3 dest = obj->visitablePos();
|
||||
|
||||
return findPath(hero, dest, allowGatherArmy, [&](int3 firstTileToGet) -> Goals::TSubgoal
|
||||
{
|
||||
return selectVisitingGoal(hero, obj);
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<AIPath> CPathfindingManager::getPathsToTile(HeroPtr hero, int3 tile)
|
||||
{
|
||||
return pathfinder->getPathInfo(hero, tile);
|
||||
}
|
||||
|
||||
Goals::TGoalVec CPathfindingManager::findPath(
|
||||
HeroPtr hero,
|
||||
crint3 dest,
|
||||
bool allowGatherArmy,
|
||||
const std::function<Goals::TSubgoal(int3)> doVisitTile)
|
||||
{
|
||||
Goals::TGoalVec result;
|
||||
boost::optional<uint64_t> armyValueRequired;
|
||||
uint64_t danger;
|
||||
|
||||
std::vector<AIPath> chainInfo = pathfinder->getPathInfo(hero, dest);
|
||||
|
||||
logAi->trace("Trying to find a way for %s to visit tile %s", hero->name, dest.toString());
|
||||
|
||||
for(auto path : chainInfo)
|
||||
{
|
||||
int3 firstTileToGet = path.firstTileToGet();
|
||||
|
||||
logAi->trace("Path found size=%i, first tile=%s", path.nodes.size(), firstTileToGet.toString());
|
||||
|
||||
if(firstTileToGet.valid() && ai->isTileNotReserved(hero.get(), firstTileToGet))
|
||||
{
|
||||
danger = path.getTotalDanger(hero);
|
||||
|
||||
if(isSafeToVisit(hero, danger))
|
||||
{
|
||||
logAi->trace("It's safe for %s to visit tile %s with danger %s", hero->name, dest.toString(), std::to_string(danger));
|
||||
|
||||
auto solution = dest == firstTileToGet
|
||||
? doVisitTile(firstTileToGet)
|
||||
: clearWayTo(hero, firstTileToGet);
|
||||
result.push_back(solution);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!armyValueRequired || armyValueRequired > danger)
|
||||
{
|
||||
armyValueRequired = boost::make_optional(danger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
danger = armyValueRequired.get_value_or(0);
|
||||
|
||||
if(allowGatherArmy && danger > 0)
|
||||
{
|
||||
//we need to get army in order to conquer that place
|
||||
logAi->trace("Gather army for %s, value=%s", hero->name, std::to_string(danger));
|
||||
result.push_back(sptr(Goals::GatherArmy(danger * SAFE_ATTACK_CONSTANT).sethero(hero).setisAbstract(true)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Goals::TSubgoal CPathfindingManager::selectVisitingGoal(HeroPtr hero, ObjectIdRef obj) const
|
||||
{
|
||||
int3 dest = obj->visitablePos();
|
||||
|
||||
if(obj->ID.num == Obj::HERO) //enemy hero may move to other position
|
||||
{
|
||||
return sptr(Goals::VisitHero(obj->id.getNum()).sethero(hero).setisAbstract(true));
|
||||
}
|
||||
else //just visit that tile
|
||||
{
|
||||
//if target is town, fuzzy system will use additional "estimatedReward" variable to increase priority a bit
|
||||
//TODO: change to getObj eventually and and move appropiate logic there
|
||||
return obj->ID.num == Obj::TOWN
|
||||
? sptr(Goals::VisitTile(dest).sethero(hero).setobjid(obj->ID.num).setisAbstract(true))
|
||||
: sptr(Goals::VisitTile(dest).sethero(hero).setisAbstract(true));
|
||||
}
|
||||
|
||||
return sptr(Goals::VisitTile(dest).sethero(hero).setisAbstract(true));
|
||||
}
|
||||
|
||||
Goals::TSubgoal CPathfindingManager::clearWayTo(HeroPtr hero, int3 firstTileToGet)
|
||||
{
|
||||
if(isBlockedBorderGate(firstTileToGet))
|
||||
{
|
||||
//FIXME: this way we'll not visit gate and activate quest :?
|
||||
return sptr(Goals::FindObj(Obj::KEYMASTER, cb->getTile(firstTileToGet)->visitableObjects.back()->subID));
|
||||
}
|
||||
|
||||
auto topObj = cb->getTopObj(firstTileToGet);
|
||||
if(topObj)
|
||||
{
|
||||
|
||||
if(vstd::contains(ai->reservedObjs, topObj) && !vstd::contains(ai->reservedHeroesMap[hero], topObj))
|
||||
{
|
||||
return sptr(Goals::Invalid());
|
||||
}
|
||||
|
||||
if(topObj->ID == Obj::HERO && cb->getPlayerRelations(hero->tempOwner, topObj->tempOwner) != PlayerRelations::ENEMIES)
|
||||
{
|
||||
if(topObj != hero.get(true)) //the hero we want to free
|
||||
{
|
||||
logAi->error("%s stands in the way of %s", topObj->getObjectName(), hero->getObjectName());
|
||||
|
||||
return sptr(Goals::Invalid());
|
||||
}
|
||||
}
|
||||
|
||||
if(topObj->ID == Obj::QUEST_GUARD || topObj->ID == Obj::BORDERGUARD)
|
||||
{
|
||||
if(shouldVisit(hero, topObj))
|
||||
{
|
||||
//do NOT use VISIT_TILE, as tile with quets guard can't be visited
|
||||
return sptr(Goals::VisitObj(topObj->id.getNum()).sethero(hero));
|
||||
}
|
||||
|
||||
//TODO: we should be able to return apriopriate quest here
|
||||
//ret.push_back(ai->questToGoal());
|
||||
//however, visiting obj for firts time will give us quest
|
||||
//do not access quets guard if we can't complete the quest
|
||||
return sptr(Goals::Invalid());
|
||||
}
|
||||
}
|
||||
|
||||
return sptr(Goals::VisitTile(firstTileToGet).sethero(hero).setisAbstract(true));
|
||||
}
|
||||
|
||||
void CPathfindingManager::resetPaths()
|
||||
{
|
||||
logAi->debug("AIPathfinder has been reseted.");
|
||||
pathfinder->clear();
|
||||
}
|
||||
64
AI/VCAI/Pathfinding/CPathfindingManager.h
Normal file
64
AI/VCAI/Pathfinding/CPathfindingManager.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* AIhelper.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 "VCAI.h"
|
||||
#include "AINodeStorage.h"
|
||||
|
||||
class IPathfindingManager // : pulbic IAbstractManager
|
||||
{
|
||||
public:
|
||||
virtual ~IPathfindingManager() = default;
|
||||
virtual void setCB(CPlayerSpecificInfoCallback * CB) = 0;
|
||||
virtual void setAI(VCAI * AI) = 0;
|
||||
|
||||
virtual void resetPaths() = 0;
|
||||
virtual Goals::TGoalVec howToVisitTile(HeroPtr hero, int3 tile, bool allowGatherArmy = true) = 0;
|
||||
virtual Goals::TGoalVec howToVisitObj(HeroPtr hero, ObjectIdRef obj, bool allowGatherArmy = true) = 0;
|
||||
virtual Goals::TGoalVec howToVisitTile(int3 tile) = 0;
|
||||
virtual Goals::TGoalVec howToVisitObj(ObjectIdRef obj) = 0;
|
||||
virtual std::vector<AIPath> getPathsToTile(HeroPtr hero, int3 tile) = 0;
|
||||
};
|
||||
|
||||
class CPathfindingManager : public IPathfindingManager
|
||||
{
|
||||
friend class AIhelper;
|
||||
|
||||
private:
|
||||
CPlayerSpecificInfoCallback * cb; //this is enough, but we downcast from CCallback
|
||||
VCAI * ai;
|
||||
std::unique_ptr<AIPathfinder> pathfinder;
|
||||
|
||||
public:
|
||||
CPathfindingManager() = default;
|
||||
CPathfindingManager(CPlayerSpecificInfoCallback * CB, VCAI * AI = nullptr); //for tests only
|
||||
|
||||
Goals::TGoalVec howToVisitTile(HeroPtr hero, int3 tile, bool allowGatherArmy = true) override;
|
||||
Goals::TGoalVec howToVisitObj(HeroPtr hero, ObjectIdRef obj, bool allowGatherArmy = true) override;
|
||||
Goals::TGoalVec howToVisitTile(int3 tile) override;
|
||||
Goals::TGoalVec howToVisitObj(ObjectIdRef obj) override;
|
||||
std::vector<AIPath> getPathsToTile(HeroPtr hero, int3 tile) override;
|
||||
void resetPaths() override;
|
||||
|
||||
private:
|
||||
void setCB(CPlayerSpecificInfoCallback * CB) override;
|
||||
void setAI(VCAI * AI) override;
|
||||
|
||||
Goals::TGoalVec findPath(
|
||||
HeroPtr hero,
|
||||
crint3 dest,
|
||||
bool allowGatherArmy,
|
||||
const std::function<Goals::TSubgoal(int3)> goalFactory);
|
||||
|
||||
Goals::TSubgoal clearWayTo(HeroPtr hero, int3 firstTileToGet);
|
||||
|
||||
Goals::TSubgoal selectVisitingGoal(HeroPtr hero, ObjectIdRef obj) const;
|
||||
};
|
||||
Reference in New Issue
Block a user