Browse Source

Nullkiller: startup scripts

Andrii Danylchenko 4 years ago
parent
commit
bcf8db3d05

+ 5 - 0
AI/Nullkiller/AIhelper.cpp

@@ -215,4 +215,9 @@ void AIhelper::updateHeroRoles()
 float AIhelper::evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const
 {
 	return heroManager->evaluateSecSkill(skill, hero);
+}
+
+float AIhelper::evaluateHero(const CGHeroInstance * hero) const
+{
+	return heroManager->evaluateHero(hero);
 }

+ 1 - 0
AI/Nullkiller/AIhelper.h

@@ -85,6 +85,7 @@ public:
 	int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const override;
 	void updateHeroRoles() override;
 	float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const override;
+	float evaluateHero(const CGHeroInstance * hero) const override;
 
 private:
 	bool notifyGoalCompleted(Goals::TSubgoal goal) override;

+ 123 - 2
AI/Nullkiller/Behaviors/RecruitHeroBehavior.cpp

@@ -13,6 +13,7 @@
 #include "../AIhelper.h"
 #include "../AIUtility.h"
 #include "../Goals/RecruitHero.h"
+#include "../Goals/ExecuteHeroChain.h"
 #include "lib/mapping/CMap.h" //for victory conditions
 #include "lib/CPathfinder.h"
 
@@ -33,13 +34,133 @@ Goals::TGoalVec RecruitHeroBehavior::getTasks()
 
 	if(ai->canRecruitAnyHero())
 	{
-		if(cb->getDate(Date::DAY) == 1
-			|| cb->getHeroesInfo().size() < cb->getTownsInfo().size() + 1
+		if(cb->getHeroesInfo().size() < cb->getTownsInfo().size() + 1
 			|| cb->getResourceAmount(Res::GOLD) > 10000)
 		{
 			tasks.push_back(Goals::sptr(Goals::RecruitHero()));
 		}
 	}
 
+	return tasks;
+}
+
+std::string StartupBehavior::toString() const
+{
+	return "Startup";
+}
+
+const AIPath getShortestPath(const CGTownInstance * town, const std::vector<AIPath> & paths)
+{
+	auto shortestPath = *vstd::minElementByFun(paths, [town](const AIPath & path) -> float
+	{
+		if(town->garrisonHero && path.targetHero == town->garrisonHero.get())
+			return 1;
+
+		return path.movementCost();
+	});
+
+	return shortestPath;
+}
+
+const CGHeroInstance * getNearestHero(const CGTownInstance * town)
+{
+	auto paths = ai->ah->getPathsToTile(town->visitablePos());
+
+	if(paths.empty())
+		return nullptr;
+
+	auto shortestPath = getShortestPath(town, paths);
+
+	if(shortestPath.nodes.size() > 1 
+		|| shortestPath.targetHero->visitablePos().dist2dSQ(town->visitablePos()) > 4
+		|| town->garrisonHero && shortestPath.targetHero == town->garrisonHero.get())
+		return nullptr;
+
+	return shortestPath.targetHero;
+}
+
+Goals::TGoalVec StartupBehavior::getTasks()
+{
+	Goals::TGoalVec tasks;
+	auto towns = cb->getTownsInfo();
+
+	if(!towns.size())
+		return tasks;
+
+	const CGTownInstance * startupTown = towns.front();
+	bool canRecruitHero = ai->canRecruitAnyHero(startupTown);
+		
+	if(towns.size() > 1)
+	{
+		startupTown = *vstd::maxElementByFun(towns, [](const CGTownInstance * town) -> float
+		{
+			auto closestHero = getNearestHero(town);
+
+			if(!closestHero)
+				return 0;
+
+			return ai->ah->evaluateHero(closestHero);
+		});
+	}
+
+	auto closestHero = getNearestHero(startupTown);
+
+	if(closestHero)
+	{
+		if(!startupTown->visitingHero)
+		{
+			if(ai->ah->howManyReinforcementsCanGet(startupTown->getUpperArmy(), closestHero) > 200)
+			{
+				auto paths = ai->ah->getPathsToTile(startupTown->visitablePos());
+
+				if(paths.size())
+				{
+					auto path = getShortestPath(startupTown, paths);
+
+					tasks.push_back(Goals::sptr(Goals::ExecuteHeroChain(path, startupTown).setpriority(100)));
+				}
+			}
+		}
+		else
+		{
+			auto visitingHero = startupTown->visitingHero.get();
+			auto visitingHeroScore = ai->ah->evaluateHero(visitingHero);
+				
+			if(startupTown->garrisonHero)
+			{
+				auto garrisonHero = startupTown->garrisonHero.get();
+				auto garrisonHeroScore = ai->ah->evaluateHero(garrisonHero);
+
+				if(garrisonHeroScore > visitingHeroScore)
+				{
+					if(ai->ah->howManyReinforcementsCanGet(garrisonHero, visitingHero) > 200)
+						tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, garrisonHero).setpriority(100)));
+				}
+				else
+				{
+					tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, visitingHero).setpriority(100)));
+				}
+			}
+			else if(canRecruitHero)
+			{
+				tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, visitingHero).setpriority(100)));
+			}
+		}
+	}
+
+	if(tasks.empty() && canRecruitHero && !startupTown->visitingHero)
+	{
+		tasks.push_back(Goals::sptr(Goals::RecruitHero()));
+	}
+
+	if(tasks.empty() && towns.size())
+	{
+		for(const CGTownInstance * town : towns)
+		{
+			if(town->garrisonHero)
+				tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(town, nullptr).setpriority(0.0001f)));
+		}
+	}
+
 	return tasks;
 }

+ 11 - 0
AI/Nullkiller/Behaviors/RecruitHeroBehavior.h

@@ -24,3 +24,14 @@ public:
 	virtual std::string toString() const override;
 };
 
+class StartupBehavior : public Behavior
+{
+public:
+	StartupBehavior()
+	{
+	}
+
+	virtual Goals::TGoalVec getTasks() override;
+	virtual std::string toString() const override;
+};
+

+ 5 - 0
AI/Nullkiller/Engine/Nullkiller.cpp

@@ -86,6 +86,11 @@ void Nullkiller::makeTurn()
 			choseBestTask(std::make_shared<RecruitHeroBehavior>())
 		};
 
+		if(cb->getDate(Date::DAY) == 1)
+		{
+			bestTasks.push_back(choseBestTask(std::make_shared<StartupBehavior>()));
+		}
+
 		Goals::TSubgoal bestTask = choseBestTask(bestTasks);
 
 		if(bestTask->invalid())

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

@@ -20,6 +20,7 @@ public:
 	bool isActive(const CGHeroInstance * hero) const { return activeHero.h == hero; }
 	void setActive(const HeroPtr & hero) { activeHero = hero; }
 	void lockHero(const HeroPtr & hero) { lockedHeroes.insert(hero); }
+	void unlockHero(const HeroPtr & hero) { lockedHeroes.erase(hero); }
 
 private:
 	void resetAiState();

+ 5 - 5
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -176,9 +176,9 @@ float getSkillReward(const CGObjectInstance * target, const CGHeroInstance * her
 	case Obj::MARLETTO_TOWER:
 	case Obj::MERCENARY_CAMP:
 	case Obj::SHRINE_OF_MAGIC_GESTURE:
+	case Obj::SHRINE_OF_MAGIC_INCANTATION:
 		return 1;
 	case Obj::ARENA:
-	case Obj::SHRINE_OF_MAGIC_INCANTATION:
 	case Obj::SHRINE_OF_MAGIC_THOUGHT:
 		return 2;
 	case Obj::LIBRARY_OF_ENLIGHTENMENT:
@@ -240,6 +240,9 @@ int32_t getGoldReward(const CGObjectInstance * target, const CGHeroInstance * he
 /// importance
 float PriorityEvaluator::evaluate(Goals::TSubgoal task)
 {
+	if(task->priority > 0)
+		return task->priority;
+
 	auto heroPtr = task->hero;
 
 	if(!heroPtr.validAndSet())
@@ -263,10 +266,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
 	uint64_t danger = task->evaluationContext.danger;
 	double result = 0;
 	int rewardType = (goldReward > 0 ? 1 : 0) + (armyReward > 0 ? 1 : 0) + (skillReward > 0 ? 1 : 0);
-
-	if(day == 1)
-		goldReward *= 2;
-
+	
 	try
 	{
 		armyLossPersentageVariable->setValue(armyLossPersentage);

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

@@ -65,7 +65,8 @@ namespace Goals
 		BUILD_BOAT,
 		COMPLETE_QUEST,
 		ADVENTURE_SPELL_CAST,
-		EXECUTE_HERO_CHAIN
+		EXECUTE_HERO_CHAIN,
+		EXCHANGE_SWAP_TOWN_HEROES
 	};
 
 	class DLL_EXPORT TSubgoal : public std::shared_ptr<AbstractGoal>

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

@@ -110,3 +110,59 @@ std::string ExecuteHeroChain::completeMessage() const
 {
 	return "Hero chain completed";
 }
+
+ExchangeSwapTownHeroes::ExchangeSwapTownHeroes(const CGTownInstance * town, const CGHeroInstance * garrisonHero)
+	:CGoal(Goals::EXCHANGE_SWAP_TOWN_HEROES), town(town), garrisonHero(garrisonHero)
+{
+}
+
+std::string ExchangeSwapTownHeroes::name() const
+{
+	return "Exchange and swap heroes of " + town->name;
+}
+
+std::string ExchangeSwapTownHeroes::completeMessage() const
+{
+	return "Exchange and swap heroes of " + town->name + " compelete";
+}
+
+bool ExchangeSwapTownHeroes::operator==(const ExchangeSwapTownHeroes & other) const
+{
+	return town == other.town;
+}
+
+TSubgoal ExchangeSwapTownHeroes::whatToDoToAchieve()
+{
+	return iAmElementar();
+}
+
+void ExchangeSwapTownHeroes::accept(VCAI * ai)
+{
+	if(!garrisonHero && town->garrisonHero && town->visitingHero)
+		throw cannotFulfillGoalException("Invalid configuration. Garrison hero is null.");
+
+	if(!garrisonHero)
+	{
+		if(!town->garrisonHero)
+			throw cannotFulfillGoalException("Invalid configuration. There is no hero in town garrison.");
+		
+		cb->swapGarrisonHero(town);
+		ai->nullkiller->unlockHero(town->visitingHero.get());
+		logAi->debug("Extracted hero %s from garrison of %s", town->visitingHero->name, town->name);
+
+		return;
+	}
+
+	if(town->visitingHero && town->visitingHero.get() != garrisonHero)
+		cb->swapGarrisonHero(town);
+
+	ai->moveHeroToTile(town->visitablePos(), garrisonHero);
+
+	cb->swapGarrisonHero(town); // selected hero left in garrison with strongest army
+	ai->nullkiller->lockHero(town->garrisonHero.get());
+
+	if(town->visitingHero)
+		ai->nullkiller->unlockHero(town->visitingHero.get());
+
+	logAi->debug("Put hero %s to garrison of %s", town->garrisonHero->name, town->name);
+}

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

@@ -33,4 +33,25 @@ namespace Goals
 		std::string completeMessage() const override;
 		virtual bool operator==(const ExecuteHeroChain & other) const override;
 	};
+
+	class DLL_EXPORT ExchangeSwapTownHeroes : public CGoal<ExchangeSwapTownHeroes>
+	{
+	private:
+		const CGTownInstance * town;
+		const CGHeroInstance * garrisonHero;
+
+	public:
+		ExchangeSwapTownHeroes(const CGTownInstance * town, const CGHeroInstance * garrisonHero = 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 ExchangeSwapTownHeroes & other) const override;
+	};
 }

+ 5 - 0
AI/Nullkiller/HeroManager.cpp

@@ -163,6 +163,11 @@ int HeroManager::selectBestSkill(const HeroPtr & hero, const std::vector<Seconda
 	return result;
 }
 
+float HeroManager::evaluateHero(const CGHeroInstance * hero) const
+{
+	return evaluateFightingStrength(hero);
+}
+
 SecondarySkillScoreMap::SecondarySkillScoreMap(std::map<SecondarySkill, float> scoreMap)
 	:scoreMap(scoreMap)
 {

+ 2 - 0
AI/Nullkiller/HeroManager.h

@@ -35,6 +35,7 @@ public:
 	virtual HeroRole getHeroRole(const HeroPtr & hero) const = 0;
 	virtual void updateHeroRoles() = 0;
 	virtual float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const = 0;
+	virtual float evaluateHero(const CGHeroInstance * hero) const = 0;
 };
 
 class DLL_EXPORT ISecondarySkillRule
@@ -72,6 +73,7 @@ public:
 	int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const override;
 	void updateHeroRoles() override;
 	float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const override;
+	float evaluateHero(const CGHeroInstance * hero) const override;
 
 private:
 	float evaluateFightingStrength(const CGHeroInstance * hero) const;

+ 1 - 1
AI/Nullkiller/VCAI.cpp

@@ -1077,7 +1077,7 @@ void VCAI::moveCreaturesToHero(const CGTownInstance * t)
 {
 	if(t->visitingHero && t->armedGarrison() && t->visitingHero->tempOwner == t->tempOwner)
 	{
-		pickBestCreatures(t->visitingHero, t);
+		pickBestCreatures(t->visitingHero, t->getUpperArmy());
 	}
 }