diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 28a24514d..f8d2dc5cf 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -132,7 +132,8 @@ std::vector AINodeStorage::getInitialNodes() initialNode->turns = actor->initialTurn; initialNode->moveRemains = actor->initialMovement; initialNode->danger = 0; - initialNode->cost = 0.0; + initialNode->cost = actor->initialTurn; + initialNode->action = CGPathNode::ENodeAction::NORMAL; if(actor->isMovable) { @@ -175,6 +176,16 @@ void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInf { dstNode->specialAction->applyOnDestination(dstNode->actor->hero, destination, source, dstNode, srcNode); } + +#ifdef VCMI_TRACE_PATHFINDER_EX + logAi->trace( + "Commited %s -> %s, cost: %f, hero: %s, mask: %i", + source.coord.toString(), + destination.coord.toString(), + destination.cost, + dstNode->actor->hero->name, + dstNode->actor->chainMask); +#endif }); } @@ -194,6 +205,7 @@ void AINodeStorage::commit( destination->manaCost = source->manaCost; destination->danger = source->danger; destination->theNodeBefore = source->theNodeBefore; + destination->chainOther = nullptr; } std::vector AINodeStorage::calculateNeighbours( @@ -227,33 +239,53 @@ bool AINodeStorage::calculateHeroChain() heroChainPass = true; heroChain.resize(0); + std::vector buffer; + + buffer.reserve(NUM_CHAINS); + foreach_tile_pos([&](const int3 & pos) { auto layer = EPathfindingLayer::LAND; auto chains = nodes[pos.x][pos.y][pos.z][layer]; + buffer.resize(0); + for(AIPathNode & node : chains) { - if(node.locked && node.turns < 1) - addHeroChain(&node); + if(node.turns <= heroChainMaxTurns && node.action != CGPathNode::ENodeAction::UNKNOWN) + buffer.push_back(&node); + } + + for(AIPathNode * node : buffer) + { + addHeroChain(node, buffer); } }); return heroChain.size(); } -void AINodeStorage::addHeroChain(AIPathNode * srcNode) +void AINodeStorage::addHeroChain(AIPathNode * srcNode, std::vector variants) { - auto chains = nodes[srcNode->coord.x][srcNode->coord.y][srcNode->coord.z][srcNode->layer]; - - for(AIPathNode & node : chains) + for(AIPathNode * node : variants) { - if(!node.locked || !node.actor || node.action == CGPathNode::ENodeAction::UNKNOWN && node.actor->hero) + if(node == srcNode || !node->actor || node->turns > heroChainMaxTurns + || node->action == CGPathNode::ENodeAction::UNKNOWN && node->actor->hero) { continue; } - addHeroChain(srcNode, &node); - addHeroChain(&node, srcNode); +#ifdef VCMI_TRACE_PATHFINDER_EX + logAi->trace( + "Thy exchange %s[%i] -> %s[%i] at %s", + node->actor->hero->name, + node->actor->chainMask, + srcNode->actor->hero->name, + srcNode->actor->chainMask, + srcNode->coord.toString()); +#endif + + addHeroChain(srcNode, node); + //addHeroChain(&node, srcNode); } } @@ -261,37 +293,65 @@ void AINodeStorage::addHeroChain(AIPathNode * carrier, AIPathNode * other) { if(carrier->actor->canExchange(other->actor)) { +#ifdef VCMI_TRACE_PATHFINDER_EX + logAi->trace( + "Exchange allowed %s[%i] -> %s[%i] at %s", + other->actor->hero->name, + other->actor->chainMask, + carrier->actor->hero->name, + carrier->actor->chainMask, + carrier->coord.toString()); +#endif + 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) + { +#ifdef VCMI_TRACE_PATHFINDER_EX + logAi->trace("Exchange at %s is ineficient. Blocked.", carrier->coord.toString()); +#endif return; + } auto newActor = carrier->actor->exchange(other->actor); auto chainNodeOptional = getOrCreateNode(carrier->coord, carrier->layer, newActor); if(!chainNodeOptional) + { +#ifdef VCMI_TRACE_PATHFINDER_EX + logAi->trace("Exchange at %s can not allocate node. Blocked.", carrier->coord.toString()); +#endif return; + } auto chainNode = chainNodeOptional.get(); - if(chainNode->locked) - return; - -#ifdef VCMI_TRACE_PATHFINDER - logAi->trace("Hero exhange at %s, %s -> %s", carrier->coord.toString(), other->actor->hero->name, carrier->actor->hero->name); + if(chainNode->action != CGPathNode::ENodeAction::UNKNOWN) + { +#ifdef VCMI_TRACE_PATHFINDER_EX + logAi->trace("Exchange at %s node is already in use. Blocked.", carrier->coord.toString()); #endif + return; + } - commitExchange(chainNode, carrier, other); - heroChain.push_back(chainNode); + if(commitExchange(chainNode, carrier, other)) + { +#ifdef VCMI_TRACE_PATHFINDER_EX + logAi->trace( + "Chain accepted at %s %s -> %s, mask %i, cost %f", + chainNode->coord.toString(), + other->actor->hero->name, + chainNode->actor->hero->name, + chainNode->actor->chainMask, + chainNode->cost); +#endif + heroChain.push_back(chainNode); + } } } -void AINodeStorage::commitExchange( +bool AINodeStorage::commitExchange( AIPathNode * exchangeNode, AIPathNode * carrierParentNode, AIPathNode * otherParentNode) const @@ -302,7 +362,7 @@ void AINodeStorage::commitExchange( auto armyLoss = carrierParentNode->armyLoss + otherParentNode->armyLoss; auto turns = carrierParentNode->turns; - auto cost = carrierParentNode->cost; + auto cost = carrierParentNode->cost + otherParentNode->cost / 1000.0; auto movementLeft = carrierParentNode->moveRemains; if(carrierParentNode->turns < otherParentNode->turns) @@ -312,27 +372,24 @@ void AINodeStorage::commitExchange( + carrierParentNode->moveRemains / (float)moveRemains; turns = otherParentNode->turns; - cost = waitingCost; + 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", - exchangeNode->coord.toString(), - carrierActor->hero->name, - cost); + { +#ifdef VCMI_TRACE_PATHFINDER_EX + logAi->trace("Exchange at %s is is not effective enough. %f < %f", exchangeNode->coord.toString(), exchangeNode->cost, cost); #endif + return false; + } commit(exchangeNode, carrierParentNode, carrierParentNode->action, turns, movementLeft, cost); - exchangeNode->theNodeBefore = carrierParentNode; exchangeNode->chainOther = otherParentNode; exchangeNode->armyLoss = armyLoss; - exchangeNode->manaCost = carrierParentNode->manaCost; + + return true; } const CGHeroInstance * AINodeStorage::getHero(const CGPathNode * node) const @@ -538,6 +595,7 @@ std::vector AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand) path.heroArmy = node.actor->creatureSet; path.armyLoss = node.armyLoss; path.targetObjectDanger = evaluateDanger(pos, path.targetHero); + path.chainMask = node.actor->chainMask; fillChainInfo(&node, path); @@ -551,22 +609,26 @@ void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path) const { while(node != nullptr) { - if(!node->actor->hero || node->coord == node->actor->hero->visitablePos()) + if(!node->actor->hero) 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); + if(node->actor->hero->visitablePos() != node->coord) + { + 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; + node = getAINode(node->theNodeBefore); } } @@ -586,6 +648,11 @@ int3 AIPath::firstTileToGet() const return int3(-1, -1, -1); } +const AIPathNodeInfo & AIPath::firstNode() const +{ + return nodes.back(); +} + uint64_t AIPath::getPathDanger() const { if(nodes.size()) diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index acf1297d8..0d4cc28fc 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -11,6 +11,7 @@ #pragma once #define VCMI_TRACE_PATHFINDER +#define VCMI_TRACE_PATHFINDER_EX #include "../../../lib/CPathfinder.h" #include "../../../lib/mapObjects/CGHeroInstance.h" @@ -19,6 +20,7 @@ #include "../Goals/AbstractGoal.h" #include "Actions/ISpecialAction.h" #include "Actors.h" +#include #define VCMI_TRACE_PATHFINDER @@ -49,6 +51,7 @@ struct AIPath uint64_t armyLoss; const CGHeroInstance * targetHero; const CCreatureSet * heroArmy; + uint64_t chainMask; AIPath(); @@ -60,6 +63,8 @@ struct AIPath int3 firstTileToGet() const; + const AIPathNodeInfo & firstNode() const; + float movementCost() const; uint64_t getHeroStrength() const; @@ -78,6 +83,7 @@ private: std::vector> actors; std::vector heroChain; bool heroChainPass; // true if we need to calculate hero chain + int heroChainMaxTurns = 0; 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. @@ -123,7 +129,7 @@ public: private: STRONG_INLINE void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility); - void addHeroChain(AIPathNode * srcNode); + void addHeroChain(AIPathNode * srcNode, std::vector variants); void addHeroChain(AIPathNode * carrier, AIPathNode * other); void calculateTownPortalTeleportations(const PathNodeInfo & source, std::vector & neighbours); void fillChainInfo(const AIPathNode * node, AIPath & path) const; @@ -135,7 +141,7 @@ private: int movementLeft, float cost) const; - void AINodeStorage::commitExchange( + bool AINodeStorage::commitExchange( AIPathNode * exchangeNode, AIPathNode * carrierParentNode, AIPathNode * otherParentNode) const; diff --git a/AI/Nullkiller/Pathfinding/AIPathfinder.cpp b/AI/Nullkiller/Pathfinding/AIPathfinder.cpp index ead99a49b..a5c3282e9 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinder.cpp +++ b/AI/Nullkiller/Pathfinding/AIPathfinder.cpp @@ -51,18 +51,20 @@ void AIPathfinder::updatePaths(std::vector heroes, bool useHeroChain) } logAi->debug("Recalculate all paths"); + int pass = 0; storage->clear(); storage->setHeroes(heroes, ai); auto config = std::make_shared(cb, ai, storage); - cb->calculatePaths(config); - while(useHeroChain && storage->calculateHeroChain()) - { - config = std::make_shared(cb, ai, storage); + do { + logAi->trace("Recalculate paths pass %" PRIi32, pass++); cb->calculatePaths(config); - } + + logAi->trace("Recalculate chain pass %" PRIi32, pass); + useHeroChain = useHeroChain && storage->calculateHeroChain(); + } while(useHeroChain); } void AIPathfinder::updatePaths(const HeroPtr & hero) diff --git a/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp b/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp index d52b19e38..7547a2500 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp +++ b/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp @@ -13,7 +13,6 @@ #include "Rules/AIMovementAfterDestinationRule.h" #include "Rules/AIMovementToDestinationRule.h" #include "Rules/AIPreviousNodeRule.h" -#include "Rules/AIMovementCostRule.h" namespace AIPathfinding { diff --git a/AI/Nullkiller/Pathfinding/Actors.cpp b/AI/Nullkiller/Pathfinding/Actors.cpp index 15a7ba2f1..dfbe7e1d9 100644 --- a/AI/Nullkiller/Pathfinding/Actors.cpp +++ b/AI/Nullkiller/Pathfinding/Actors.cpp @@ -82,21 +82,16 @@ void ChainActor::setBaseActor(HeroActor * base) armyValue = base->armyValue; chainMask = base->chainMask; creatureSet = base->creatureSet; + isMovable = base->isMovable; } void HeroActor::setupSpecialActors() { auto allActors = std::vector{ this }; - for(int i = 1; i <= SPECIAL_ACTORS_COUNT; i++) + for(ChainActor & specialActor : specialActors) { - ChainActor & specialActor = specialActors[i - 1]; - specialActor.setBaseActor(this); - specialActor.allowBattle = (i & 1) > 0; - specialActor.allowSpellCast = (i & 2) > 0; - specialActor.allowUseResources = (i & 4) > 0; - allActors.push_back(&specialActor); } @@ -104,6 +99,9 @@ void HeroActor::setupSpecialActors() { ChainActor * actor = allActors[i]; + actor->allowBattle = (i & 1) > 0; + actor->allowSpellCast = (i & 2) > 0; + actor->allowUseResources = (i & 4) > 0; actor->battleActor = allActors[i | 1]; actor->castActor = allActors[i | 2]; actor->resourceActor = allActors[i | 4]; @@ -180,7 +178,7 @@ CCreatureSet * HeroActor::pickBestCreatures(const CCreatureSet * army1, const CC { for(auto & i : armyPtr->Slots()) { - creToPower[i.second->type] += i.second->getPower(); + creToPower[i.second->type] += i.second->count; } } //TODO - consider more than just power (ie morale penalty, hero specialty in certain stacks, etc) @@ -193,7 +191,7 @@ CCreatureSet * HeroActor::pickBestCreatures(const CCreatureSet * army1, const CC typedef const std::pair & CrePowerPair; auto creIt = boost::max_element(creToPower, [](CrePowerPair lhs, CrePowerPair rhs) { - return lhs.second < rhs.second; + return lhs.first->AIValue * lhs.second < rhs.first->AIValue * rhs.second; }); target->addToSlot(SlotID(i), creIt->first->idNumber, TQuantity(creIt->second)); diff --git a/AI/Nullkiller/Pathfinding/PathfindingManager.cpp b/AI/Nullkiller/Pathfinding/PathfindingManager.cpp index ea9fc1782..fb42cc14d 100644 --- a/AI/Nullkiller/Pathfinding/PathfindingManager.cpp +++ b/AI/Nullkiller/Pathfinding/PathfindingManager.cpp @@ -125,14 +125,23 @@ Goals::TGoalVec PathfindingManager::findPaths( for(auto path : chainInfo) { - if(hero && hero.get() != path.targetHero) + if(hero && hero.get() != path.targetHero || path.nodes.empty()) continue; - int3 firstTileToGet = path.firstTileToGet(); + const AIPathNodeInfo & firstNode = path.firstNode(); + int3 firstTileToGet = firstNode.coord; + #ifdef VCMI_TRACE_PATHFINDER - logAi->trace("Path found size=%i, first tile=%s", path.nodes.size(), firstTileToGet.toString()); + std::stringstream str; + + str << "Path found "; + + for(auto node : path.nodes) + str << node.targetHero->name << "->" << node.coord.toString() << "; "; + + logAi->trace(str.str()); #endif - if(firstTileToGet.valid() && ai->isTileNotReserved(hero.get(), firstTileToGet)) + if(ai->isTileNotReserved(hero.get(), firstTileToGet)) { danger = path.getTotalDanger(hero); @@ -148,7 +157,7 @@ Goals::TGoalVec PathfindingManager::findPaths( { solution = dest == firstTileToGet ? doVisitTile(firstTileToGet) - : clearWayTo(hero, firstTileToGet); + : clearWayTo(firstNode.targetHero, firstTileToGet); } if(solution->invalid()) @@ -161,7 +170,13 @@ Goals::TGoalVec PathfindingManager::findPaths( 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()); + logAi->trace("It's safe for %s to visit tile %s with danger %s, loss %s, army strength %s, goal %s", + hero->name, + dest.toString(), + std::to_string(danger), + std::to_string(path.armyLoss), + std::to_string(path.heroArmy->getArmyStrength()), + solution->name()); #endif result.push_back(solution); @@ -210,9 +225,7 @@ Goals::TSubgoal PathfindingManager::clearWayTo(HeroPtr hero, int3 firstTileToGet { if(topObj != hero.get(true)) //the hero we want to free { - logAi->error("%s stands in the way of %s", topObj->getObjectName(), hero->getObjectName()); - - return sptr(Goals::Invalid()); + logAi->warn("%s stands in the way of %s", topObj->getObjectName(), hero->getObjectName()); } } @@ -240,10 +253,10 @@ Goals::TSubgoal PathfindingManager::clearWayTo(HeroPtr hero, int3 firstTileToGet return sptr(Goals::VisitTile(firstTileToGet).sethero(hero).setisAbstract(true)); } -void PathfindingManager::updatePaths(std::vector heroes) +void PathfindingManager::updatePaths(std::vector heroes, bool useHeroChain) { logAi->debug("AIPathfinder has been reseted."); - pathfinder->updatePaths(heroes); + pathfinder->updatePaths(heroes, useHeroChain); } void PathfindingManager::updatePaths(const HeroPtr & hero) diff --git a/AI/Nullkiller/Pathfinding/PathfindingManager.h b/AI/Nullkiller/Pathfinding/PathfindingManager.h index f86e41b13..8edc3be36 100644 --- a/AI/Nullkiller/Pathfinding/PathfindingManager.h +++ b/AI/Nullkiller/Pathfinding/PathfindingManager.h @@ -20,7 +20,7 @@ public: virtual void init(CPlayerSpecificInfoCallback * CB) = 0; virtual void setAI(VCAI * AI) = 0; - virtual void updatePaths(std::vector heroes) = 0; + virtual void updatePaths(std::vector heroes, bool useHeroChain = false) = 0; virtual void updatePaths(const HeroPtr & hero) = 0; virtual Goals::TGoalVec howToVisitTile(const HeroPtr & hero, const int3 & tile, bool allowGatherArmy = true) const = 0; virtual Goals::TGoalVec howToVisitObj(const HeroPtr & hero, ObjectIdRef obj, bool allowGatherArmy = true) const = 0; @@ -47,7 +47,7 @@ public: Goals::TGoalVec howToVisitTile(const int3 & tile, bool allowGatherArmy = true) const override; Goals::TGoalVec howToVisitObj(ObjectIdRef obj, bool allowGatherArmy = true) const override; std::vector getPathsToTile(const HeroPtr & hero, const int3 & tile) const override; - void updatePaths(std::vector heroes) override; + void updatePaths(std::vector heroes, bool useHeroChain = false) override; void updatePaths(const HeroPtr & hero) override; STRONG_INLINE diff --git a/AI/Nullkiller/Pathfinding/Rules/AIPreviousNodeRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AIPreviousNodeRule.cpp index 480808626..04666b041 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIPreviousNodeRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AIPreviousNodeRule.cpp @@ -38,39 +38,10 @@ namespace AIPathfinding auto srcNode = nodeStorage->getAINode(source.node); - if(srcNode->specialAction) + if(srcNode->specialAction || srcNode->chainOther) { // 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 - } - }); } }