1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-13 19:54:17 +02:00

AI: separate hero chain recalculation

This commit is contained in:
Andrii Danylchenko
2021-05-15 21:01:48 +03:00
committed by Andrii Danylchenko
parent c1e521a544
commit a88181acd7
12 changed files with 226 additions and 101 deletions

View File

@@ -199,9 +199,9 @@ bool isSafeToVisit(HeroPtr h, crint3 tile)
return isSafeToVisit(h, fh->evaluateDanger(tile, h.get()));
}
bool isSafeToVisit(HeroPtr h, uint64_t dangerStrength)
bool isSafeToVisit(HeroPtr h, const CCreatureSet * heroArmy, uint64_t dangerStrength)
{
const ui64 heroStrength = h->getTotalStrength();
const ui64 heroStrength = h->getFightingStrength() * heroArmy->getArmyStrength();
if(dangerStrength)
{
@@ -218,6 +218,11 @@ bool isSafeToVisit(HeroPtr h, uint64_t dangerStrength)
return true; //there's no danger
}
bool isSafeToVisit(HeroPtr h, uint64_t dangerStrength)
{
return isSafeToVisit(h, h.get(), dangerStrength);
}
bool isObjectRemovable(const CGObjectInstance * obj)
{
//FIXME: move logic to object property!

View File

@@ -168,6 +168,7 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj);
bool isObjectRemovable(const CGObjectInstance * obj); //FIXME FIXME: move logic to object property!
bool isSafeToVisit(HeroPtr h, uint64_t dangerStrength);
bool isSafeToVisit(HeroPtr h, const CCreatureSet *, uint64_t dangerStrength);
bool isSafeToVisit(HeroPtr h, crint3 tile);
bool compareHeroStrength(HeroPtr h1, HeroPtr h2);

View File

@@ -94,11 +94,15 @@ namespace Goals
float movementCost;
int manaCost;
uint64_t danger;
uint64_t armyLoss;
uint64_t heroStrength;
EvaluationContext()
: movementCost(0.0),
manaCost(0),
danger(0)
danger(0),
armyLoss(0),
heroStrength(0)
{
}
};

View File

@@ -28,8 +28,10 @@ AINodeStorage::~AINodeStorage() = default;
void AINodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs)
{
//TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline
if(heroChainPass)
return;
//TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline
int3 pos;
const PlayerColor player = ai->playerID;
const int3 sizes = gs->getMapSize();
@@ -73,6 +75,7 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
void AINodeStorage::clear()
{
actors.clear();
heroChainPass = false;
}
const AIPathNode * AINodeStorage::getAINode(const CGPathNode * node) const
@@ -114,6 +117,9 @@ boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
{
if(heroChainPass)
return heroChain;
std::vector<CGPathNode *> initialNodes;
for(auto actorPtr : actors)
@@ -152,6 +158,7 @@ void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, CGPat
heroNode.manaCost = 0;
heroNode.specialAction.reset();
heroNode.armyLoss = 0;
heroNode.chainOther = nullptr;
heroNode.update(coord, layer, accessibility);
}
}
@@ -162,14 +169,7 @@ void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInf
updateAINode(destination.node, [&](AIPathNode * dstNode)
{
dstNode->moveRemains = destination.movementLeft;
dstNode->turns = destination.turn;
dstNode->cost = destination.cost;
dstNode->danger = srcNode->danger;
dstNode->action = destination.action;
dstNode->theNodeBefore = srcNode->theNodeBefore;
dstNode->manaCost = srcNode->manaCost;
dstNode->armyLoss = srcNode->armyLoss;
commit(dstNode, srcNode, destination.action, destination.turn, destination.movementLeft, destination.cost);
if(dstNode->specialAction && dstNode->actor)
{
@@ -178,6 +178,24 @@ void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInf
});
}
void AINodeStorage::commit(
AIPathNode * destination,
const AIPathNode * source,
CGPathNode::ENodeAction action,
int turn,
int movementLeft,
float cost) const
{
destination->action = source->action;
destination->cost = cost;
destination->moveRemains = movementLeft;
destination->turns = turn;
destination->armyLoss = source->armyLoss;
destination->manaCost = source->manaCost;
destination->danger = source->danger;
destination->theNodeBefore = source->theNodeBefore;
}
std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
const PathNodeInfo & source,
const PathfinderConfig * pathfinderConfig,
@@ -201,41 +219,55 @@ std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
}
}
if((source.node->layer == EPathfindingLayer::LAND || source.node->layer == EPathfindingLayer::SAIL)
&& source.node->turns < 1)
{
addHeroChain(neighbours, srcNode);
}
return neighbours;
}
void AINodeStorage::addHeroChain(std::vector<CGPathNode *> & result, const AIPathNode * srcNode)
bool AINodeStorage::calculateHeroChain()
{
heroChainPass = true;
heroChain.resize(0);
foreach_tile_pos([&](const int3 & pos) {
auto layer = EPathfindingLayer::LAND;
auto chains = nodes[pos.x][pos.y][pos.z][layer];
for(AIPathNode & node : chains)
{
if(node.locked && node.turns < 1)
addHeroChain(&node);
}
});
return heroChain.size();
}
void AINodeStorage::addHeroChain(AIPathNode * srcNode)
{
auto chains = nodes[srcNode->coord.x][srcNode->coord.y][srcNode->coord.z][srcNode->layer];
for(const AIPathNode & node : chains)
for(AIPathNode & node : chains)
{
if(!node.locked || !node.actor || node.action == CGPathNode::ENodeAction::UNKNOWN && node.actor->hero)
{
continue;
}
addHeroChain(result, srcNode, &node);
addHeroChain(result, &node, srcNode);
addHeroChain(srcNode, &node);
addHeroChain(&node, srcNode);
}
}
void AINodeStorage::addHeroChain(
std::vector<CGPathNode *> & result,
const AIPathNode * carrier,
const AIPathNode * other)
void AINodeStorage::addHeroChain(AIPathNode * carrier, AIPathNode * other)
{
if(carrier->actor->canExchange(other->actor))
{
bool hasLessMp = carrier->turns > other->turns || carrier->moveRemains < other->moveRemains;
bool hasLessExperience = carrier->actor->hero->exp < other->actor->hero->exp;
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace("Check hero exhange at %s, %s -> %s", carrier->coord.toString(), other->actor->hero->name, carrier->actor->hero->name);
#endif
if(hasLessMp && hasLessExperience)
return;
@@ -250,12 +282,59 @@ void AINodeStorage::addHeroChain(
if(chainNode->locked)
return;
chainNode->specialAction = newActor->getExchangeAction();
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace("Hero exhange at %s, %s -> %s", carrier->coord.toString(), other->actor->hero->name, carrier->actor->hero->name);
#endif
result.push_back(chainNode);
commitExchange(chainNode, carrier, other);
heroChain.push_back(chainNode);
}
}
void AINodeStorage::commitExchange(
AIPathNode * exchangeNode,
AIPathNode * carrierParentNode,
AIPathNode * otherParentNode) const
{
auto carrierActor = carrierParentNode->actor;
auto exchangeActor = exchangeNode->actor;
auto otherActor = otherParentNode->actor;
auto armyLoss = carrierParentNode->armyLoss + otherParentNode->armyLoss;
auto turns = carrierParentNode->turns;
auto cost = carrierParentNode->cost;
auto movementLeft = carrierParentNode->moveRemains;
if(carrierParentNode->turns < otherParentNode->turns)
{
int moveRemains = exchangeActor->hero->maxMovePoints(exchangeNode->layer);
float waitingCost = otherParentNode->turns - carrierParentNode->turns - 1
+ carrierParentNode->moveRemains / (float)moveRemains;
turns = otherParentNode->turns;
cost = waitingCost;
movementLeft = moveRemains;
}
if(exchangeNode->turns != 0xFF && exchangeNode->cost < cost)
return;
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Accepted hero exhange at %s, carrier %s, mp cost %f",
destination.coord.toString(),
carrierActor->hero->name,
destination.cost);
#endif
commit(exchangeNode, carrierParentNode, carrierParentNode->action, turns, movementLeft, cost);
exchangeNode->theNodeBefore = carrierParentNode;
exchangeNode->chainOther = otherParentNode;
exchangeNode->armyLoss = armyLoss;
exchangeNode->manaCost = carrierParentNode->manaCost;
}
const CGHeroInstance * AINodeStorage::getHero(const CGPathNode * node) const
{
auto aiNode = getAINode(node);
@@ -441,6 +520,9 @@ bool AINodeStorage::isTileAccessible(const HeroPtr & hero, const int3 & pos, con
std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand) const
{
std::vector<AIPath> paths;
paths.reserve(NUM_CHAINS / 4);
auto chains = nodes[pos.x][pos.y][pos.z][isOnLand ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL];
for(const AIPathNode & node : chains)
@@ -451,33 +533,44 @@ std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand)
}
AIPath path;
const AIPathNode * current = &node;
path.targetHero = node.actor->hero;
auto initialPos = path.targetHero->visitablePos();
while(current != nullptr && current->coord != initialPos)
{
AIPathNodeInfo pathNode;
pathNode.cost = current->cost;
pathNode.turns = current->turns;
pathNode.danger = current->danger;
pathNode.coord = current->coord;
path.nodes.push_back(pathNode);
path.specialAction = current->specialAction;
current = getAINode(current->theNodeBefore);
}
path.heroArmy = node.actor->creatureSet;
path.armyLoss = node.armyLoss;
path.targetObjectDanger = evaluateDanger(pos, path.targetHero);
fillChainInfo(&node, path);
paths.push_back(path);
}
return paths;
}
void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path) const
{
while(node != nullptr)
{
if(!node->actor->hero || node->coord == node->actor->hero->visitablePos())
return;
AIPathNodeInfo pathNode;
pathNode.cost = node->cost;
pathNode.targetHero = node->actor->hero;
pathNode.turns = node->turns;
pathNode.danger = node->danger;
pathNode.coord = node->coord;
path.nodes.push_back(pathNode);
path.specialAction = node->specialAction;
if(node->chainOther)
fillChainInfo(node->chainOther, path);
node = getAINode(node->theNodeBefore);
}
}
AIPath::AIPath()
: nodes({})
{
@@ -514,6 +607,11 @@ float AIPath::movementCost() const
return 0.0;
}
uint64_t AIPath::getHeroStrength() const
{
return targetHero->getFightingStrength() * heroArmy->getArmyStrength();
}
uint64_t AIPath::getTotalDanger(HeroPtr hero) const
{
uint64_t pathDanger = getPathDanger();

View File

@@ -23,6 +23,7 @@ struct AIPathNode : public CGPathNode
uint64_t danger;
uint64_t armyLoss;
uint32_t manaCost;
const AIPathNode * chainOther;
std::shared_ptr<const ISpecialAction> specialAction;
const ChainActor * actor;
};
@@ -33,6 +34,7 @@ struct AIPathNodeInfo
int turns;
int3 coord;
uint64_t danger;
const CGHeroInstance * targetHero;
};
struct AIPath
@@ -40,7 +42,9 @@ struct AIPath
std::vector<AIPathNodeInfo> nodes;
std::shared_ptr<const ISpecialAction> specialAction;
uint64_t targetObjectDanger;
uint64_t armyLoss;
const CGHeroInstance * targetHero;
const CCreatureSet * heroArmy;
AIPath();
@@ -53,6 +57,8 @@ struct AIPath
int3 firstTileToGet() const;
float movementCost() const;
uint64_t getHeroStrength() const;
};
class AINodeStorage : public INodeStorage
@@ -66,11 +72,9 @@ private:
const VCAI * ai;
std::unique_ptr<FuzzyHelper> dangerEvaluator;
std::vector<std::shared_ptr<ChainActor>> actors;
std::vector<CGPathNode *> heroChain;
bool heroChainPass; // true if we need to calculate hero chain
STRONG_INLINE
void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility);
void addHeroChain(std::vector<CGPathNode *> & result, const AIPathNode * srcNode);
void addHeroChain(std::vector<CGPathNode *> & result, const AIPathNode * carrier, const AIPathNode * other);
public:
/// more than 1 chain layer for each hero allows us to have more than 1 path to each tile so we can chose more optimal one.
static const int NUM_CHAINS = 3 * GameConstants::MAX_HEROES_PER_PLAYER;
@@ -101,14 +105,11 @@ public:
boost::optional<AIPathNode *> getOrCreateNode(const int3 & coord, const EPathfindingLayer layer, const ChainActor * actor);
std::vector<AIPath> getChainInfo(const int3 & pos, bool isOnLand) const;
bool isTileAccessible(const HeroPtr & hero, const int3 & pos, const EPathfindingLayer layer) const;
void setHeroes(std::vector<HeroPtr> heroes, const VCAI * ai);
const CGHeroInstance * getHero(const CGPathNode * node) const;
const std::set<const CGHeroInstance *> getAllHeroes() const;
void clear();
bool calculateHeroChain();
uint64_t evaluateDanger(const int3 & tile, const CGHeroInstance * hero) const
{
@@ -116,5 +117,22 @@ public:
}
private:
STRONG_INLINE
void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility);
void addHeroChain(AIPathNode * srcNode);
void addHeroChain(AIPathNode * carrier, AIPathNode * other);
void calculateTownPortalTeleportations(const PathNodeInfo & source, std::vector<CGPathNode *> & neighbours);
void fillChainInfo(const AIPathNode * node, AIPath & path) const;
void commit(
AIPathNode * destination,
const AIPathNode * source,
CGPathNode::ENodeAction action,
int turn,
int movementLeft,
float cost) const;
void AINodeStorage::commitExchange(
AIPathNode * exchangeNode,
AIPathNode * carrierParentNode,
AIPathNode * otherParentNode) const;
};

View File

@@ -43,7 +43,7 @@ std::vector<AIPath> AIPathfinder::getPathInfo(const HeroPtr & hero, const int3 &
return storage->getChainInfo(tile, !tileInfo->isWater());
}
void AIPathfinder::updatePaths(std::vector<HeroPtr> heroes)
void AIPathfinder::updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain)
{
if(!storage)
{
@@ -55,8 +55,14 @@ void AIPathfinder::updatePaths(std::vector<HeroPtr> heroes)
storage->clear();
storage->setHeroes(heroes, ai);
auto config = std::make_shared<AIPathfinding::AIPathfinderConfig>(cb, ai, storage);
auto config = std::make_shared<AIPathfinding::AIPathfinderConfig>(cb, ai, storage);
cb->calculatePaths(config);
while(useHeroChain && storage->calculateHeroChain())
{
config = std::make_shared<AIPathfinding::AIPathfinderConfig>(cb, ai, storage);
cb->calculatePaths(config);
}
}
void AIPathfinder::updatePaths(const HeroPtr & hero)

View File

@@ -25,7 +25,7 @@ public:
AIPathfinder(CPlayerSpecificInfoCallback * cb, VCAI * ai);
std::vector<AIPath> getPathInfo(const HeroPtr & hero, const int3 & tile) const;
bool isTileAccessible(const HeroPtr & hero, const int3 & tile) const;
void updatePaths(std::vector<HeroPtr> heroes);
void updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain = false);
void updatePaths(const HeroPtr & heroes);
void init();
};

View File

@@ -13,6 +13,7 @@
#include "Rules/AIMovementAfterDestinationRule.h"
#include "Rules/AIMovementToDestinationRule.h"
#include "Rules/AIPreviousNodeRule.h"
#include "Rules/AIMovementCostRule.h"
namespace AIPathfinding
{
@@ -25,7 +26,7 @@ namespace AIPathfinding
std::make_shared<AILayerTransitionRule>(cb, ai, nodeStorage),
std::make_shared<DestinationActionRule>(),
std::make_shared<AIMovementToDestinationRule>(nodeStorage),
std::make_shared<MovementCostRule>(),
std::make_shared<AIMovementCostRule>(nodeStorage),
std::make_shared<AIPreviousNodeRule>(nodeStorage),
std::make_shared<AIMovementAfterDestinationRule>(cb, nodeStorage)
};

View File

@@ -48,7 +48,7 @@ ChainActor::ChainActor(const ChainActor * carrier, const ChainActor * other, con
carrierParent(carrier), otherParent(other), chainMask(carrier->chainMask | other->chainMask)
{
baseActor = static_cast<HeroActor *>(this);
armyValue = heroArmy->getArmyStrength();
armyValue = hero->getFightingStrength() * heroArmy->getArmyStrength();
}
HeroActor::HeroActor(const CGHeroInstance * hero, int chainMask)

View File

@@ -136,7 +136,7 @@ Goals::TGoalVec PathfindingManager::findPaths(
{
danger = path.getTotalDanger(hero);
if(isSafeToVisit(hero, danger))
if(isSafeToVisit(hero, path.heroArmy, danger))
{
Goals::TSubgoal solution;
@@ -158,6 +158,8 @@ Goals::TGoalVec PathfindingManager::findPaths(
solution->evaluationContext.danger = danger;
solution->evaluationContext.movementCost += path.movementCost();
solution->evaluationContext.armyLoss += path.armyLoss;
solution->evaluationContext.heroStrength = path.getHeroStrength();
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace("It's safe for %s to visit tile %s with danger %s, goal %s", hero->name, dest.toString(), std::to_string(danger), solution->name());
#endif

View File

@@ -23,44 +23,5 @@ namespace AIPathfinding
const PathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const
{
auto srcNode = nodeStorage->getAINode(source.node);
auto dstNode = nodeStorage->getAINode(destination.node);
auto srcActor = srcNode->actor;
auto dstActor = dstNode->actor;
if(srcActor == dstActor)
{
MovementCostRule::process(source, destination, pathfinderConfig, pathfinderHelper);
return;
}
auto carrierActor = dstActor->carrierParent;
auto otherActor = dstActor->otherParent;
if(source.coord == destination.coord)
{
auto carrierNode = nodeStorage->getOrCreateNode(source.coord, source.node->layer, carrierActor).get();
auto otherNode = nodeStorage->getOrCreateNode(source.coord, source.node->layer, otherActor).get();
if(carrierNode->turns >= otherNode->turns)
{
destination.turn = carrierNode->turns;
destination.cost = carrierNode->cost;
return;
}
double waitingCost = otherNode->turns - carrierNode->turns - 1.0
+ carrierNode->moveRemains / (double)pathfinderHelper->getMaxMovePoints(carrierNode->layer);
destination.turn = otherNode->turns;
destination.cost = waitingCost;
}
else
{
// TODO: exchange through sail->land border might be more sofisticated
destination.blocked = true;
}
}
}

View File

@@ -36,12 +36,41 @@ namespace AIPathfinding
return;
}
auto aiSourceNode = nodeStorage->getAINode(source.node);
auto srcNode = nodeStorage->getAINode(source.node);
if(aiSourceNode->specialAction)
if(srcNode->specialAction)
{
// there is some action on source tile which should be performed before we can bypass it
destination.node->theNodeBefore = source.node;
}
auto dstNode = nodeStorage->getAINode(destination.node);
auto srcActor = srcNode->actor;
auto dstActor = dstNode->actor;
if(srcActor == dstActor)
{
return;
}
auto carrierActor = dstActor->carrierParent;
auto otherActor = dstActor->otherParent;
nodeStorage->updateAINode(destination.node, [&](AIPathNode * dstNode) {
if(source.coord == destination.coord)
{
auto carrierNode = nodeStorage->getOrCreateNode(source.coord, source.node->layer, carrierActor).get();
auto otherNode = nodeStorage->getOrCreateNode(source.coord, source.node->layer, otherActor).get();
if(destination.coord != carrierNode->coord)
dstNode->theNodeBefore = carrierNode;
dstNode->chainOther = otherNode;
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace("Link Hero exhange at %s, %s -> %s", dstNode->coord.toString(), otherNode->actor->hero->name, carrierNode->actor->hero->name);
#endif
}
});
}
}