diff --git a/AI/Nullkiller2/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller2/Pathfinding/AINodeStorage.cpp index 2291c2f79..0c1197056 100644 --- a/AI/Nullkiller2/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller2/Pathfinding/AINodeStorage.cpp @@ -1442,8 +1442,18 @@ void AINodeStorage::calculateChainInfo(std::vector & 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 & 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 + { + 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(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() diff --git a/AI/Nullkiller2/Pathfinding/AINodeStorage.h b/AI/Nullkiller2/Pathfinding/AINodeStorage.h index ca9842feb..2a89d6b14 100644 --- a/AI/Nullkiller2/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller2/Pathfinding/AINodeStorage.h @@ -288,7 +288,17 @@ public: } void calculateTownPortalTeleportations(std::vector & neighbours); - void fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const; + + using RealMoveMasksByHero = std::map; + + 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 void iterateValidNodes(const int3 & pos, EPathfindingLayer layer, Fn fn)