mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-06 09:09:40 +02:00
AI pathfinding: buy boat
This commit is contained in:
@@ -38,15 +38,34 @@ bool AINodeStorage::isBattleNode(const CGPathNode * node) const
|
||||
return (getAINode(node)->chainMask & BATTLE_CHAIN) > 0;
|
||||
}
|
||||
|
||||
AIPathNode * AINodeStorage::getNode(const int3 & coord, const EPathfindingLayer layer, int chainNumber)
|
||||
boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(const int3 & pos, const EPathfindingLayer layer, int chainNumber)
|
||||
{
|
||||
return &nodes[coord.x][coord.y][coord.z][layer][chainNumber];
|
||||
auto chains = nodes[pos.x][pos.y][pos.z][layer];
|
||||
|
||||
for(AIPathNode & node : chains)
|
||||
{
|
||||
if(node.chainMask == chainNumber)
|
||||
{
|
||||
return &node;
|
||||
}
|
||||
|
||||
if(node.chainMask == 0)
|
||||
{
|
||||
node.chainMask = chainNumber;
|
||||
|
||||
return &node;
|
||||
}
|
||||
}
|
||||
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
CGPathNode * AINodeStorage::getInitialNode()
|
||||
{
|
||||
auto hpos = hero->getPosition(false);
|
||||
auto initialNode = getNode(hpos, hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND, 0);
|
||||
auto initialNode =
|
||||
getOrCreateNode(hpos, hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND, NORMAL_CHAIN)
|
||||
.get();
|
||||
|
||||
initialNode->turns = 0;
|
||||
initialNode->moveRemains = hero->movement;
|
||||
@@ -61,7 +80,9 @@ void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, CGPat
|
||||
{
|
||||
AIPathNode & heroNode = nodes[coord.x][coord.y][coord.z][layer][i];
|
||||
|
||||
heroNode.chainMask = i;
|
||||
heroNode.chainMask = 0;
|
||||
heroNode.danger = 0;
|
||||
heroNode.specialAction.reset();
|
||||
heroNode.update(coord, layer, accessibility);
|
||||
}
|
||||
}
|
||||
@@ -92,12 +113,12 @@ std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
|
||||
{
|
||||
for(EPathfindingLayer i = EPathfindingLayer::LAND; i <= EPathfindingLayer::AIR; i.advance(1))
|
||||
{
|
||||
auto nextNode = getNode(neighbour, i, srcNode->chainMask);
|
||||
auto nextNode = getOrCreateNode(neighbour, i, srcNode->chainMask);
|
||||
|
||||
if(nextNode->accessible == CGPathNode::NOT_SET)
|
||||
if(!nextNode || nextNode.get()->accessible == CGPathNode::NOT_SET)
|
||||
continue;
|
||||
|
||||
neighbours.push_back(nextNode);
|
||||
neighbours.push_back(nextNode.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,9 +136,12 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
|
||||
|
||||
for(auto & neighbour : accessibleExits)
|
||||
{
|
||||
auto node = getNode(neighbour, source.node->layer, srcNode->chainMask);
|
||||
auto node = getOrCreateNode(neighbour, source.node->layer, srcNode->chainMask);
|
||||
|
||||
neighbours.push_back(node);
|
||||
if(!node)
|
||||
continue;
|
||||
|
||||
neighbours.push_back(node.get());
|
||||
}
|
||||
|
||||
return neighbours;
|
||||
@@ -183,8 +207,11 @@ std::vector<AIPath> AINodeStorage::getChainInfo(int3 pos) const
|
||||
pathNode.coord = current->coord;
|
||||
|
||||
path.nodes.push_back(pathNode);
|
||||
path.specialAction = current->specialAction;
|
||||
|
||||
current = getAINode(current->theNodeBefore);
|
||||
}
|
||||
|
||||
paths.push_back(path);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,17 +13,19 @@
|
||||
#include "../../../lib/CPathfinder.h"
|
||||
#include "../../../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../AIUtility.h"
|
||||
#include "../Goals.h"
|
||||
|
||||
class IVirtualObject
|
||||
class ISpecialAction
|
||||
{
|
||||
public:
|
||||
virtual void materialize();
|
||||
virtual Goals::TSubgoal whatToDo(HeroPtr hero) const = 0;
|
||||
};
|
||||
|
||||
struct AIPathNode : public CGPathNode
|
||||
{
|
||||
uint32_t chainMask;
|
||||
uint64_t danger;
|
||||
std::shared_ptr<const ISpecialAction> specialAction;
|
||||
};
|
||||
|
||||
struct AIPathNodeInfo
|
||||
@@ -38,6 +40,7 @@ struct AIPathNodeInfo
|
||||
struct AIPath
|
||||
{
|
||||
std::vector<AIPathNodeInfo> nodes;
|
||||
std::shared_ptr<const ISpecialAction> specialAction;
|
||||
|
||||
AIPath();
|
||||
|
||||
@@ -63,9 +66,13 @@ private:
|
||||
|
||||
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;
|
||||
static const int NUM_CHAINS = 3;
|
||||
|
||||
// chain flags, can be combined
|
||||
static const int NORMAL_CHAIN = 1;
|
||||
static const int BATTLE_CHAIN = 2;
|
||||
static const int CAST_CHAIN = 4;
|
||||
static const int RESOURCE_CHAIN = 8;
|
||||
|
||||
AINodeStorage(const int3 & sizes);
|
||||
~AINodeStorage();
|
||||
@@ -90,7 +97,7 @@ public:
|
||||
|
||||
bool isBattleNode(const CGPathNode * node) const;
|
||||
bool hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const;
|
||||
AIPathNode * getNode(const int3 & coord, const EPathfindingLayer layer, int chainNumber);
|
||||
boost::optional<AIPathNode *> getOrCreateNode(const int3 & coord, const EPathfindingLayer layer, int chainNumber);
|
||||
std::vector<AIPath> getChainInfo(int3 pos) const;
|
||||
|
||||
void setHero(HeroPtr heroPtr)
|
||||
|
||||
@@ -16,8 +16,8 @@ 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)
|
||||
AIPathfinder::AIPathfinder(CPlayerSpecificInfoCallback * cb, VCAI * ai)
|
||||
:cb(cb), ai(ai)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ std::vector<AIPath> AIPathfinder::getPathInfo(HeroPtr hero, int3 tile)
|
||||
|
||||
storageMap[hero] = nodeStorage;
|
||||
|
||||
auto config = std::make_shared<AIPathfinderConfig>(cb, nodeStorage);
|
||||
auto config = std::make_shared<AIPathfinderConfig>(cb, ai, nodeStorage);
|
||||
|
||||
nodeStorage->setHero(hero.get());
|
||||
cb->calculatePaths(config, hero.get());
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../AIUtility.h"
|
||||
#include "AINodeStorage.h"
|
||||
#include "../AIUtility.h"
|
||||
#include "../VCAI.h"
|
||||
|
||||
class AIPathfinder
|
||||
{
|
||||
@@ -20,9 +21,10 @@ private:
|
||||
static std::map<HeroPtr, std::shared_ptr<AINodeStorage>> storageMap;
|
||||
static boost::mutex storageMutex;
|
||||
CPlayerSpecificInfoCallback * cb;
|
||||
VCAI * ai;
|
||||
|
||||
public:
|
||||
AIPathfinder(CPlayerSpecificInfoCallback * cb);
|
||||
AIPathfinder(CPlayerSpecificInfoCallback * cb, VCAI * ai);
|
||||
std::vector<AIPath> getPathInfo(HeroPtr hero, int3 tile);
|
||||
void clear();
|
||||
};
|
||||
|
||||
@@ -10,6 +10,119 @@
|
||||
#include "StdInc.h"
|
||||
#include "AIPathfinderConfig.h"
|
||||
#include "../../../CCallback.h"
|
||||
#include "../../../lib/mapping/CMap.h"
|
||||
#include "../../../lib/mapObjects/MapObjects.h"
|
||||
|
||||
class BuildBoatAction : public ISpecialAction
|
||||
{
|
||||
private:
|
||||
const IShipyard * shipyard;
|
||||
|
||||
public:
|
||||
BuildBoatAction(const IShipyard * shipyard)
|
||||
:shipyard(shipyard)
|
||||
{
|
||||
}
|
||||
|
||||
virtual Goals::TSubgoal whatToDo(HeroPtr hero) const override
|
||||
{
|
||||
return sptr(Goals::BuildBoat(shipyard));
|
||||
}
|
||||
};
|
||||
|
||||
class AILayerTransitionRule : public LayerTransitionRule
|
||||
{
|
||||
private:
|
||||
CPlayerSpecificInfoCallback * cb;
|
||||
VCAI * ai;
|
||||
std::map<int3, std::shared_ptr<const BuildBoatAction>> virtualBoats;
|
||||
std::shared_ptr<AINodeStorage> nodeStorage;
|
||||
|
||||
public:
|
||||
AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, VCAI * ai, std::shared_ptr<AINodeStorage> nodeStorage)
|
||||
:cb(cb), ai(ai), nodeStorage(nodeStorage)
|
||||
{
|
||||
setup();
|
||||
}
|
||||
|
||||
virtual void process(
|
||||
const PathNodeInfo & source,
|
||||
CDestinationNodeInfo & destination,
|
||||
const PathfinderConfig * pathfinderConfig,
|
||||
CPathfinderHelper * pathfinderHelper) const override
|
||||
{
|
||||
LayerTransitionRule::process(source, destination, pathfinderConfig, pathfinderHelper);
|
||||
|
||||
if(!destination.blocked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::SAIL
|
||||
&& vstd::contains(virtualBoats, destination.coord))
|
||||
{
|
||||
logAi->trace("Bypassing virtual boat at %s!", destination.coord.toString());
|
||||
|
||||
nodeStorage->updateAINode(destination.node, [&](AIPathNode * node)
|
||||
{
|
||||
std::shared_ptr<const BuildBoatAction> virtualBoat = virtualBoats.at(destination.coord);
|
||||
|
||||
auto boatNodeOptional = nodeStorage->getOrCreateNode(
|
||||
node->coord,
|
||||
node->layer,
|
||||
node->chainMask | AINodeStorage::RESOURCE_CHAIN);
|
||||
|
||||
if(boatNodeOptional)
|
||||
{
|
||||
AIPathNode * boatNode = boatNodeOptional.get();
|
||||
|
||||
boatNode->specialAction = virtualBoat;
|
||||
destination.blocked = false;
|
||||
destination.action = CGPathNode::ENodeAction::EMBARK;
|
||||
destination.node = boatNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
logAi->trace(
|
||||
"Can not allocate boat node while moving %s -> %s",
|
||||
source.coord.toString(),
|
||||
destination.coord.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void setup()
|
||||
{
|
||||
std::vector<const IShipyard *> shipyards;
|
||||
|
||||
for(const CGTownInstance * t : cb->getTownsInfo())
|
||||
{
|
||||
if(t->hasBuilt(BuildingID::SHIPYARD))
|
||||
shipyards.push_back(t);
|
||||
}
|
||||
|
||||
for(const CGObjectInstance * obj : ai->visitableObjs)
|
||||
{
|
||||
if(obj->ID != Obj::TOWN) //towns were handled in the previous loop
|
||||
{
|
||||
if(const IShipyard * shipyard = IShipyard::castFrom(obj))
|
||||
shipyards.push_back(shipyard);
|
||||
}
|
||||
}
|
||||
|
||||
for(const IShipyard * shipyard : shipyards)
|
||||
{
|
||||
if(shipyard->shipyardStatus() == IShipyard::GOOD)
|
||||
{
|
||||
int3 boatLocation = shipyard->bestLocation();
|
||||
virtualBoats[boatLocation] = std::make_shared<BuildBoatAction>(shipyard);
|
||||
logAi->debug("Virtual boat added at %s", boatLocation.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class AIMovementAfterDestinationRule : public MovementAfterDestinationRule
|
||||
{
|
||||
@@ -88,8 +201,25 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
auto destNode = nodeStorage->getAINode(destination.node);
|
||||
auto battleNode = nodeStorage->getNode(destination.coord, destination.node->layer, destNode->chainMask | AINodeStorage::BATTLE_CHAIN);
|
||||
const AIPathNode * destNode = nodeStorage->getAINode(destination.node);
|
||||
auto battleNodeOptional = nodeStorage->getOrCreateNode(
|
||||
destination.coord,
|
||||
destination.node->layer,
|
||||
destNode->chainMask | AINodeStorage::BATTLE_CHAIN);
|
||||
|
||||
if(!battleNodeOptional)
|
||||
{
|
||||
logAi->trace(
|
||||
"Can not allocate battle node while moving %s -> %s",
|
||||
source.coord.toString(),
|
||||
destination.coord.toString());
|
||||
|
||||
destination.blocked = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
AIPathNode * battleNode = battleNodeOptional.get();
|
||||
|
||||
if(battleNode->locked)
|
||||
{
|
||||
@@ -150,6 +280,13 @@ public:
|
||||
if(blocker == BlockingReason::NONE)
|
||||
return;
|
||||
|
||||
if(blocker == BlockingReason::DESTINATION_BLOCKED
|
||||
&& destination.action == CGPathNode::EMBARK
|
||||
&& nodeStorage->getAINode(destination.node)->specialAction)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(blocker == BlockingReason::SOURCE_GUARDED && nodeStorage->isBattleNode(source.node))
|
||||
{
|
||||
auto srcGuardians = cb->getGuardingCreatures(source.coord);
|
||||
@@ -216,6 +353,8 @@ public:
|
||||
"Link src node %s to destination node %s while bypassing guard",
|
||||
source.coord.toString(),
|
||||
destination.coord.toString());
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,16 +367,27 @@ public:
|
||||
"Link src node %s to destination node %s while bypassing visitable obj",
|
||||
source.coord.toString(),
|
||||
destination.coord.toString());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto aiSourceNode = nodeStorage->getAINode(source.node);
|
||||
|
||||
if(aiSourceNode->specialAction)
|
||||
{
|
||||
// there is some action on source tile which should be performed before we can bypass it
|
||||
destination.node->theNodeBefore = source.node;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<std::shared_ptr<IPathfindingRule>> makeRuleset(
|
||||
CPlayerSpecificInfoCallback * cb,
|
||||
VCAI * ai,
|
||||
std::shared_ptr<AINodeStorage> nodeStorage)
|
||||
{
|
||||
std::vector<std::shared_ptr<IPathfindingRule>> rules = {
|
||||
std::make_shared<LayerTransitionRule>(),
|
||||
std::make_shared<AILayerTransitionRule>(cb, ai, nodeStorage),
|
||||
std::make_shared<DestinationActionRule>(),
|
||||
std::make_shared<AIMovementToDestinationRule>(cb, nodeStorage),
|
||||
std::make_shared<MovementCostRule>(),
|
||||
@@ -250,7 +400,8 @@ std::vector<std::shared_ptr<IPathfindingRule>> makeRuleset(
|
||||
|
||||
AIPathfinderConfig::AIPathfinderConfig(
|
||||
CPlayerSpecificInfoCallback * cb,
|
||||
VCAI * ai,
|
||||
std::shared_ptr<AINodeStorage> nodeStorage)
|
||||
:PathfinderConfig(nodeStorage, makeRuleset(cb, nodeStorage))
|
||||
:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -11,9 +11,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "AINodeStorage.h"
|
||||
#include "../VCAI.h"
|
||||
|
||||
class AIPathfinderConfig : public PathfinderConfig
|
||||
{
|
||||
public:
|
||||
AIPathfinderConfig(CPlayerSpecificInfoCallback * cb, std::shared_ptr<AINodeStorage> nodeStorage);
|
||||
AIPathfinderConfig(
|
||||
CPlayerSpecificInfoCallback * cb,
|
||||
VCAI * ai,
|
||||
std::shared_ptr<AINodeStorage> nodeStorage);
|
||||
};
|
||||
@@ -19,10 +19,10 @@ PathfindingManager::PathfindingManager(CPlayerSpecificInfoCallback * CB, VCAI *
|
||||
{
|
||||
}
|
||||
|
||||
void PathfindingManager::setCB(CPlayerSpecificInfoCallback * CB)
|
||||
void PathfindingManager::init(CPlayerSpecificInfoCallback * CB)
|
||||
{
|
||||
cb = CB;
|
||||
pathfinder.reset(new AIPathfinder(cb));
|
||||
pathfinder.reset(new AIPathfinder(cb, ai));
|
||||
}
|
||||
|
||||
void PathfindingManager::setAI(VCAI * AI)
|
||||
@@ -60,10 +60,17 @@ Goals::TGoalVec PathfindingManager::howToVisitObj(ObjectIdRef obj)
|
||||
|
||||
Goals::TGoalVec PathfindingManager::howToVisitTile(HeroPtr hero, int3 tile, bool allowGatherArmy)
|
||||
{
|
||||
return findPath(hero, tile, allowGatherArmy, [&](int3 firstTileToGet) -> Goals::TSubgoal
|
||||
auto result = findPath(hero, tile, allowGatherArmy, [&](int3 firstTileToGet) -> Goals::TSubgoal
|
||||
{
|
||||
return sptr(Goals::VisitTile(firstTileToGet).sethero(hero).setisAbstract(true));
|
||||
});
|
||||
|
||||
for(Goals::TSubgoal solution : result)
|
||||
{
|
||||
solution->setparent(sptr(Goals::VisitTile(tile).sethero(hero).setevaluationContext(solution->evaluationContext)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Goals::TGoalVec PathfindingManager::howToVisitObj(HeroPtr hero, ObjectIdRef obj, bool allowGatherArmy)
|
||||
@@ -75,13 +82,20 @@ Goals::TGoalVec PathfindingManager::howToVisitObj(HeroPtr hero, ObjectIdRef obj,
|
||||
|
||||
int3 dest = obj->visitablePos();
|
||||
|
||||
return findPath(hero, dest, allowGatherArmy, [&](int3 firstTileToGet) -> Goals::TSubgoal
|
||||
auto result = findPath(hero, dest, allowGatherArmy, [&](int3 firstTileToGet) -> Goals::TSubgoal
|
||||
{
|
||||
if(obj->ID.num == Obj::HERO && obj->getOwner() == hero->getOwner())
|
||||
return sptr(Goals::VisitHero(obj->id.getNum()).sethero(hero).setisAbstract(true));
|
||||
else
|
||||
return sptr(Goals::VisitObj(obj->id.getNum()).sethero(hero).setisAbstract(true));
|
||||
});
|
||||
|
||||
for(Goals::TSubgoal solution : result)
|
||||
{
|
||||
solution->setparent(sptr(Goals::VisitObj(obj->id.getNum()).sethero(hero).setevaluationContext(solution->evaluationContext)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<AIPath> PathfindingManager::getPathsToTile(HeroPtr hero, int3 tile)
|
||||
@@ -117,9 +131,24 @@ Goals::TGoalVec PathfindingManager::findPath(
|
||||
{
|
||||
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);
|
||||
Goals::TSubgoal solution;
|
||||
|
||||
if(path.specialAction)
|
||||
{
|
||||
solution = path.specialAction->whatToDo(hero);
|
||||
}
|
||||
else
|
||||
{
|
||||
solution = dest == firstTileToGet
|
||||
? doVisitTile(firstTileToGet)
|
||||
: clearWayTo(hero, firstTileToGet);
|
||||
}
|
||||
|
||||
if(solution->evaluationContext.danger < danger)
|
||||
solution->evaluationContext.danger = danger;
|
||||
|
||||
solution->evaluationContext.movementCost += path.movementCost();
|
||||
|
||||
result.push_back(solution);
|
||||
|
||||
continue;
|
||||
|
||||
@@ -17,7 +17,7 @@ class IPathfindingManager
|
||||
{
|
||||
public:
|
||||
virtual ~IPathfindingManager() = default;
|
||||
virtual void setCB(CPlayerSpecificInfoCallback * CB) = 0;
|
||||
virtual void init(CPlayerSpecificInfoCallback * CB) = 0;
|
||||
virtual void setAI(VCAI * AI) = 0;
|
||||
|
||||
virtual void resetPaths() = 0;
|
||||
@@ -49,7 +49,7 @@ public:
|
||||
void resetPaths() override;
|
||||
|
||||
private:
|
||||
void setCB(CPlayerSpecificInfoCallback * CB) override;
|
||||
void init(CPlayerSpecificInfoCallback * CB) override;
|
||||
void setAI(VCAI * AI) override;
|
||||
|
||||
Goals::TGoalVec findPath(
|
||||
|
||||
Reference in New Issue
Block a user