From 545484893832a37275b816d92a93ba9f6c9e3edd Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Wed, 6 Feb 2019 14:51:12 +0200 Subject: [PATCH] AI: extract pathfinding special actions and rules to separate files --- AI/VCAI/CMakeLists.txt | 15 + AI/VCAI/Pathfinding/AINodeStorage.cpp | 25 +- AI/VCAI/Pathfinding/AINodeStorage.h | 16 +- AI/VCAI/Pathfinding/AIPathfinderConfig.cpp | 482 +----------------- AI/VCAI/Pathfinding/Actions/BattleAction.cpp | 21 + AI/VCAI/Pathfinding/Actions/BattleAction.h | 30 ++ AI/VCAI/Pathfinding/Actions/BoatActions.cpp | 61 +++ AI/VCAI/Pathfinding/Actions/BoatActions.h | 72 +++ AI/VCAI/Pathfinding/Actions/ISpecialAction.h | 31 ++ .../Pathfinding/Actions/TownPortalAction.cpp | 24 + .../Pathfinding/Actions/TownPortalAction.h | 33 ++ .../Rules/AILayerTransitionRule.cpp | 154 ++++++ .../Pathfinding/Rules/AILayerTransitionRule.h | 52 ++ .../Rules/AIMovementAfterDestinationRule.cpp | 148 ++++++ .../Rules/AIMovementAfterDestinationRule.h | 36 ++ .../Rules/AIMovementToDestinationRule.cpp | 51 ++ .../Rules/AIMovementToDestinationRule.h | 35 ++ .../Pathfinding/Rules/AIPreviousNodeRule.cpp | 47 ++ .../Pathfinding/Rules/AIPreviousNodeRule.h | 35 ++ AI/VCAI/VCAI.vcxproj | 15 + AI/VCAI/VCAI.vcxproj.filters | 25 + 21 files changed, 893 insertions(+), 515 deletions(-) create mode 100644 AI/VCAI/Pathfinding/Actions/BattleAction.cpp create mode 100644 AI/VCAI/Pathfinding/Actions/BattleAction.h create mode 100644 AI/VCAI/Pathfinding/Actions/BoatActions.cpp create mode 100644 AI/VCAI/Pathfinding/Actions/BoatActions.h create mode 100644 AI/VCAI/Pathfinding/Actions/ISpecialAction.h create mode 100644 AI/VCAI/Pathfinding/Actions/TownPortalAction.cpp create mode 100644 AI/VCAI/Pathfinding/Actions/TownPortalAction.h create mode 100644 AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.cpp create mode 100644 AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.h create mode 100644 AI/VCAI/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp create mode 100644 AI/VCAI/Pathfinding/Rules/AIMovementAfterDestinationRule.h create mode 100644 AI/VCAI/Pathfinding/Rules/AIMovementToDestinationRule.cpp create mode 100644 AI/VCAI/Pathfinding/Rules/AIMovementToDestinationRule.h create mode 100644 AI/VCAI/Pathfinding/Rules/AIPreviousNodeRule.cpp create mode 100644 AI/VCAI/Pathfinding/Rules/AIPreviousNodeRule.h diff --git a/AI/VCAI/CMakeLists.txt b/AI/VCAI/CMakeLists.txt index d9400968b..9e0db96b7 100644 --- a/AI/VCAI/CMakeLists.txt +++ b/AI/VCAI/CMakeLists.txt @@ -12,6 +12,13 @@ set(VCAI_SRCS Pathfinding/AIPathfinder.cpp Pathfinding/AINodeStorage.cpp Pathfinding/PathfindingManager.cpp + Pathfinding/Actions/BattleAction.cpp + Pathfinding/Actions/BoatActions.cpp + Pathfinding/Actions/TownPortalAction.cpp + Pathfinding/Rules/AILayerTransitionRule.cpp + Pathfinding/Rules/AIMovementAfterDestinationRule.cpp + Pathfinding/Rules/AIMovementToDestinationRule.cpp + Pathfinding/Rules/AIPreviousNodeRule.cpp AIUtility.cpp AIhelper.cpp ResourceManager.cpp @@ -54,6 +61,14 @@ set(VCAI_HEADERS Pathfinding/AIPathfinder.h Pathfinding/AINodeStorage.h Pathfinding/PathfindingManager.h + Pathfinding/Actions/ISpecialAction.h + Pathfinding/Actions/BattleAction.h + Pathfinding/Actions/BoatActions.h + Pathfinding/Actions/TownPortalAction.h + Pathfinding/Rules/AILayerTransitionRule.h + Pathfinding/Rules/AIMovementAfterDestinationRule.h + Pathfinding/Rules/AIMovementToDestinationRule.h + Pathfinding/Rules/AIPreviousNodeRule.h AIUtility.h AIhelper.h ResourceManager.h diff --git a/AI/VCAI/Pathfinding/AINodeStorage.cpp b/AI/VCAI/Pathfinding/AINodeStorage.cpp index cbdc38091..401a44879 100644 --- a/AI/VCAI/Pathfinding/AINodeStorage.cpp +++ b/AI/VCAI/Pathfinding/AINodeStorage.cpp @@ -9,13 +9,14 @@ */ #include "StdInc.h" #include "AINodeStorage.h" +#include "Actions/TownPortalAction.h" #include "../Goals/Goals.h" #include "../../../CCallback.h" #include "../../../lib/mapping/CMap.h" #include "../../../lib/mapObjects/MapObjects.h" - #include "../../../lib/PathfinderUtil.h" #include "../../../lib/CPlayerState.h" + extern boost::thread_specific_ptr cb; @@ -191,26 +192,6 @@ void AINodeStorage::setHero(HeroPtr heroPtr) hero = heroPtr.get(); } -class TownPortalAction : public ISpecialAction -{ -private: - const CGTownInstance * target; - const HeroPtr hero; - -public: - TownPortalAction(const CGTownInstance * target) - :target(target) - { - } - - virtual Goals::TSubgoal whatToDo(HeroPtr hero) const override - { - const CGTownInstance * targetTown = target; // const pointer is not allowed in settown - - return sptr(Goals::AdventureSpellCast(hero, SpellID::TOWN_PORTAL).settown(targetTown).settile(targetTown->visitablePos())); - } -}; - std::vector AINodeStorage::calculateTeleportations( const PathNodeInfo & source, const PathfinderConfig * pathfinderConfig, @@ -299,7 +280,7 @@ void AINodeStorage::calculateTownPortalTeleportations( AIPathNode * node = nodeOptional.get(); node->theNodeBefore = source.node; - node->specialAction.reset(new TownPortalAction(targetTown)); + node->specialAction.reset(new AIPathfinding::TownPortalAction(targetTown)); node->moveRemains = source.node->moveRemains; neighbours.push_back(node); diff --git a/AI/VCAI/Pathfinding/AINodeStorage.h b/AI/VCAI/Pathfinding/AINodeStorage.h index 4eda383ef..add85c020 100644 --- a/AI/VCAI/Pathfinding/AINodeStorage.h +++ b/AI/VCAI/Pathfinding/AINodeStorage.h @@ -14,24 +14,10 @@ #include "../../../lib/mapObjects/CGHeroInstance.h" #include "../AIUtility.h" #include "../Goals/AbstractGoal.h" +#include "Actions/ISpecialAction.h" struct AIPathNode; -class ISpecialAction -{ -public: - virtual Goals::TSubgoal whatToDo(HeroPtr hero) const = 0; - - virtual void applyOnDestination( - HeroPtr hero, - CDestinationNodeInfo & destination, - const PathNodeInfo & source, - AIPathNode * dstMode, - const AIPathNode * srcNode) const - { - } -}; - struct AIPathNode : public CGPathNode { uint32_t chainMask; diff --git a/AI/VCAI/Pathfinding/AIPathfinderConfig.cpp b/AI/VCAI/Pathfinding/AIPathfinderConfig.cpp index ada244ebf..d1e4f727f 100644 --- a/AI/VCAI/Pathfinding/AIPathfinderConfig.cpp +++ b/AI/VCAI/Pathfinding/AIPathfinderConfig.cpp @@ -9,487 +9,13 @@ */ #include "StdInc.h" #include "AIPathfinderConfig.h" -#include "../Goals/Goals.h" -#include "../../../CCallback.h" -#include "../../../lib/mapping/CMap.h" -#include "../../../lib/mapObjects/MapObjects.h" +#include "Rules/AILayerTransitionRule.h" +#include "Rules/AIMovementAfterDestinationRule.h" +#include "Rules/AIMovementToDestinationRule.h" +#include "Rules/AIPreviousNodeRule.h" namespace AIPathfinding { - class VirtualBoatAction : public ISpecialAction - { - private: - uint64_t specialChain; - - public: - VirtualBoatAction(uint64_t specialChain) - :specialChain(specialChain) - { - } - - uint64_t getSpecialChain() const - { - return specialChain; - } - }; - - class BuildBoatAction : public VirtualBoatAction - { - private: - const IShipyard * shipyard; - - public: - BuildBoatAction(const IShipyard * shipyard) - :VirtualBoatAction(AINodeStorage::RESOURCE_CHAIN), shipyard(shipyard) - { - } - - virtual Goals::TSubgoal whatToDo(HeroPtr hero) const override - { - return sptr(Goals::BuildBoat(shipyard)); - } - }; - - class SummonBoatAction : public VirtualBoatAction - { - public: - SummonBoatAction() - :VirtualBoatAction(AINodeStorage::CAST_CHAIN) - { - } - - virtual Goals::TSubgoal whatToDo(HeroPtr hero) const override - { - return sptr(Goals::AdventureSpellCast(hero, SpellID::SUMMON_BOAT)); - } - - virtual void applyOnDestination( - HeroPtr hero, - CDestinationNodeInfo & destination, - const PathNodeInfo & source, - AIPathNode * dstMode, - const AIPathNode * srcNode) const override - { - dstMode->manaCost = srcNode->manaCost + getManaCost(hero); - dstMode->theNodeBefore = source.node; - } - - bool isAffordableBy(HeroPtr hero, const AIPathNode * source) const - { -#ifdef VCMI_TRACE_PATHFINDER - logAi->trace( - "Hero %s has %d mana and needed %d and already spent %d", - hero->name, - hero->mana, - getManaCost(hero), - source->manaCost); -#endif - - return hero->mana >= source->manaCost + getManaCost(hero); - } - - private: - uint32_t getManaCost(HeroPtr hero) const - { - SpellID summonBoat = SpellID::SUMMON_BOAT; - - return hero->getSpellCost(summonBoat.toSpell()); - } - }; - - class BattleAction : public ISpecialAction - { - private: - const int3 target; - const HeroPtr hero; - - public: - BattleAction(const int3 target) - :target(target) - { - } - - virtual Goals::TSubgoal whatToDo(HeroPtr hero) const override - { - return sptr(Goals::VisitTile(target).sethero(hero)); - } - }; - - class AILayerTransitionRule : public LayerTransitionRule - { - private: - CPlayerSpecificInfoCallback * cb; - VCAI * ai; - std::map> virtualBoats; - std::shared_ptr nodeStorage; - std::shared_ptr summonableVirtualBoat; - - public: - AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, VCAI * ai, std::shared_ptr nodeStorage) - :cb(cb), ai(ai), nodeStorage(nodeStorage) - { - setup(); - } - - virtual void process( - const PathNodeInfo & source, - CDestinationNodeInfo & destination, - const PathfinderConfig * pathfinderConfig, - CPathfinderHelper * pathfinderHelper) const override - { - LayerTransitionRule::process(source, destination, pathfinderConfig, pathfinderHelper); - - if(!destination.blocked) - { - return; - } - - if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::SAIL) - { - std::shared_ptr virtualBoat = findVirtualBoat(destination, source); - - if(virtualBoat && tryEmbarkVirtualBoat(destination, source, virtualBoat)) - { -#ifdef VCMI_TRACE_PATHFINDER - logAi->trace("Embarking to virtual boat while moving %s -> %s!", source.coord.toString(), destination.coord.toString()); -#endif - } - } - } - - private: - void setup() - { - std::vector shipyards; - - for(const CGTownInstance * t : cb->getTownsInfo()) - { - if(t->hasBuilt(BuildingID::SHIPYARD)) - shipyards.push_back(t); - } - - for(const CGObjectInstance * obj : ai->visitableObjs) - { - if(obj->ID != Obj::TOWN) //towns were handled in the previous loop - { - if(const IShipyard * shipyard = IShipyard::castFrom(obj)) - shipyards.push_back(shipyard); - } - } - - for(const IShipyard * shipyard : shipyards) - { - if(shipyard->shipyardStatus() == IShipyard::GOOD) - { - int3 boatLocation = shipyard->bestLocation(); - virtualBoats[boatLocation] = std::make_shared(shipyard); - logAi->debug("Virtual boat added at %s", boatLocation.toString()); - } - } - - auto hero = nodeStorage->getHero(); - auto summonBoatSpell = SpellID(SpellID::SUMMON_BOAT).toSpell(); - - if(hero->canCastThisSpell(summonBoatSpell) - && hero->getSpellSchoolLevel(summonBoatSpell) >= SecSkillLevel::ADVANCED) - { - // TODO: For lower school level we might need to check the existance of some boat - summonableVirtualBoat.reset(new SummonBoatAction()); - } - } - - std::shared_ptr findVirtualBoat( - CDestinationNodeInfo &destination, - const PathNodeInfo &source) const - { - std::shared_ptr virtualBoat; - - if(vstd::contains(virtualBoats, destination.coord)) - { - virtualBoat = virtualBoats.at(destination.coord); - } - else if( - summonableVirtualBoat - && summonableVirtualBoat->isAffordableBy(nodeStorage->getHero(), nodeStorage->getAINode(source.node))) - { - virtualBoat = summonableVirtualBoat; - } - - return virtualBoat; - } - - bool tryEmbarkVirtualBoat( - CDestinationNodeInfo &destination, - const PathNodeInfo &source, - std::shared_ptr virtualBoat) const - { - bool result = false; - - nodeStorage->updateAINode(destination.node, [&](AIPathNode * node) - { - auto boatNodeOptional = nodeStorage->getOrCreateNode( - node->coord, - node->layer, - node->chainMask | virtualBoat->getSpecialChain()); - - if(boatNodeOptional) - { - AIPathNode * boatNode = boatNodeOptional.get(); - - if(boatNode->action == CGPathNode::UNKNOWN) - { - boatNode->specialAction = virtualBoat; - destination.blocked = false; - destination.action = CGPathNode::ENodeAction::EMBARK; - destination.node = boatNode; - result = true; - } - else - { -#ifdef VCMI_TRACE_PATHFINDER - logAi->trace( - "Special transition node already allocated. Blocked moving %s -> %s", - source.coord.toString(), - destination.coord.toString()); -#endif - } - } - else - { - logAi->debug( - "Can not allocate special transition node while moving %s -> %s", - source.coord.toString(), - destination.coord.toString()); - } - }); - - return result; - } - }; - - class AIMovementAfterDestinationRule : public MovementAfterDestinationRule - { - private: - CPlayerSpecificInfoCallback * cb; - std::shared_ptr nodeStorage; - - public: - AIMovementAfterDestinationRule(CPlayerSpecificInfoCallback * cb, std::shared_ptr nodeStorage) - :cb(cb), nodeStorage(nodeStorage) - { - } - - virtual void process( - const PathNodeInfo & source, - CDestinationNodeInfo & destination, - const PathfinderConfig * pathfinderConfig, - CPathfinderHelper * pathfinderHelper) const override - { - if(nodeStorage->hasBetterChain(source, destination)) - { - destination.blocked = true; - - return; - } - - auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper); - - if(blocker == BlockingReason::NONE) - return; - - if(blocker == BlockingReason::DESTINATION_BLOCKVIS && destination.nodeObject) - { - auto objID = destination.nodeObject->ID; - auto enemyHero = objID == Obj::HERO && destination.objectRelations == PlayerRelations::ENEMIES; - - if(!enemyHero && !isObjectRemovable(destination.nodeObject)) - { - destination.blocked = true; - } - - return; - } - - if(blocker == BlockingReason::DESTINATION_VISIT) - { - return; - } - - if(blocker == BlockingReason::DESTINATION_GUARDED) - { - auto srcGuardians = cb->getGuardingCreatures(source.coord); - auto destGuardians = cb->getGuardingCreatures(destination.coord); - - if(destGuardians.empty()) - { - destination.blocked = true; - - return; - } - - vstd::erase_if(destGuardians, [&](const CGObjectInstance * destGuard) -> bool - { - return vstd::contains(srcGuardians, destGuard); - }); - - auto guardsAlreadyBypassed = destGuardians.empty() && srcGuardians.size(); - if(guardsAlreadyBypassed && nodeStorage->isBattleNode(source.node)) - { -#ifdef VCMI_TRACE_PATHFINDER - logAi->trace( - "Bypass guard at destination while moving %s -> %s", - source.coord.toString(), - destination.coord.toString()); -#endif - - return; - } - - const AIPathNode * destNode = nodeStorage->getAINode(destination.node); - auto battleNodeOptional = nodeStorage->getOrCreateNode( - destination.coord, - destination.node->layer, - destNode->chainMask | AINodeStorage::BATTLE_CHAIN); - - if(!battleNodeOptional) - { -#ifdef VCMI_TRACE_PATHFINDER - logAi->trace( - "Can not allocate battle node while moving %s -> %s", - source.coord.toString(), - destination.coord.toString()); -#endif - - destination.blocked = true; - - return; - } - - AIPathNode * battleNode = battleNodeOptional.get(); - - if(battleNode->locked) - { -#ifdef VCMI_TRACE_PATHFINDER - logAi->trace( - "Block bypass guard at destination while moving %s -> %s", - source.coord.toString(), - destination.coord.toString()); -#endif - destination.blocked = true; - - return; - } - - auto hero = nodeStorage->getHero(); - auto danger = evaluateDanger(destination.coord, hero); - - destination.node = battleNode; - nodeStorage->commit(destination, source); - - if(battleNode->danger < danger) - { - battleNode->danger = danger; - } - - battleNode->specialAction = std::make_shared(destination.coord); -#ifdef VCMI_TRACE_PATHFINDER - logAi->trace( - "Begin bypass guard at destination with danger %s while moving %s -> %s", - std::to_string(danger), - source.coord.toString(), - destination.coord.toString()); -#endif - return; - } - - destination.blocked = true; - } - }; - - class AIMovementToDestinationRule : public MovementToDestinationRule - { - private: - std::shared_ptr nodeStorage; - - public: - AIMovementToDestinationRule(std::shared_ptr nodeStorage) - : nodeStorage(nodeStorage) - { - } - - virtual void process( - const PathNodeInfo & source, - CDestinationNodeInfo & destination, - const PathfinderConfig * pathfinderConfig, - CPathfinderHelper * pathfinderHelper) const override - { - auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper); - - if(blocker == BlockingReason::NONE) - return; - - if(blocker == BlockingReason::DESTINATION_BLOCKED - && destination.action == CGPathNode::EMBARK - && nodeStorage->getAINode(destination.node)->specialAction) - { - return; - } - - if(blocker == BlockingReason::SOURCE_GUARDED && nodeStorage->isBattleNode(source.node)) - { -#ifdef VCMI_TRACE_PATHFINDER - logAi->trace( - "Bypass src guard while moving from %s to %s", - source.coord.toString(), - destination.coord.toString()); -#endif - return; - } - - destination.blocked = true; - } - }; - - class AIPreviousNodeRule : public MovementToDestinationRule - { - private: - std::shared_ptr nodeStorage; - - public: - AIPreviousNodeRule(std::shared_ptr nodeStorage) - : nodeStorage(nodeStorage) - { - } - - virtual void process( - const PathNodeInfo & source, - CDestinationNodeInfo & destination, - const PathfinderConfig * pathfinderConfig, - CPathfinderHelper * pathfinderHelper) const override - { - if(source.node->action == CGPathNode::ENodeAction::BLOCKING_VISIT || source.node->action == CGPathNode::ENodeAction::VISIT) - { - // we can not directly bypass objects, we need to interact with them first - destination.node->theNodeBefore = source.node; -#ifdef VCMI_TRACE_PATHFINDER - logAi->trace( - "Link src node %s to destination node %s while bypassing visitable obj", - source.coord.toString(), - destination.coord.toString()); -#endif - return; - } - - auto aiSourceNode = nodeStorage->getAINode(source.node); - - if(aiSourceNode->specialAction) - { - // there is some action on source tile which should be performed before we can bypass it - destination.node->theNodeBefore = source.node; - } - } - }; - std::vector> makeRuleset( CPlayerSpecificInfoCallback * cb, VCAI * ai, diff --git a/AI/VCAI/Pathfinding/Actions/BattleAction.cpp b/AI/VCAI/Pathfinding/Actions/BattleAction.cpp new file mode 100644 index 000000000..215678279 --- /dev/null +++ b/AI/VCAI/Pathfinding/Actions/BattleAction.cpp @@ -0,0 +1,21 @@ +/* +* BattleAction.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ + +#include "StdInc.h" +#include "../../Goals/VisitTile.h" +#include "BattleAction.h" + +namespace AIPathfinding +{ + Goals::TSubgoal BattleAction::whatToDo(const HeroPtr & hero) const + { + return Goals::sptr(Goals::VisitTile(targetTile).sethero(hero)); + } +} \ No newline at end of file diff --git a/AI/VCAI/Pathfinding/Actions/BattleAction.h b/AI/VCAI/Pathfinding/Actions/BattleAction.h new file mode 100644 index 000000000..630e49a55 --- /dev/null +++ b/AI/VCAI/Pathfinding/Actions/BattleAction.h @@ -0,0 +1,30 @@ +/* +* BattleAction.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ + +#pragma once + +#include "ISpecialAction.h" + +namespace AIPathfinding +{ + class BattleAction : public ISpecialAction + { + private: + const int3 targetTile; + + public: + BattleAction(const int3 targetTile) + :targetTile(targetTile) + { + } + + virtual Goals::TSubgoal whatToDo(const HeroPtr & hero) const override; + }; +} \ No newline at end of file diff --git a/AI/VCAI/Pathfinding/Actions/BoatActions.cpp b/AI/VCAI/Pathfinding/Actions/BoatActions.cpp new file mode 100644 index 000000000..d1e80e867 --- /dev/null +++ b/AI/VCAI/Pathfinding/Actions/BoatActions.cpp @@ -0,0 +1,61 @@ +/* +* BoatActions.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ + +#include "StdInc.h" +#include "../../Goals/AdventureSpellCast.h" +#include "../../Goals/BuildBoat.h" +#include "../../../../lib/mapping/CMap.h" +#include "../../../../lib/mapObjects/MapObjects.h" +#include "BoatActions.h" + +namespace AIPathfinding +{ + Goals::TSubgoal BuildBoatAction::whatToDo(const HeroPtr & hero) const + { + return Goals::sptr(Goals::BuildBoat(shipyard)); + } + + Goals::TSubgoal SummonBoatAction::whatToDo(const HeroPtr & hero) const + { + return Goals::sptr(Goals::AdventureSpellCast(hero, SpellID::SUMMON_BOAT)); + } + + void SummonBoatAction::applyOnDestination( + const HeroPtr & hero, + CDestinationNodeInfo & destination, + const PathNodeInfo & source, + AIPathNode * dstMode, + const AIPathNode * srcNode) const + { + dstMode->manaCost = srcNode->manaCost + getManaCost(hero); + dstMode->theNodeBefore = source.node; + } + + bool SummonBoatAction::isAffordableBy(const HeroPtr & hero, const AIPathNode * source) const + { +#ifdef VCMI_TRACE_PATHFINDER + logAi->trace( + "Hero %s has %d mana and needed %d and already spent %d", + hero->name, + hero->mana, + getManaCost(hero), + source->manaCost); +#endif + + return hero->mana >= source->manaCost + getManaCost(hero); + } + + uint32_t SummonBoatAction::getManaCost(const HeroPtr & hero) const + { + SpellID summonBoat = SpellID::SUMMON_BOAT; + + return hero->getSpellCost(summonBoat.toSpell()); + } +} \ No newline at end of file diff --git a/AI/VCAI/Pathfinding/Actions/BoatActions.h b/AI/VCAI/Pathfinding/Actions/BoatActions.h new file mode 100644 index 000000000..e8429e60d --- /dev/null +++ b/AI/VCAI/Pathfinding/Actions/BoatActions.h @@ -0,0 +1,72 @@ +/* +* BoatActions.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ + +#pragma once + +#include "ISpecialAction.h" +#include "../../../lib/mapping/CMap.h" +#include "../../../lib/mapObjects/MapObjects.h" + +namespace AIPathfinding +{ + class VirtualBoatAction : public ISpecialAction + { + private: + uint64_t specialChain; + + public: + VirtualBoatAction(uint64_t specialChain) + :specialChain(specialChain) + { + } + + uint64_t getSpecialChain() const + { + return specialChain; + } + }; + + class SummonBoatAction : public VirtualBoatAction + { + public: + SummonBoatAction() + :VirtualBoatAction(AINodeStorage::CAST_CHAIN) + { + } + + virtual Goals::TSubgoal whatToDo(const HeroPtr & hero) const override; + + virtual void applyOnDestination( + const HeroPtr & hero, + CDestinationNodeInfo & destination, + const PathNodeInfo & source, + AIPathNode * dstMode, + const AIPathNode * srcNode) const override; + + bool isAffordableBy(const HeroPtr & hero, const AIPathNode * source) const; + + private: + uint32_t getManaCost(const HeroPtr & hero) const; + }; + + class BuildBoatAction : public VirtualBoatAction + { + private: + const IShipyard * shipyard; + + public: + BuildBoatAction(const IShipyard * shipyard) + :VirtualBoatAction(AINodeStorage::RESOURCE_CHAIN), shipyard(shipyard) + { + } + + virtual Goals::TSubgoal whatToDo(const HeroPtr & hero) const override; + }; +} \ No newline at end of file diff --git a/AI/VCAI/Pathfinding/Actions/ISpecialAction.h b/AI/VCAI/Pathfinding/Actions/ISpecialAction.h new file mode 100644 index 000000000..4dafbcce4 --- /dev/null +++ b/AI/VCAI/Pathfinding/Actions/ISpecialAction.h @@ -0,0 +1,31 @@ +/* +* ISpecialAction.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ + +#pragma once + +#include "../../AIUtility.h" +#include "../../Goals/AbstractGoal.h" + +class AIPathNode; + +class ISpecialAction +{ +public: + virtual Goals::TSubgoal whatToDo(const HeroPtr & hero) const = 0; + + virtual void applyOnDestination( + const HeroPtr & hero, + CDestinationNodeInfo & destination, + const PathNodeInfo & source, + AIPathNode * dstMode, + const AIPathNode * srcNode) const + { + } +}; \ No newline at end of file diff --git a/AI/VCAI/Pathfinding/Actions/TownPortalAction.cpp b/AI/VCAI/Pathfinding/Actions/TownPortalAction.cpp new file mode 100644 index 000000000..99a195069 --- /dev/null +++ b/AI/VCAI/Pathfinding/Actions/TownPortalAction.cpp @@ -0,0 +1,24 @@ +/* +* TownPortalAction.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ + +#include "StdInc.h" +#include "../../Goals/AdventureSpellCast.h" +#include "../../../../lib/mapping/CMap.h" +#include "../../../../lib/mapObjects/MapObjects.h" +#include "TownPortalAction.h" + +using namespace AIPathfinding; + +Goals::TSubgoal TownPortalAction::whatToDo(const HeroPtr & hero) const +{ + const CGTownInstance * targetTown = target; // const pointer is not allowed in settown + + return Goals::sptr(Goals::AdventureSpellCast(hero, SpellID::TOWN_PORTAL).settown(targetTown).settile(targetTown->visitablePos())); +} \ No newline at end of file diff --git a/AI/VCAI/Pathfinding/Actions/TownPortalAction.h b/AI/VCAI/Pathfinding/Actions/TownPortalAction.h new file mode 100644 index 000000000..e9747b716 --- /dev/null +++ b/AI/VCAI/Pathfinding/Actions/TownPortalAction.h @@ -0,0 +1,33 @@ +/* +* TownPortalAction.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ + +#pragma once + +#include "ISpecialAction.h" +#include "../../../lib/mapping/CMap.h" +#include "../../../lib/mapObjects/MapObjects.h" +#include "../../Goals/AdventureSpellCast.h" + +namespace AIPathfinding +{ + class TownPortalAction : public ISpecialAction + { + private: + const CGTownInstance * target; + + public: + TownPortalAction(const CGTownInstance * target) + :target(target) + { + } + + virtual Goals::TSubgoal whatToDo(const HeroPtr & hero) const override; + }; +} \ No newline at end of file diff --git a/AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.cpp b/AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.cpp new file mode 100644 index 000000000..7a1fae0dc --- /dev/null +++ b/AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.cpp @@ -0,0 +1,154 @@ +/* +* AILayerTransitionRule.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "AILayerTransitionRule.h" + +namespace AIPathfinding +{ + AILayerTransitionRule::AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, VCAI * ai, std::shared_ptr nodeStorage) + :cb(cb), ai(ai), nodeStorage(nodeStorage) + { + setup(); + } + + void AILayerTransitionRule::process( + const PathNodeInfo & source, + CDestinationNodeInfo & destination, + const PathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const + { + LayerTransitionRule::process(source, destination, pathfinderConfig, pathfinderHelper); + + if(!destination.blocked) + { + return; + } + + if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::SAIL) + { + std::shared_ptr virtualBoat = findVirtualBoat(destination, source); + + if(virtualBoat && tryEmbarkVirtualBoat(destination, source, virtualBoat)) + { +#ifdef VCMI_TRACE_PATHFINDER + logAi->trace("Embarking to virtual boat while moving %s -> %s!", source.coord.toString(), destination.coord.toString()); +#endif + } + } + } + + void AILayerTransitionRule::setup() + { + std::vector shipyards; + + for(const CGTownInstance * t : cb->getTownsInfo()) + { + if(t->hasBuilt(BuildingID::SHIPYARD)) + shipyards.push_back(t); + } + + for(const CGObjectInstance * obj : ai->visitableObjs) + { + if(obj->ID != Obj::TOWN) //towns were handled in the previous loop + { + if(const IShipyard * shipyard = IShipyard::castFrom(obj)) + shipyards.push_back(shipyard); + } + } + + for(const IShipyard * shipyard : shipyards) + { + if(shipyard->shipyardStatus() == IShipyard::GOOD) + { + int3 boatLocation = shipyard->bestLocation(); + virtualBoats[boatLocation] = std::make_shared(shipyard); + logAi->debug("Virtual boat added at %s", boatLocation.toString()); + } + } + + auto hero = nodeStorage->getHero(); + auto summonBoatSpell = SpellID(SpellID::SUMMON_BOAT).toSpell(); + + if(hero->canCastThisSpell(summonBoatSpell) + && hero->getSpellSchoolLevel(summonBoatSpell) >= SecSkillLevel::ADVANCED) + { + // TODO: For lower school level we might need to check the existance of some boat + summonableVirtualBoat.reset(new SummonBoatAction()); + } + } + + std::shared_ptr AILayerTransitionRule::findVirtualBoat( + CDestinationNodeInfo & destination, + const PathNodeInfo & source) const + { + std::shared_ptr virtualBoat; + + if(vstd::contains(virtualBoats, destination.coord)) + { + virtualBoat = virtualBoats.at(destination.coord); + } + else if( + summonableVirtualBoat + && summonableVirtualBoat->isAffordableBy(nodeStorage->getHero(), nodeStorage->getAINode(source.node))) + { + virtualBoat = summonableVirtualBoat; + } + + return virtualBoat; + } + + bool AILayerTransitionRule::tryEmbarkVirtualBoat( + CDestinationNodeInfo & destination, + const PathNodeInfo & source, + std::shared_ptr virtualBoat) const + { + bool result = false; + + nodeStorage->updateAINode(destination.node, [&](AIPathNode * node) + { + auto boatNodeOptional = nodeStorage->getOrCreateNode( + node->coord, + node->layer, + node->chainMask | virtualBoat->getSpecialChain()); + + if(boatNodeOptional) + { + AIPathNode * boatNode = boatNodeOptional.get(); + + if(boatNode->action == CGPathNode::UNKNOWN) + { + boatNode->specialAction = virtualBoat; + destination.blocked = false; + destination.action = CGPathNode::ENodeAction::EMBARK; + destination.node = boatNode; + result = true; + } + else + { +#ifdef VCMI_TRACE_PATHFINDER + logAi->trace( + "Special transition node already allocated. Blocked moving %s -> %s", + source.coord.toString(), + destination.coord.toString()); +#endif + } + } + else + { + logAi->debug( + "Can not allocate special transition node while moving %s -> %s", + source.coord.toString(), + destination.coord.toString()); + } + }); + + return result; + } +} diff --git a/AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.h b/AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.h new file mode 100644 index 000000000..804722842 --- /dev/null +++ b/AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.h @@ -0,0 +1,52 @@ +/* +* AILayerTransitionRule.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ + +#pragma once + +#include "../AINodeStorage.h" +#include "../../VCAI.h" +#include "../Actions/BoatActions.h" +#include "../../../../CCallback.h" +#include "../../../../lib/mapping/CMap.h" +#include "../../../../lib/mapObjects/MapObjects.h" + +namespace AIPathfinding +{ + class AILayerTransitionRule : public LayerTransitionRule + { + private: + CPlayerSpecificInfoCallback * cb; + VCAI * ai; + std::map> virtualBoats; + std::shared_ptr nodeStorage; + std::shared_ptr summonableVirtualBoat; + + public: + AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, VCAI * ai, std::shared_ptr nodeStorage); + + virtual void process( + const PathNodeInfo & source, + CDestinationNodeInfo & destination, + const PathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const override; + + private: + void setup(); + + std::shared_ptr findVirtualBoat( + CDestinationNodeInfo & destination, + const PathNodeInfo & source) const; + + bool tryEmbarkVirtualBoat( + CDestinationNodeInfo & destination, + const PathNodeInfo & source, + std::shared_ptr virtualBoat) const; + }; +} diff --git a/AI/VCAI/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp b/AI/VCAI/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp new file mode 100644 index 000000000..ba0ac2f0f --- /dev/null +++ b/AI/VCAI/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp @@ -0,0 +1,148 @@ +/* +* AIMovementAfterDestinationRule.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "AIMovementAfterDestinationRule.h" +#include "../Actions/BattleAction.h" + +namespace AIPathfinding +{ + AIMovementAfterDestinationRule::AIMovementAfterDestinationRule( + CPlayerSpecificInfoCallback * cb, + std::shared_ptr nodeStorage) + :cb(cb), nodeStorage(nodeStorage) + { + } + + void AIMovementAfterDestinationRule::process( + const PathNodeInfo & source, + CDestinationNodeInfo & destination, + const PathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const + { + if(nodeStorage->hasBetterChain(source, destination)) + { + destination.blocked = true; + + return; + } + + auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper); + + if(blocker == BlockingReason::NONE) + return; + + if(blocker == BlockingReason::DESTINATION_BLOCKVIS && destination.nodeObject) + { + auto objID = destination.nodeObject->ID; + auto enemyHero = objID == Obj::HERO && destination.objectRelations == PlayerRelations::ENEMIES; + + if(!enemyHero && !isObjectRemovable(destination.nodeObject)) + { + destination.blocked = true; + } + + return; + } + + if(blocker == BlockingReason::DESTINATION_VISIT) + { + return; + } + + if(blocker == BlockingReason::DESTINATION_GUARDED) + { + auto srcGuardians = cb->getGuardingCreatures(source.coord); + auto destGuardians = cb->getGuardingCreatures(destination.coord); + + if(destGuardians.empty()) + { + destination.blocked = true; + + return; + } + + vstd::erase_if(destGuardians, [&](const CGObjectInstance * destGuard) -> bool + { + return vstd::contains(srcGuardians, destGuard); + }); + + auto guardsAlreadyBypassed = destGuardians.empty() && srcGuardians.size(); + if(guardsAlreadyBypassed && nodeStorage->isBattleNode(source.node)) + { +#ifdef VCMI_TRACE_PATHFINDER + logAi->trace( + "Bypass guard at destination while moving %s -> %s", + source.coord.toString(), + destination.coord.toString()); +#endif + + return; + } + + const AIPathNode * destNode = nodeStorage->getAINode(destination.node); + auto battleNodeOptional = nodeStorage->getOrCreateNode( + destination.coord, + destination.node->layer, + destNode->chainMask | AINodeStorage::BATTLE_CHAIN); + + if(!battleNodeOptional) + { +#ifdef VCMI_TRACE_PATHFINDER + logAi->trace( + "Can not allocate battle node while moving %s -> %s", + source.coord.toString(), + destination.coord.toString()); +#endif + + destination.blocked = true; + + return; + } + + AIPathNode * battleNode = battleNodeOptional.get(); + + if(battleNode->locked) + { +#ifdef VCMI_TRACE_PATHFINDER + logAi->trace( + "Block bypass guard at destination while moving %s -> %s", + source.coord.toString(), + destination.coord.toString()); +#endif + destination.blocked = true; + + return; + } + + auto hero = nodeStorage->getHero(); + auto danger = evaluateDanger(destination.coord, hero); + + destination.node = battleNode; + nodeStorage->commit(destination, source); + + if(battleNode->danger < danger) + { + battleNode->danger = danger; + } + + battleNode->specialAction = std::make_shared(destination.coord); +#ifdef VCMI_TRACE_PATHFINDER + logAi->trace( + "Begin bypass guard at destination with danger %s while moving %s -> %s", + std::to_string(danger), + source.coord.toString(), + destination.coord.toString()); +#endif + return; + } + + destination.blocked = true; + } +} diff --git a/AI/VCAI/Pathfinding/Rules/AIMovementAfterDestinationRule.h b/AI/VCAI/Pathfinding/Rules/AIMovementAfterDestinationRule.h new file mode 100644 index 000000000..b8d87e602 --- /dev/null +++ b/AI/VCAI/Pathfinding/Rules/AIMovementAfterDestinationRule.h @@ -0,0 +1,36 @@ +/* +* AIMovementAfterDestinationRule.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ + +#pragma once + +#include "../AINodeStorage.h" +#include "../../VCAI.h" +#include "../../../../CCallback.h" +#include "../../../../lib/mapping/CMap.h" +#include "../../../../lib/mapObjects/MapObjects.h" + +namespace AIPathfinding +{ + class AIMovementAfterDestinationRule : public MovementAfterDestinationRule + { + private: + CPlayerSpecificInfoCallback * cb; + std::shared_ptr nodeStorage; + + public: + AIMovementAfterDestinationRule(CPlayerSpecificInfoCallback * cb, std::shared_ptr nodeStorage); + + virtual void process( + const PathNodeInfo & source, + CDestinationNodeInfo & destination, + const PathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const override; + }; +} diff --git a/AI/VCAI/Pathfinding/Rules/AIMovementToDestinationRule.cpp b/AI/VCAI/Pathfinding/Rules/AIMovementToDestinationRule.cpp new file mode 100644 index 000000000..e34575157 --- /dev/null +++ b/AI/VCAI/Pathfinding/Rules/AIMovementToDestinationRule.cpp @@ -0,0 +1,51 @@ +/* +* AIMovementToDestinationRule.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "AIMovementToDestinationRule.h" + +namespace AIPathfinding +{ + AIMovementToDestinationRule::AIMovementToDestinationRule(std::shared_ptr nodeStorage) + : nodeStorage(nodeStorage) + { + } + + void AIMovementToDestinationRule::process( + const PathNodeInfo & source, + CDestinationNodeInfo & destination, + const PathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const + { + auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper); + + if(blocker == BlockingReason::NONE) + return; + + if(blocker == BlockingReason::DESTINATION_BLOCKED + && destination.action == CGPathNode::EMBARK + && nodeStorage->getAINode(destination.node)->specialAction) + { + return; + } + + if(blocker == BlockingReason::SOURCE_GUARDED && nodeStorage->isBattleNode(source.node)) + { +#ifdef VCMI_TRACE_PATHFINDER + logAi->trace( + "Bypass src guard while moving from %s to %s", + source.coord.toString(), + destination.coord.toString()); +#endif + return; + } + + destination.blocked = true; + } +} diff --git a/AI/VCAI/Pathfinding/Rules/AIMovementToDestinationRule.h b/AI/VCAI/Pathfinding/Rules/AIMovementToDestinationRule.h new file mode 100644 index 000000000..6f0e8ac01 --- /dev/null +++ b/AI/VCAI/Pathfinding/Rules/AIMovementToDestinationRule.h @@ -0,0 +1,35 @@ +/* +* AIMovementToDestinationRule.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ + +#pragma once + +#include "../AINodeStorage.h" +#include "../../VCAI.h" +#include "../../../../CCallback.h" +#include "../../../../lib/mapping/CMap.h" +#include "../../../../lib/mapObjects/MapObjects.h" + +namespace AIPathfinding +{ + class AIMovementToDestinationRule : public MovementToDestinationRule + { + private: + std::shared_ptr nodeStorage; + + public: + AIMovementToDestinationRule(std::shared_ptr nodeStorage); + + virtual void process( + const PathNodeInfo & source, + CDestinationNodeInfo & destination, + const PathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const override; + }; +} diff --git a/AI/VCAI/Pathfinding/Rules/AIPreviousNodeRule.cpp b/AI/VCAI/Pathfinding/Rules/AIPreviousNodeRule.cpp new file mode 100644 index 000000000..3dbdcee6d --- /dev/null +++ b/AI/VCAI/Pathfinding/Rules/AIPreviousNodeRule.cpp @@ -0,0 +1,47 @@ +/* +* AIPreviousNodeRule.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "AIPreviousNodeRule.h" + +namespace AIPathfinding +{ + AIPreviousNodeRule::AIPreviousNodeRule(std::shared_ptr nodeStorage) + : nodeStorage(nodeStorage) + { + } + + void AIPreviousNodeRule::process( + const PathNodeInfo & source, + CDestinationNodeInfo & destination, + const PathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const + { + if(source.node->action == CGPathNode::ENodeAction::BLOCKING_VISIT || source.node->action == CGPathNode::ENodeAction::VISIT) + { + // we can not directly bypass objects, we need to interact with them first + destination.node->theNodeBefore = source.node; +#ifdef VCMI_TRACE_PATHFINDER + logAi->trace( + "Link src node %s to destination node %s while bypassing visitable obj", + source.coord.toString(), + destination.coord.toString()); +#endif + return; + } + + auto aiSourceNode = nodeStorage->getAINode(source.node); + + if(aiSourceNode->specialAction) + { + // there is some action on source tile which should be performed before we can bypass it + destination.node->theNodeBefore = source.node; + } + } +} diff --git a/AI/VCAI/Pathfinding/Rules/AIPreviousNodeRule.h b/AI/VCAI/Pathfinding/Rules/AIPreviousNodeRule.h new file mode 100644 index 000000000..d97aa53b5 --- /dev/null +++ b/AI/VCAI/Pathfinding/Rules/AIPreviousNodeRule.h @@ -0,0 +1,35 @@ +/* +* AIPreviousNodeRule.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ + +#pragma once + +#include "../AINodeStorage.h" +#include "../../VCAI.h" +#include "../../../../CCallback.h" +#include "../../../../lib/mapping/CMap.h" +#include "../../../../lib/mapObjects/MapObjects.h" + +namespace AIPathfinding +{ + class AIPreviousNodeRule : public MovementToDestinationRule + { + private: + std::shared_ptr nodeStorage; + + public: + AIPreviousNodeRule(std::shared_ptr nodeStorage); + + virtual void process( + const PathNodeInfo & source, + CDestinationNodeInfo & destination, + const PathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const override; + }; +} diff --git a/AI/VCAI/VCAI.vcxproj b/AI/VCAI/VCAI.vcxproj index 1bf44ab82..37b33af82 100644 --- a/AI/VCAI/VCAI.vcxproj +++ b/AI/VCAI/VCAI.vcxproj @@ -168,10 +168,20 @@ + + + + + + + + + + @@ -214,10 +224,15 @@ + + + + + diff --git a/AI/VCAI/VCAI.vcxproj.filters b/AI/VCAI/VCAI.vcxproj.filters index 8bae66bb2..9a8225034 100644 --- a/AI/VCAI/VCAI.vcxproj.filters +++ b/AI/VCAI/VCAI.vcxproj.filters @@ -90,6 +90,15 @@ Goals + + Pathfinding\Actions + + + + + + + @@ -189,6 +198,16 @@ Goals + + Pathfinding\Actions + + + + + + + + @@ -197,5 +216,11 @@ {f97140a0-eee3-456f-b586-4b13265c01da} + + {beabfdb9-2e76-4daa-8d1a-81086387f319} + + + {3ebb4852-a986-447a-b5cc-20992df76f0c} + \ No newline at end of file