From 9ab44b950a20db85137e98340ed0b3bb9edb2495 Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Wed, 8 Aug 2018 22:24:18 +0300 Subject: [PATCH] AI: pathfinder rework, rules refactored a bit. --- lib/CPathfinder.cpp | 255 +++++++++++++++++++++++++++----------------- lib/CPathfinder.h | 60 ++++++++--- 2 files changed, 203 insertions(+), 112 deletions(-) diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index f6e0c3757..95f329030 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -30,18 +30,18 @@ CNeighbourFinder::CNeighbourFinder() } std::vector CNeighbourFinder::calculateNeighbours( - CPathNodeInfo & source, - CPathfinderHelper * pathfinderHelper, - CNodeHelper * nodeHelper) const + CPathNodeInfo & source, + CPathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const { std::vector neighbours; - auto accessibleNeighbourTiles = getNeighbourTiles(source, pathfinderHelper); + auto accessibleNeighbourTiles = getNeighbourTiles(source, pathfinderConfig, pathfinderHelper); for(auto & neighbour : accessibleNeighbourTiles) { for(EPathfindingLayer i = EPathfindingLayer::LAND; i <= EPathfindingLayer::AIR; i.advance(1)) { - auto node = nodeHelper->getNode(neighbour, i); + auto node = pathfinderConfig->nodeHelper->getNode(neighbour, i); if(node->accessible == CGPathNode::NOT_SET) continue; @@ -54,16 +54,16 @@ std::vector CNeighbourFinder::calculateNeighbours( } std::vector CNeighbourFinder::calculateTeleportations( - CPathNodeInfo & source, - CPathfinderHelper * pathfinderHelper, - CNodeHelper * nodeHelper) const + CPathNodeInfo & source, + CPathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const { std::vector neighbours; - auto accessibleExits = getTeleportExits(source, pathfinderHelper); + auto accessibleExits = getTeleportExits(source, pathfinderConfig, pathfinderHelper); for(auto & neighbour : accessibleExits) { - auto node = nodeHelper->getNode(neighbour, source.node->layer); + auto node = pathfinderConfig->nodeHelper->getNode(neighbour, source.node->layer); neighbours.push_back(node); } @@ -71,7 +71,10 @@ std::vector CNeighbourFinder::calculateTeleportations( return neighbours; } -std::vector CNeighbourFinder::getNeighbourTiles(CPathNodeInfo & source, CPathfinderHelper * pathfinderHelper) const +std::vector CNeighbourFinder::getNeighbourTiles( + CPathNodeInfo & source, + CPathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const { std::vector neighbourTiles; @@ -139,36 +142,92 @@ PathfinderOptions::PathfinderOptions() originalMovementRules = settings["pathfinder"]["originalMovementRules"].Bool(); } +class CMovementCostRule : public IPathfindingRule +{ +public: + virtual void process( + CPathNodeInfo & source, + CDestinationNodeInfo & destination, + CPathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) override + { + int turnAtNextTile = destination.turn, moveAtNextTile = destination.movementLeft; + int cost = pathfinderHelper->getMovementCost(source, destination, moveAtNextTile); + int remains = moveAtNextTile - cost; + if(remains < 0) + { + //occurs rarely, when hero with low movepoints tries to leave the road + pathfinderHelper->updateTurnInfo(++turnAtNextTile); + moveAtNextTile = pathfinderHelper->getMaxMovePoints(destination.node->layer); + cost = pathfinderHelper->getMovementCost(source, destination, moveAtNextTile); //cost must be updated, movement points changed :( + remains = moveAtNextTile - cost; + } + if(destination.action == CGPathNode::EMBARK || destination.action == CGPathNode::DISEMBARK) + { + /// FREE_SHIP_BOARDING bonus only remove additional penalty + /// land <-> sail transition still cost movement points as normal movement + remains = pathfinderHelper->movementPointsAfterEmbark(moveAtNextTile, cost, destination.action - 1); + } + + destination.turn = turnAtNextTile; + destination.movementLeft = remains; + + if(destination.isBetterWay() && + ((source.node->turns == turnAtNextTile && remains) || pathfinderHelper->passOneTurnLimitCheck(source))) + { + assert(destination.node != source.node->theNodeBefore); //two tiles can't point to each other + destination.node->moveRemains = remains; + destination.node->turns = turnAtNextTile; + destination.node->theNodeBefore = source.node; + destination.node->action = destination.action; + + return; + } + + destination.blocked = true; + } +}; + +CPathfinderConfig::CPathfinderConfig( + std::shared_ptr nodeHelper, + std::shared_ptr neighbourFinder, + std::vector> rules) + : nodeHelper(nodeHelper), neighbourFinder(neighbourFinder), rules(rules), options() +{ +} CPathfinder::CPathfinder( CPathsInfo & _out, CGameState * _gs, const CGHeroInstance * _hero) - :CPathfinder( + : CPathfinder( _gs, _hero, - std::make_shared(_out, _hero), - std::make_shared()) + std::make_shared( + std::make_shared(_out, _hero), + std::make_shared(), + std::vector>{ + std::make_shared(), + std::make_shared() + })) { } CPathfinder::CPathfinder( CGameState * _gs, const CGHeroInstance * _hero, - std::shared_ptr nodeHelper, - std::shared_ptr neighbourFinder) + std::shared_ptr config) : CGameInfoCallback(_gs, boost::optional()) , hero(_hero) , FoW(getPlayerTeam(hero->tempOwner)->fogOfWarMap), patrolTiles({}) - , nodeHelper(nodeHelper) - , neighbourFinder(neighbourFinder) + , config(config) , source() , destination() { assert(hero); assert(hero == getHero(hero->id)); - hlp = make_unique(_gs, hero, options); + hlp = make_unique(_gs, hero, config->options); initializePatrol(); initializeGraph(); @@ -176,40 +235,10 @@ CPathfinder::CPathfinder( void CPathfinder::calculatePaths() { - auto passOneTurnLimitCheck = [&]() -> bool - { - if(!options.oneTurnSpecialLayersLimit) - return true; - - if(source.node->layer == ELayer::WATER) - return false; - if(source.node->layer == ELayer::AIR) - { - if(options.originalMovementRules && source.node->accessible == CGPathNode::ACCESSIBLE) - return true; - else - return false; - } - - return true; - }; - - auto isBetterWay = [&](int remains, int turn) -> bool - { - if(destination.node->turns == 0xff) //we haven't been here before - return true; - else if(destination.node->turns > turn) - return true; - else if(destination.node->turns >= turn && destination.node->moveRemains < remains) //this route is faster - return true; - - return false; - }; - //logGlobal->info("Calculating paths for hero %s (adress %d) of player %d", hero->name, hero , hero->tempOwner); //initial tile - set cost on 0 and add to the queue - CGPathNode * initialNode = nodeHelper->getInitialNode(); + CGPathNode * initialNode = config->nodeHelper->getInitialNode(); if(!isInTheMap(initialNode->coord)/* || !gs->map->isInTheMap(dest)*/) //check input { @@ -236,7 +265,7 @@ void CPathfinder::calculatePaths() { hlp->updateTurnInfo(++turn); movement = hlp->getMaxMovePoints(source.node->layer); - if(!passOneTurnLimitCheck()) + if(!hlp->passOneTurnLimitCheck(source)) continue; } @@ -245,7 +274,7 @@ void CPathfinder::calculatePaths() source.objectRelations = gs->getPlayerRelations(hero->tempOwner, source.nodeObject->tempOwner); //add accessible neighbouring nodes to the queue - auto neighbourNodes = neighbourFinder->calculateNeighbours(source, hlp.get(), nodeHelper.get()); + auto neighbourNodes = config->neighbourFinder->calculateNeighbours(source, config.get(), hlp.get()); for(CGPathNode * neighbour : neighbourNodes) { destination.setNode(gs, neighbour); @@ -275,41 +304,20 @@ void CPathfinder::calculatePaths() continue; destination.action = getDestAction(); + destination.turn = turn; + destination.movementLeft = movement; + + for(auto rule : config->rules) + { + rule->process(source, destination, config.get(), hlp.get()); - int turnAtNextTile = turn, moveAtNextTile = movement; - int cost = hlp->getMovementCost(source, destination, moveAtNextTile); - int remains = moveAtNextTile - cost; - if(remains < 0) - { - //occurs rarely, when hero with low movepoints tries to leave the road - hlp->updateTurnInfo(++turnAtNextTile); - moveAtNextTile = hlp->getMaxMovePoints(destination.node->layer); - cost = hlp->getMovementCost(source, destination, moveAtNextTile); //cost must be updated, movement points changed :( - remains = moveAtNextTile - cost; - } - if(destination.action == CGPathNode::EMBARK || destination.action == CGPathNode::DISEMBARK) - { - /// FREE_SHIP_BOARDING bonus only remove additional penalty - /// land <-> sail transition still cost movement points as normal movement - remains = hero->movementPointsAfterEmbark(moveAtNextTile, cost, destination.action - 1, hlp->getTurnInfo()); - cost = moveAtNextTile - remains; + if(destination.blocked) + break; } - if(isBetterWay(remains, turnAtNextTile) && - ((source.node->turns == turnAtNextTile && remains) || passOneTurnLimitCheck())) - { - assert(destination.node != source.node->theNodeBefore); //two tiles can't point to each other - destination.node->moveRemains = remains; - destination.node->turns = turnAtNextTile; - destination.node->theNodeBefore = source.node; - destination.node->action = destination.action; - - CMovementAfterDestinationRule rl = CMovementAfterDestinationRule(); - rl.process(hlp.get(), source, destination); - - if(!destination.furtherProcessingImpossible) - pq.push(destination.node); - } + if(!destination.blocked && !destination.furtherProcessingImpossible) + pq.push(destination.node); + } //neighbours loop //just add all passable teleport exits @@ -319,7 +327,7 @@ void CPathfinder::calculatePaths() if(!source.isNodeObjectVisitable() || patrolState == PATROL_RADIUS) continue; - auto teleportationNodes = neighbourFinder->calculateTeleportations(source, hlp.get(), nodeHelper.get()); + auto teleportationNodes = config->neighbourFinder->calculateTeleportations(source, config.get(), hlp.get()); for(CGPathNode * teleportNode : teleportationNodes) { if(teleportNode->locked) @@ -333,11 +341,13 @@ void CPathfinder::calculatePaths() continue; destination.setNode(gs, teleportNode); + destination.turn = turn; + destination.movementLeft = movement; - if(isBetterWay(movement, turn)) + if(destination.isBetterWay()) { - destination.node->moveRemains = movement; - destination.node->turns = turn; + destination.node->moveRemains = destination.movementLeft; + destination.node->turns = destination.turn; destination.node->theNodeBefore = source.node; destination.node->action = getTeleportDestAction(); if(destination.node->action == CGPathNode::TELEPORT_NORMAL) @@ -389,6 +399,7 @@ std::vector CPathfinderHelper::getCastleGates(CPathNodeInfo & source) cons std::vector CNeighbourFinder::getTeleportExits( CPathNodeInfo & source, + CPathfinderConfig * pathfinderConfig, CPathfinderHelper * pathfinderHelper) const { std::vector teleportationExits; @@ -443,7 +454,7 @@ bool CPathfinder::isLayerTransitionPossible(const ELayer destLayer) const case ELayer::LAND: if(destLayer == ELayer::AIR) { - if(!options.lightweightFlyingMode || isSourceInitialPosition()) + if(!config->options.lightweightFlyingMode || isSourceInitialPosition()) return true; } else if(destLayer == ELayer::SAIL) @@ -503,7 +514,7 @@ bool CPathfinder::isLayerTransitionPossible() const break; case ELayer::AIR: - if(options.originalMovementRules) + if(config->options.originalMovementRules) { if((source.node->accessible != CGPathNode::ACCESSIBLE && source.node->accessible != CGPathNode::VISITABLE) && @@ -547,7 +558,7 @@ bool CPathfinder::isMovementToDestPossible() const return false; if(isSourceGuarded()) { - if(!(options.originalMovementRules && source.node->layer == ELayer::AIR) && + if(!(config->options.originalMovementRules && source.node->layer == ELayer::AIR) && !isDestinationGuardian()) // Can step into tile of guard { return false; @@ -594,7 +605,11 @@ bool CPathfinder::isMovementToDestPossible() const return true; } -void CMovementAfterDestinationRule::process(CPathfinderHelper * pathfinderHelper, CPathNodeInfo & source, CDestinationNodeInfo & destination) +void CMovementAfterDestinationRule::process( + CPathNodeInfo & source, + CDestinationNodeInfo & destination, + CPathfinderConfig * config, + CPathfinderHelper * pathfinderHelper) { switch(destination.action) { @@ -707,12 +722,12 @@ CGPathNode::ENodeAction CPathfinder::getDestAction() const } else if(isDestinationGuardian()) action = CGPathNode::BATTLE; - else if(destination.nodeObject->blockVisit && !(options.useCastleGate && destination.nodeObject->ID == Obj::TOWN)) + else if(destination.nodeObject->blockVisit && !(config->options.useCastleGate && destination.nodeObject->ID == Obj::TOWN)) action = CGPathNode::BLOCKING_VISIT; if(action == CGPathNode::NORMAL) { - if(options.originalMovementRules && isDestinationGuarded()) + if(config->options.originalMovementRules && isDestinationGuarded()) action = CGPathNode::BATTLE; else action = CGPathNode::VISIT; @@ -744,7 +759,7 @@ CGPathNode::ENodeAction CPathfinder::getTeleportDestAction() const bool CPathfinder::isSourceInitialPosition() const { - return source.node->coord == nodeHelper->getInitialNode()->coord; + return source.node->coord == config->nodeHelper->getInitialNode()->coord; } bool CPathfinder::isSourceGuarded() const @@ -795,7 +810,7 @@ void CPathfinder::initializeGraph() { auto updateNode = [&](int3 pos, ELayer layer, const TerrainTile * tinfo) { - auto node = nodeHelper->getNode(pos, layer); + auto node = config->nodeHelper->getNode(pos, layer); auto accessibility = evaluateAccessibility(pos, tinfo, layer); node->update(pos, layer, accessibility); }; @@ -816,15 +831,15 @@ void CPathfinder::initializeGraph() case ETerrainType::WATER: updateNode(pos, ELayer::SAIL, tinfo); - if(options.useFlying) + if(config->options.useFlying) updateNode(pos, ELayer::AIR, tinfo); - if(options.useWaterWalking) + if(config->options.useWaterWalking) updateNode(pos, ELayer::WATER, tinfo); break; default: updateNode(pos, ELayer::LAND, tinfo); - if(options.useFlying) + if(config->options.useFlying) updateNode(pos, ELayer::AIR, tinfo); break; } @@ -949,6 +964,35 @@ bool CPathfinderHelper::addTeleportWhirlpool(const CGWhirlpool * obj) const return options.useTeleportWhirlpool && hasBonusOfType(Bonus::WHIRLPOOL_PROTECTION) && obj; } +int CPathfinderHelper::getHeroMaxMovementPoints(EPathfindingLayer layer) +{ + return hero->maxMovePoints(layer); +} + +int CPathfinderHelper::movementPointsAfterEmbark(int movement, int turn, int action) +{ + return hero->movementPointsAfterEmbark(movement, turn, action, getTurnInfo()); +} + +bool CPathfinderHelper::passOneTurnLimitCheck(CPathNodeInfo & source) +{ + + if(!options.oneTurnSpecialLayersLimit) + return true; + + if(source.node->layer == EPathfindingLayer::WATER) + return false; + if(source.node->layer == EPathfindingLayer::AIR) + { + if(options.originalMovementRules && source.node->accessible == CGPathNode::ACCESSIBLE) + return true; + else + return false; + } + + return true; +} + TurnInfo::BonusCache::BonusCache(TBonusListPtr bl) { noTerrainPenalty.reserve(ETerrainType::ROCK); @@ -1365,6 +1409,19 @@ void CDestinationNodeInfo::setNode(CGameState * gs, CGPathNode * n, bool exclude action = CGPathNode::ENodeAction::UNKNOWN; } +bool CDestinationNodeInfo::isBetterWay() +{ + if(node->turns == 0xff) //we haven't been here before + return true; + else if(node->turns > turn) + return true; + else if(node->turns >= turn && node->moveRemains < movementLeft) //this route is faster + return true; + + return false; + +} + bool CPathNodeInfo::isNodeObjectVisitable() const { /// Hero can't visit objects while walking on water or flying diff --git a/lib/CPathfinder.h b/lib/CPathfinder.h index 1f98e4c2f..21e4a21de 100644 --- a/lib/CPathfinder.h +++ b/lib/CPathfinder.h @@ -115,12 +115,16 @@ struct DLL_LINKAGE CPathNodeInfo struct DLL_LINKAGE CDestinationNodeInfo : public CPathNodeInfo { CGPathNode::ENodeAction action; + int turn; + int movementLeft; bool furtherProcessingImpossible; bool blocked; CDestinationNodeInfo(); virtual void setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject = false) override; + + virtual bool isBetterWay(); }; class CNodeHelper @@ -132,17 +136,26 @@ public: class CPathfinderHelper; class CPathfinder; +class CPathfinderConfig; class IPathfindingRule { public: - virtual void process(CPathfinderHelper * pathfinderHelper, CPathNodeInfo & source, CDestinationNodeInfo & destination) = 0; + virtual void process( + CPathNodeInfo & source, + CDestinationNodeInfo & destination, + CPathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) = 0; }; class CMovementAfterDestinationRule : public IPathfindingRule { public: - virtual void process(CPathfinderHelper * pathfinderHelper, CPathNodeInfo & source, CDestinationNodeInfo & destination) override; + virtual void process( + CPathNodeInfo & source, + CDestinationNodeInfo & destination, + CPathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) override; }; class CNeighbourFinder @@ -151,17 +164,23 @@ public: CNeighbourFinder(); virtual std::vector calculateNeighbours( CPathNodeInfo & source, - CPathfinderHelper * pathfinderHelper, - CNodeHelper * nodeHelper) const; + CPathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const; virtual std::vector calculateTeleportations( CPathNodeInfo & source, - CPathfinderHelper * pathfinderHelper, - CNodeHelper * nodeHelper) const; + CPathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const; protected: - std::vector getNeighbourTiles(CPathNodeInfo & source, CPathfinderHelper * pathfinderHelper) const; - std::vector getTeleportExits(CPathNodeInfo & source, CPathfinderHelper * pathfinderHelper) const; + std::vector getNeighbourTiles( + CPathNodeInfo & source, + CPathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const; + + std::vector getTeleportExits(CPathNodeInfo & source, + CPathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const; }; struct DLL_LINKAGE PathfinderOptions @@ -215,6 +234,20 @@ struct DLL_LINKAGE PathfinderOptions PathfinderOptions(); }; +class CPathfinderConfig +{ +public: + std::shared_ptr nodeHelper; + std::shared_ptr neighbourFinder; + std::vector> rules; + PathfinderOptions options; + + CPathfinderConfig( + std::shared_ptr nodeHelper, + std::shared_ptr neighbourFinder, + std::vector> rules); +}; + class CPathfinder : private CGameInfoCallback { public: @@ -224,20 +257,17 @@ public: CPathfinder( CGameState * _gs, const CGHeroInstance * _hero, - std::shared_ptr nodeHelper, - std::shared_ptr neighbourFinder); + std::shared_ptr config); void calculatePaths(); //calculates possible paths for hero, uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists private: typedef EPathfindingLayer ELayer; - PathfinderOptions options; const CGHeroInstance * hero; const std::vector > > &FoW; std::unique_ptr hlp; - std::shared_ptr nodeHelper; - std::shared_ptr neighbourFinder; + std::shared_ptr config; enum EPatrolState { PATROL_NONE = 0, @@ -362,4 +392,8 @@ public: checkLast ); } + + int getHeroMaxMovementPoints(EPathfindingLayer layer); + int movementPointsAfterEmbark(int movement, int cost, int action); + bool passOneTurnLimitCheck(CPathNodeInfo & source); };