1
0
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:
Andrii Danylchenko
2018-10-09 22:31:44 +03:00
parent 32c480c33d
commit 72aff02418
22 changed files with 530 additions and 223 deletions

View File

@@ -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);
}

View File

@@ -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)

View File

@@ -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());

View File

@@ -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();
};

View File

@@ -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))
{
}

View File

@@ -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);
};

View File

@@ -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;

View File

@@ -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(