mirror of
https://github.com/vcmi/vcmi.git
synced 2026-06-19 22:57:37 +02:00
Fix branched hero chain reconstruction to preserve order and parent links
Fix reconstruction of branched hero chains so branch nodes keep their original ordering and parentIndex links remain valid. Previously, reconstructed paths could connect nodes in the wrong sequence, which produced invalid handoff paths during chain execution.
This commit is contained in:
@@ -1442,8 +1442,18 @@ void AINodeStorage::calculateChainInfo(std::vector<AIPath> & paths, const int3 &
|
||||
path.targetHero = node.actor->hero;
|
||||
path.heroArmy = node.actor->creatureSet;
|
||||
path.armyLoss = node.armyLoss;
|
||||
path.chainMask = node.actor->chainMask;
|
||||
|
||||
fillChainInfo(&node, path, -1);
|
||||
RealMoveMasksByHero realMoveMasks;
|
||||
int parentIndex = -1;
|
||||
if(!tryReconstructChainInfo(&node, path, parentIndex, realMoveMasks))
|
||||
{
|
||||
#if NK2AI_PATHFINDER_TRACE_LEVEL >= 2
|
||||
logAi->trace("AINodeStorage::calculateChainInfo Skip conflicting reconstructed chain path %s", path.toString());
|
||||
#endif
|
||||
paths.pop_back();
|
||||
continue;
|
||||
}
|
||||
path.targetObjectDanger = aiNk->dangerEvaluator->evaluateDanger(pos, path.targetHero, !node.actor->allowBattle);
|
||||
for(const auto & pathNode : path.nodes)
|
||||
{
|
||||
@@ -1487,46 +1497,83 @@ void AINodeStorage::calculateChainInfo(std::vector<AIPath> & paths, const int3 &
|
||||
path.targetHero,
|
||||
getHeroArmyStrengthWithCommander(path.targetHero, path.heroArmy, fortLevel),
|
||||
path.targetObjectDanger);
|
||||
|
||||
path.chainMask = node.actor->chainMask;
|
||||
path.exchangeCount = node.actor->actorExchangeCount;
|
||||
}
|
||||
}
|
||||
|
||||
void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const
|
||||
bool AINodeStorage::tryReconstructChainInfo(const AIPathNode * node, AIPath & path, int & parentIndex, RealMoveMasksByHero & realMoveMasks) const
|
||||
{
|
||||
while(node != nullptr)
|
||||
{
|
||||
if(!node->actor->hero)
|
||||
return;
|
||||
return true;
|
||||
|
||||
const auto tryAppendCurrentNode = [this, &node](AIPath & candidatePath, int candidateParentIndex,
|
||||
RealMoveMasksByHero & candidateMasks) -> std::optional<int>
|
||||
{
|
||||
if(isRealMovementNode(node))
|
||||
{
|
||||
auto existingMask = candidateMasks.find(node->actor->hero);
|
||||
if(existingMask != candidateMasks.end() && existingMask->second != node->actor->chainMask)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
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 = candidateParentIndex;
|
||||
pathNode.actionIsBlocked = false;
|
||||
pathNode.layer = node->layer;
|
||||
|
||||
if(pathNode.specialAction)
|
||||
{
|
||||
auto targetNode = node->theNodeBefore ? getAINode(node->theNodeBefore) : node;
|
||||
pathNode.actionIsBlocked = !pathNode.specialAction->canAct(aiNk, targetNode);
|
||||
}
|
||||
|
||||
const int nextParentIndex = static_cast<int>(candidatePath.nodes.size());
|
||||
|
||||
candidatePath.nodes.push_back(pathNode);
|
||||
if(isRealMovementNode(node))
|
||||
candidateMasks[node->actor->hero] = node->actor->chainMask;
|
||||
|
||||
return nextParentIndex;
|
||||
};
|
||||
|
||||
if(node->chainOther)
|
||||
fillChainInfo(node->chainOther, path, parentIndex);
|
||||
|
||||
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)
|
||||
{
|
||||
auto targetNode =node->theNodeBefore ? getAINode(node->theNodeBefore) : node;
|
||||
AIPath pathWithBranch = path;
|
||||
auto masksWithBranch = realMoveMasks;
|
||||
int parentIndexWithBranch = parentIndex;
|
||||
|
||||
pathNode.actionIsBlocked = !pathNode.specialAction->canAct(aiNk, targetNode);
|
||||
if(!tryReconstructChainInfo(node->chainOther, pathWithBranch, parentIndexWithBranch, masksWithBranch))
|
||||
return false; // Reject path because chainOther branch is conflicting for hero
|
||||
|
||||
auto nextParentIndex = tryAppendCurrentNode(pathWithBranch, parentIndexWithBranch, masksWithBranch);
|
||||
if(!nextParentIndex)
|
||||
return false; // Reject path because current node conflicts after chainOther for hero
|
||||
|
||||
path = std::move(pathWithBranch);
|
||||
realMoveMasks = std::move(masksWithBranch);
|
||||
parentIndex = *nextParentIndex;
|
||||
node = getAINode(node->theNodeBefore);
|
||||
continue;
|
||||
}
|
||||
|
||||
parentIndex = path.nodes.size();
|
||||
path.nodes.push_back(pathNode);
|
||||
auto nextParentIndex = tryAppendCurrentNode(path, parentIndex, realMoveMasks);
|
||||
if(!nextParentIndex)
|
||||
return false;
|
||||
|
||||
parentIndex = *nextParentIndex;
|
||||
node = getAINode(node->theNodeBefore);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AIPath::AIPath()
|
||||
|
||||
@@ -288,7 +288,17 @@ public:
|
||||
}
|
||||
|
||||
void calculateTownPortalTeleportations(std::vector<CGPathNode *> & neighbours);
|
||||
void fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const;
|
||||
|
||||
using RealMoveMasksByHero = std::map<const CGHeroInstance *, uint64_t>;
|
||||
|
||||
STRONG_INLINE bool isRealMovementNode(const AIPathNode * node) const
|
||||
{
|
||||
return node && node->actor && node->actor->hero && node->coord != node->actor->hero->visitablePos();
|
||||
}
|
||||
|
||||
// Reconstructs an AIPath by walking theNodeBefore / chainOther, appending branch nodes first and linking them via parentIndex
|
||||
// Returns false when reconstruction would assign conflicting real-move chainMasks to the same hero
|
||||
bool tryReconstructChainInfo(const AIPathNode * node, AIPath & path, int & parentIndex, RealMoveMasksByHero & realMoveMasks) const;
|
||||
|
||||
template<typename Fn>
|
||||
void iterateValidNodes(const int3 & pos, EPathfindingLayer layer, Fn fn)
|
||||
|
||||
Reference in New Issue
Block a user