mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	AI: extract pathfinding special actions and rules to separate files
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
| @@ -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<CCallback> 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<CGPathNode *> 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); | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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<int3, std::shared_ptr<const BuildBoatAction>> virtualBoats; | ||||
| 		std::shared_ptr<AINodeStorage> nodeStorage; | ||||
| 		std::shared_ptr<const SummonBoatAction> summonableVirtualBoat; | ||||
|  | ||||
| 	public: | ||||
| 		AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, VCAI * ai, std::shared_ptr<AINodeStorage> 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<const VirtualBoatAction> 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<const IShipyard *> 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<BuildBoatAction>(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<const VirtualBoatAction> findVirtualBoat( | ||||
| 			CDestinationNodeInfo &destination, | ||||
| 			const PathNodeInfo &source) const | ||||
| 		{ | ||||
| 			std::shared_ptr<const VirtualBoatAction> 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<const VirtualBoatAction> 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<AINodeStorage> nodeStorage; | ||||
|  | ||||
| 	public: | ||||
| 		AIMovementAfterDestinationRule(CPlayerSpecificInfoCallback * cb, std::shared_ptr<AINodeStorage> 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<BattleAction>(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<AINodeStorage> nodeStorage; | ||||
|  | ||||
| 	public: | ||||
| 		AIMovementToDestinationRule(std::shared_ptr<AINodeStorage> 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<AINodeStorage> nodeStorage; | ||||
|  | ||||
| 	public: | ||||
| 		AIPreviousNodeRule(std::shared_ptr<AINodeStorage> 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<std::shared_ptr<IPathfindingRule>> makeRuleset( | ||||
| 		CPlayerSpecificInfoCallback * cb, | ||||
| 		VCAI * ai, | ||||
|   | ||||
							
								
								
									
										21
									
								
								AI/VCAI/Pathfinding/Actions/BattleAction.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								AI/VCAI/Pathfinding/Actions/BattleAction.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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)); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										30
									
								
								AI/VCAI/Pathfinding/Actions/BattleAction.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								AI/VCAI/Pathfinding/Actions/BattleAction.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||
| 	}; | ||||
| } | ||||
							
								
								
									
										61
									
								
								AI/VCAI/Pathfinding/Actions/BoatActions.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								AI/VCAI/Pathfinding/Actions/BoatActions.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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()); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										72
									
								
								AI/VCAI/Pathfinding/Actions/BoatActions.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								AI/VCAI/Pathfinding/Actions/BoatActions.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||
| 	}; | ||||
| } | ||||
							
								
								
									
										31
									
								
								AI/VCAI/Pathfinding/Actions/ISpecialAction.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								AI/VCAI/Pathfinding/Actions/ISpecialAction.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| 	{ | ||||
| 	} | ||||
| }; | ||||
							
								
								
									
										24
									
								
								AI/VCAI/Pathfinding/Actions/TownPortalAction.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								AI/VCAI/Pathfinding/Actions/TownPortalAction.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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())); | ||||
| } | ||||
							
								
								
									
										33
									
								
								AI/VCAI/Pathfinding/Actions/TownPortalAction.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								AI/VCAI/Pathfinding/Actions/TownPortalAction.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||
| 	}; | ||||
| } | ||||
							
								
								
									
										154
									
								
								AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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<AINodeStorage> 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<const VirtualBoatAction> 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<const IShipyard *> 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<BuildBoatAction>(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<const VirtualBoatAction> AILayerTransitionRule::findVirtualBoat( | ||||
| 		CDestinationNodeInfo & destination, | ||||
| 		const PathNodeInfo & source) const | ||||
| 	{ | ||||
| 		std::shared_ptr<const VirtualBoatAction> 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<const VirtualBoatAction> 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; | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										52
									
								
								AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								AI/VCAI/Pathfinding/Rules/AILayerTransitionRule.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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<int3, std::shared_ptr<const BuildBoatAction>> virtualBoats; | ||||
| 		std::shared_ptr<AINodeStorage> nodeStorage; | ||||
| 		std::shared_ptr<const SummonBoatAction> summonableVirtualBoat; | ||||
|  | ||||
| 	public: | ||||
| 		AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, VCAI * ai, std::shared_ptr<AINodeStorage> nodeStorage); | ||||
|  | ||||
| 		virtual void process( | ||||
| 			const PathNodeInfo & source, | ||||
| 			CDestinationNodeInfo & destination, | ||||
| 			const PathfinderConfig * pathfinderConfig, | ||||
| 			CPathfinderHelper * pathfinderHelper) const override; | ||||
|  | ||||
| 	private: | ||||
| 		void setup(); | ||||
|  | ||||
| 		std::shared_ptr<const VirtualBoatAction> findVirtualBoat( | ||||
| 			CDestinationNodeInfo & destination, | ||||
| 			const PathNodeInfo & source) const; | ||||
|  | ||||
| 		bool tryEmbarkVirtualBoat( | ||||
| 			CDestinationNodeInfo & destination, | ||||
| 			const PathNodeInfo & source, | ||||
| 			std::shared_ptr<const VirtualBoatAction> virtualBoat) const; | ||||
| 	}; | ||||
| } | ||||
							
								
								
									
										148
									
								
								AI/VCAI/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								AI/VCAI/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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<AINodeStorage> 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<BattleAction>(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; | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										36
									
								
								AI/VCAI/Pathfinding/Rules/AIMovementAfterDestinationRule.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								AI/VCAI/Pathfinding/Rules/AIMovementAfterDestinationRule.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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<AINodeStorage> nodeStorage; | ||||
|  | ||||
| 	public: | ||||
| 		AIMovementAfterDestinationRule(CPlayerSpecificInfoCallback * cb, std::shared_ptr<AINodeStorage> nodeStorage); | ||||
|  | ||||
| 		virtual void process( | ||||
| 			const PathNodeInfo & source, | ||||
| 			CDestinationNodeInfo & destination, | ||||
| 			const PathfinderConfig * pathfinderConfig, | ||||
| 			CPathfinderHelper * pathfinderHelper) const override; | ||||
| 	}; | ||||
| } | ||||
							
								
								
									
										51
									
								
								AI/VCAI/Pathfinding/Rules/AIMovementToDestinationRule.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								AI/VCAI/Pathfinding/Rules/AIMovementToDestinationRule.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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<AINodeStorage> 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; | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										35
									
								
								AI/VCAI/Pathfinding/Rules/AIMovementToDestinationRule.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								AI/VCAI/Pathfinding/Rules/AIMovementToDestinationRule.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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<AINodeStorage> nodeStorage; | ||||
|  | ||||
| 	public: | ||||
| 		AIMovementToDestinationRule(std::shared_ptr<AINodeStorage> nodeStorage); | ||||
|  | ||||
| 		virtual void process( | ||||
| 			const PathNodeInfo & source, | ||||
| 			CDestinationNodeInfo & destination, | ||||
| 			const PathfinderConfig * pathfinderConfig, | ||||
| 			CPathfinderHelper * pathfinderHelper) const override; | ||||
| 	}; | ||||
| } | ||||
							
								
								
									
										47
									
								
								AI/VCAI/Pathfinding/Rules/AIPreviousNodeRule.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								AI/VCAI/Pathfinding/Rules/AIPreviousNodeRule.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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<AINodeStorage> 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; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										35
									
								
								AI/VCAI/Pathfinding/Rules/AIPreviousNodeRule.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								AI/VCAI/Pathfinding/Rules/AIPreviousNodeRule.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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<AINodeStorage> nodeStorage; | ||||
|  | ||||
| 	public: | ||||
| 		AIPreviousNodeRule(std::shared_ptr<AINodeStorage> nodeStorage); | ||||
|  | ||||
| 		virtual void process( | ||||
| 			const PathNodeInfo & source, | ||||
| 			CDestinationNodeInfo & destination, | ||||
| 			const PathfinderConfig * pathfinderConfig, | ||||
| 			CPathfinderHelper * pathfinderHelper) const override; | ||||
| 	}; | ||||
| } | ||||
| @@ -168,10 +168,20 @@ | ||||
|     <ClCompile Include="Goals\Win.cpp" /> | ||||
|     <ClCompile Include="main.cpp" /> | ||||
|     <ClCompile Include="MapObjectsEvaluator.cpp" /> | ||||
|     <ClInclude Include="Pathfinding\Actions\BattleAction.h" /> | ||||
|     <ClInclude Include="Pathfinding\Actions\TownPortalAction.h" /> | ||||
|     <ClCompile Include="Pathfinding\Actions\BattleAction.cpp" /> | ||||
|     <ClCompile Include="Pathfinding\Actions\TownPortalAction.cpp" /> | ||||
|     <ClInclude Include="Pathfinding\Actions\BoatActions.h" /> | ||||
|     <ClCompile Include="Pathfinding\Actions\BoatActions.cpp" /> | ||||
|     <ClCompile Include="Pathfinding\AINodeStorage.cpp" /> | ||||
|     <ClCompile Include="Pathfinding\AIPathfinder.cpp" /> | ||||
|     <ClCompile Include="Pathfinding\AIPathfinderConfig.cpp" /> | ||||
|     <ClCompile Include="Pathfinding\PathfindingManager.cpp" /> | ||||
|     <ClCompile Include="Pathfinding\Rules\AILayerTransitionRule.cpp" /> | ||||
|     <ClCompile Include="Pathfinding\Rules\AIMovementAfterDestinationRule.cpp" /> | ||||
|     <ClCompile Include="Pathfinding\Rules\AIMovementToDestinationRule.cpp" /> | ||||
|     <ClCompile Include="Pathfinding\Rules\AIPreviousNodeRule.cpp" /> | ||||
|     <ClCompile Include="ResourceManager.cpp" /> | ||||
|     <ClCompile Include="SectorMap.cpp" /> | ||||
|     <ClCompile Include="StdInc.cpp"> | ||||
| @@ -214,10 +224,15 @@ | ||||
|     <ClInclude Include="Goals\VisitTile.h" /> | ||||
|     <ClInclude Include="Goals\Win.h" /> | ||||
|     <ClInclude Include="MapObjectsEvaluator.h" /> | ||||
|     <ClInclude Include="Pathfinding\Actions\ISpecialAction.h" /> | ||||
|     <ClInclude Include="Pathfinding\AINodeStorage.h" /> | ||||
|     <ClInclude Include="Pathfinding\AIPathfinder.h" /> | ||||
|     <ClInclude Include="Pathfinding\AIPathfinderConfig.h" /> | ||||
|     <ClInclude Include="Pathfinding\PathfindingManager.h" /> | ||||
|     <ClInclude Include="Pathfinding\Rules\AILayerTransitionRule.h" /> | ||||
|     <ClInclude Include="Pathfinding\Rules\AIMovementAfterDestinationRule.h" /> | ||||
|     <ClInclude Include="Pathfinding\Rules\AIMovementToDestinationRule.h" /> | ||||
|     <ClInclude Include="Pathfinding\Rules\AIPreviousNodeRule.h" /> | ||||
|     <ClInclude Include="ResourceManager.h" /> | ||||
|     <ClInclude Include="SectorMap.h" /> | ||||
|     <ClInclude Include="StdInc.h" /> | ||||
|   | ||||
| @@ -90,6 +90,15 @@ | ||||
|     <ClCompile Include="Goals\AdventureSpellCast.cpp"> | ||||
|       <Filter>Goals</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="Pathfinding\Actions\BoatActions.cpp"> | ||||
|       <Filter>Pathfinding\Actions</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="Pathfinding\Actions\TownPortalAction.cpp" /> | ||||
|     <ClCompile Include="Pathfinding\Actions\BattleAction.cpp" /> | ||||
|     <ClCompile Include="Pathfinding\Rules\AILayerTransitionRule.cpp" /> | ||||
|     <ClCompile Include="Pathfinding\Rules\AIMovementAfterDestinationRule.cpp" /> | ||||
|     <ClCompile Include="Pathfinding\Rules\AIMovementToDestinationRule.cpp" /> | ||||
|     <ClCompile Include="Pathfinding\Rules\AIPreviousNodeRule.cpp" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <ClInclude Include="AIhelper.h" /> | ||||
| @@ -189,6 +198,16 @@ | ||||
|     <ClInclude Include="Goals\AdventureSpellCast.h"> | ||||
|       <Filter>Goals</Filter> | ||||
|     </ClInclude> | ||||
|     <ClInclude Include="Pathfinding\Actions\ISpecialAction.h"> | ||||
|       <Filter>Pathfinding\Actions</Filter> | ||||
|     </ClInclude> | ||||
|     <ClInclude Include="Pathfinding\Actions\TownPortalAction.h" /> | ||||
|     <ClInclude Include="Pathfinding\Actions\BattleAction.h" /> | ||||
|     <ClInclude Include="Pathfinding\Actions\BoatActions.h" /> | ||||
|     <ClInclude Include="Pathfinding\Rules\AILayerTransitionRule.h" /> | ||||
|     <ClInclude Include="Pathfinding\Rules\AIMovementAfterDestinationRule.h" /> | ||||
|     <ClInclude Include="Pathfinding\Rules\AIMovementToDestinationRule.h" /> | ||||
|     <ClInclude Include="Pathfinding\Rules\AIPreviousNodeRule.h" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <Filter Include="Pathfinding"> | ||||
| @@ -197,5 +216,11 @@ | ||||
|     <Filter Include="Goals"> | ||||
|       <UniqueIdentifier>{f97140a0-eee3-456f-b586-4b13265c01da}</UniqueIdentifier> | ||||
|     </Filter> | ||||
|     <Filter Include="Pathfinding\Rules"> | ||||
|       <UniqueIdentifier>{beabfdb9-2e76-4daa-8d1a-81086387f319}</UniqueIdentifier> | ||||
|     </Filter> | ||||
|     <Filter Include="Pathfinding\Actions"> | ||||
|       <UniqueIdentifier>{3ebb4852-a986-447a-b5cc-20992df76f0c}</UniqueIdentifier> | ||||
|     </Filter> | ||||
|   </ItemGroup> | ||||
| </Project> | ||||
		Reference in New Issue
	
	Block a user