Browse Source

AI: add ExecuteChain goal

Andrii Danylchenko 4 years ago
parent
commit
ffa626dc2f

+ 5 - 0
AI/Nullkiller/AIhelper.cpp

@@ -149,6 +149,11 @@ std::vector<AIPath> AIhelper::getPathsToTile(const HeroPtr & hero, const int3 &
 	return pathfindingManager->getPathsToTile(hero, tile);
 }
 
+std::vector<AIPath> AIhelper::getPathsToTile(const int3 & tile) const
+{
+	return pathfindingManager->getPathsToTile(tile);
+}
+
 void AIhelper::updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain)
 {
 	pathfindingManager->updatePaths(heroes, useHeroChain);

+ 1 - 0
AI/Nullkiller/AIhelper.h

@@ -62,6 +62,7 @@ public:
 	Goals::TGoalVec howToVisitTile(const int3 & tile, bool allowGatherArmy = true) const override;
 	Goals::TGoalVec howToVisitObj(ObjectIdRef obj, bool allowGatherArmy = true) const override;
 	std::vector<AIPath> getPathsToTile(const HeroPtr & hero, const int3 & tile) const override;
+	std::vector<AIPath> getPathsToTile(const int3 & tile) const override;
 	void updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain = false) override;
 
 	STRONG_INLINE

+ 36 - 16
AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp

@@ -10,6 +10,7 @@
 #include "StdInc.h"
 #include "../VCAI.h"
 #include "../AIhelper.h"
+#include "../Goals/ExecuteHeroChain.h"
 #include "CaptureObjectsBehavior.h"
 #include "../AIUtility.h"
 #include "lib/mapping/CMap.h" //for victory conditions
@@ -38,32 +39,51 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks() {
 				continue;
 
 			const int3 pos = objToVisit->visitablePos();
-			Goals::TGoalVec waysToVisitObj = ai->ah->howToVisitObj(objToVisit, false);
+			auto paths = ai->ah->getPathsToTile(pos);
+			std::vector<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj;
+			std::shared_ptr<ExecuteHeroChain> closestWay;
 
-			vstd::erase_if(waysToVisitObj, [objToVisit](Goals::TSubgoal goal) -> bool
+			for(auto & path : paths)
 			{
-				return !goal->hero.validAndSet() 
-					|| !shouldVisit(goal->hero, objToVisit);
-			});
+#ifdef VCMI_TRACE_PATHFINDER
+				std::stringstream str;
 
-			if(waysToVisitObj.empty())
-				continue;
+				str << "Path found ";
 
-			Goals::TSubgoal closestWay = *vstd::minElementByFun(waysToVisitObj, [](Goals::TSubgoal goal) -> float {
-				return goal->evaluationContext.movementCost;
-			});
+				for(auto node : path.nodes)
+					str << node.targetHero->name << "->" << node.coord.toString() << "; ";
 
-			for(Goals::TSubgoal way : waysToVisitObj)
-			{
-				if(!way->hero->movement)
-					continue;
+				logAi->trace(str.str());
+#endif
+
+				if(!shouldVisit(path.targetHero, objToVisit))
+					continue; 
+				
+				auto hero = path.targetHero;
+				auto danger = path.getTotalDanger(hero);
+
+				if(isSafeToVisit(hero, path.heroArmy, danger))
+				{
+					auto newWay = std::make_shared<ExecuteHeroChain>(path, objToVisit);
+
+					waysToVisitObj.push_back(newWay);
 
+					if(!closestWay || closestWay->evaluationContext.movementCost > newWay->evaluationContext.movementCost)
+						closestWay = newWay;
+				}
+			}
+			
+			if(waysToVisitObj.empty())
+				continue;
+			
+			for(auto way : waysToVisitObj)
+			{
 				way->evaluationContext.closestWayRatio 
 					= way->evaluationContext.movementCost / closestWay->evaluationContext.movementCost;
 
 				logAi->trace("Behavior %s found %s(%s), danger %d", toString(), way->name(), way->tile.toString(), way->evaluationContext.danger);
-
-				tasks.push_back(way);
+		
+				tasks.push_back(sptr(*way));
 			}
 		}
 	};

+ 2 - 0
AI/Nullkiller/CMakeLists.txt

@@ -52,6 +52,7 @@ set(VCAI_SRCS
 		Goals/GetArtOfType.cpp
 		Goals/FindObj.cpp
 		Goals/CompleteQuest.cpp
+		Goals/ExecuteHeroChain.cpp
 		Engine/Nullkiller.cpp
 		Engine/PriorityEvaluator.cpp
 		Behaviors/Behavior.cpp
@@ -111,6 +112,7 @@ set(VCAI_HEADERS
 		Goals/GetArtOfType.h
 		Goals/FindObj.h
 		Goals/CompleteQuest.h
+		Goals/ExecuteHeroChain.h
 		Goals/Goals.h
 		Engine/Nullkiller.h
 		Engine/PriorityEvaluator.h

+ 1 - 0
AI/Nullkiller/Engine/Nullkiller.h

@@ -14,6 +14,7 @@ public:
 	Nullkiller();
 	void makeTurn();
 	bool isActive(const CGHeroInstance * hero) const { return activeHero.h == hero; }
+	void setActive(const HeroPtr & hero) { activeHero = hero; }
 
 private:
 	Goals::TSubgoal choseBestTask(Behavior & behavior);

+ 2 - 1
AI/Nullkiller/Goals/AbstractGoal.h

@@ -64,7 +64,8 @@ namespace Goals
 		TRADE, //val resID at object objid
 		BUILD_BOAT,
 		COMPLETE_QUEST,
-		ADVENTURE_SPELL_CAST
+		ADVENTURE_SPELL_CAST,
+		EXECUTE_HERO_CHAIN
 	};
 
 	class DLL_EXPORT TSubgoal : public std::shared_ptr<AbstractGoal>

+ 111 - 0
AI/Nullkiller/Goals/ExecuteHeroChain.cpp

@@ -0,0 +1,111 @@
+/*
+* ExecuteHeroChain.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 "ExecuteHeroChain.h"
+#include "VisitTile.h"
+#include "../VCAI.h"
+#include "../FuzzyHelper.h"
+#include "../AIhelper.h"
+#include "../../lib/mapping/CMap.h" //for victory conditions
+#include "../../lib/CPathfinder.h"
+#include "../Engine/Nullkiller.h"
+
+extern boost::thread_specific_ptr<CCallback> cb;
+extern boost::thread_specific_ptr<VCAI> ai;
+extern FuzzyHelper * fh;
+
+using namespace Goals;
+
+ExecuteHeroChain::ExecuteHeroChain(const AIPath & path, const CGObjectInstance * obj)
+	:CGoal(Goals::EXECUTE_HERO_CHAIN), chainPath(path)
+{
+	if(obj)
+		objid = obj->id.getNum();
+
+	evaluationContext.danger = path.getTotalDanger(hero);
+	evaluationContext.movementCost = path.movementCost();
+	evaluationContext.armyLoss = path.armyLoss;
+	evaluationContext.heroStrength = path.getHeroStrength();
+	hero = path.targetHero;
+	tile = path.firstTileToGet();
+}
+
+bool ExecuteHeroChain::operator==(const ExecuteHeroChain & other) const
+{
+	return false;
+}
+
+TSubgoal ExecuteHeroChain::whatToDoToAchieve()
+{
+	return iAmElementar();
+}
+
+void ExecuteHeroChain::accept(VCAI * ai)
+{
+	logAi->debug("Executing hero chain towards %s", tile.toString());
+
+#ifdef VCMI_TRACE_PATHFINDER
+	std::stringstream str;
+
+	str << "Path ";
+
+	for(auto node : chainPath.nodes)
+		str << node.targetHero->name << "->" << node.coord.toString() << "; ";
+
+	logAi->trace(str.str());
+#endif
+
+	std::set<int> blockedIndexes;
+
+	for(int i = chainPath.nodes.size() - 1; i >= 0; i--)
+	{
+		auto & node = chainPath.nodes[i];
+
+		HeroPtr hero = node.targetHero;
+		auto vt = Goals::sptr(Goals::VisitTile(node.coord).sethero(hero));
+
+		if(vstd::contains(blockedIndexes, i))
+		{
+			blockedIndexes.insert(node.parentIndex);
+			ai->setGoal(hero, vt);
+
+			continue;
+		}
+
+		logAi->debug("Moving hero %s to %s", hero.name, node.coord.toString());
+
+		try
+		{
+			ai->nullkiller->setActive(hero);
+			vt->accept(ai);
+
+			blockedIndexes.insert(node.parentIndex);
+		}
+		catch(goalFulfilledException)
+		{
+			if(!hero)
+			{
+				logAi->debug("Hero %s was killed while attempting to rich %s", hero.name, node.coord.toString());
+
+				return;
+			}
+		}
+	}
+}
+
+std::string ExecuteHeroChain::name() const
+{
+	return "ExecuteHeroChain";
+}
+
+std::string ExecuteHeroChain::completeMessage() const
+{
+	return "Hero chain completed";
+}

+ 35 - 0
AI/Nullkiller/Goals/ExecuteHeroChain.h

@@ -0,0 +1,35 @@
+/*
+* ExecuteHeroChain.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 "CGoal.h"
+
+namespace Goals
+{
+	class DLL_EXPORT ExecuteHeroChain : public CGoal<ExecuteHeroChain>
+	{
+	private:
+		AIPath chainPath;
+
+	public:
+		ExecuteHeroChain(const AIPath & path, const CGObjectInstance * obj = nullptr);
+
+		TGoalVec getAllPossibleSubgoals() override
+		{
+			return TGoalVec();
+		}
+
+		TSubgoal whatToDoToAchieve() override;
+		void accept(VCAI * ai) override;
+		std::string name() const override;
+		std::string completeMessage() const override;
+		virtual bool operator==(const ExecuteHeroChain & other) const override;
+	};
+}

+ 6 - 3
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -731,7 +731,7 @@ std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand)
 		path.targetObjectDanger = evaluateDanger(pos, path.targetHero);
 		path.chainMask = node.actor->chainMask;
 		
-		fillChainInfo(&node, path);
+		fillChainInfo(&node, path, -1);
 
 		paths.push_back(path);
 	}
@@ -739,7 +739,7 @@ std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand)
 	return paths;
 }
 
-void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path) const
+void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const
 {
 	while(node != nullptr)
 	{
@@ -747,7 +747,7 @@ void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path) const
 			return;
 
 		if(node->chainOther)
-			fillChainInfo(node->chainOther, path);
+			fillChainInfo(node->chainOther, path, parentIndex);
 
 		if(node->actor->hero->visitablePos() != node->coord)
 		{
@@ -757,6 +757,9 @@ void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path) const
 			pathNode.turns = node->turns;
 			pathNode.danger = node->danger;
 			pathNode.coord = node->coord;
+			pathNode.parentIndex = parentIndex;
+
+			parentIndex = path.nodes.size();
 
 			path.nodes.push_back(pathNode);
 		}

+ 2 - 1
AI/Nullkiller/Pathfinding/AINodeStorage.h

@@ -39,6 +39,7 @@ struct AIPathNodeInfo
 	int3 coord;
 	uint64_t danger;
 	const CGHeroInstance * targetHero;
+	int parentIndex;
 };
 
 struct AIPath
@@ -158,7 +159,7 @@ private:
 	void addHeroChain(const std::vector<ExchangeCandidate> & result);
 
 	void calculateTownPortalTeleportations(const PathNodeInfo & source, std::vector<CGPathNode *> & neighbours);
-	void fillChainInfo(const AIPathNode * node, AIPath & path) const;
+	void fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const;
 	void commit(
 		AIPathNode * destination, 
 		const AIPathNode * source, 

+ 1 - 1
AI/Nullkiller/Pathfinding/AIPathfinder.cpp

@@ -31,7 +31,7 @@ bool AIPathfinder::isTileAccessible(const HeroPtr & hero, const int3 & tile) con
 		|| storage->isTileAccessible(hero, tile, EPathfindingLayer::SAIL);
 }
 
-std::vector<AIPath> AIPathfinder::getPathInfo(const HeroPtr & hero, const int3 & tile) const
+std::vector<AIPath> AIPathfinder::getPathInfo(const int3 & tile) const
 {
 	const TerrainTile * tileInfo = cb->getTile(tile, false);
 

+ 1 - 1
AI/Nullkiller/Pathfinding/AIPathfinder.h

@@ -23,7 +23,7 @@ private:
 
 public:
 	AIPathfinder(CPlayerSpecificInfoCallback * cb, VCAI * ai);
-	std::vector<AIPath> getPathInfo(const HeroPtr & hero, const int3 & tile) const;
+	std::vector<AIPath> getPathInfo(const int3 & tile) const;
 	bool isTileAccessible(const HeroPtr & hero, const int3 & tile) const;
 	void updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain = false);
 	void init();

+ 13 - 2
AI/Nullkiller/Pathfinding/PathfindingManager.cpp

@@ -104,7 +104,18 @@ Goals::TGoalVec PathfindingManager::howToVisitObj(const HeroPtr & hero, ObjectId
 
 std::vector<AIPath> PathfindingManager::getPathsToTile(const HeroPtr & hero, const int3 & tile) const
 {
-	return pathfinder->getPathInfo(hero, tile);
+	auto paths = pathfinder->getPathInfo(tile);
+
+	vstd::erase_if(paths, [&](AIPath & path) -> bool{
+		return path.targetHero != hero.h;
+	});
+
+	return paths;
+}
+
+std::vector<AIPath> PathfindingManager::getPathsToTile(const int3 & tile) const
+{
+	return pathfinder->getPathInfo(tile);
 }
 
 Goals::TGoalVec PathfindingManager::findPaths(
@@ -117,7 +128,7 @@ Goals::TGoalVec PathfindingManager::findPaths(
 	boost::optional<uint64_t> armyValueRequired;
 	uint64_t danger;
 
-	std::vector<AIPath> chainInfo = pathfinder->getPathInfo(hero, dest);
+	std::vector<AIPath> chainInfo = pathfinder->getPathInfo(dest);
 
 #ifdef VCMI_TRACE_PATHFINDER
 	logAi->trace("Trying to find a way for %s to visit tile %s", hero->name, dest.toString());

+ 2 - 0
AI/Nullkiller/Pathfinding/PathfindingManager.h

@@ -26,6 +26,7 @@ public:
 	virtual Goals::TGoalVec howToVisitTile(const int3 & tile, bool allowGatherArmy = true) const = 0;
 	virtual Goals::TGoalVec howToVisitObj(ObjectIdRef obj, bool allowGatherArmy = true) const = 0;
 	virtual std::vector<AIPath> getPathsToTile(const HeroPtr & hero, const int3 & tile) const = 0;
+	virtual std::vector<AIPath> getPathsToTile(const int3 & tile) const = 0;
 };
 
 class DLL_EXPORT PathfindingManager : public IPathfindingManager
@@ -46,6 +47,7 @@ public:
 	Goals::TGoalVec howToVisitTile(const int3 & tile, bool allowGatherArmy = true) const override;
 	Goals::TGoalVec howToVisitObj(ObjectIdRef obj, bool allowGatherArmy = true) const override;
 	std::vector<AIPath> getPathsToTile(const HeroPtr & hero, const int3 & tile) const override;
+	std::vector<AIPath> getPathsToTile(const int3 & tile) const override;
 	void updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain = false) override;
 
 	STRONG_INLINE

+ 11 - 9
AI/Nullkiller/VCAI.cpp

@@ -603,7 +603,7 @@ void VCAI::init(std::shared_ptr<CCallback> CB)
 	if(!fh)
 		fh = new FuzzyHelper();
 
-	if(playerID.getStr(false) == "green")
+	if(playerID.getStr(false) == "blue")
 	{
 		nullkiller.reset(new Nullkiller());
 	}
@@ -815,16 +815,18 @@ void VCAI::makeTurn()
 		{
 			nullkiller->makeTurn();
 		}
+		else
+		{
+			//it looks messy here, but it's better to have armed heroes before attempting realizing goals
+			for(const CGTownInstance * t : cb->getTownsInfo())
+				moveCreaturesToHero(t);
 
-		//it looks messy here, but it's better to have armed heroes before attempting realizing goals
-		for(const CGTownInstance * t : cb->getTownsInfo())
-			moveCreaturesToHero(t);
-
-		mainLoop();
+			mainLoop();
 
-		/*Below function is also responsible for hero movement via internal wander function. By design it is separate logic for heroes that have nothing to do.
-		Heroes that were not picked by striveToGoal(sptr(Goals::Win())); recently (so they do not have new goals and cannot continue/reevaluate previously locked goals) will do logic in wander().*/
-		performTypicalActions();
+			/*Below function is also responsible for hero movement via internal wander function. By design it is separate logic for heroes that have nothing to do.
+			Heroes that were not picked by striveToGoal(sptr(Goals::Win())); recently (so they do not have new goals and cannot continue/reevaluate previously locked goals) will do logic in wander().*/
+			performTypicalActions();
+		}
 
 		//for debug purpose
 		for (auto h : cb->getHeroesInfo())