浏览代码

Nullkiller: dismising and hiring hero in order to defend town

Andrii Danylchenko 4 年之前
父节点
当前提交
e6eb9ccc03

+ 4 - 1
AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp

@@ -134,7 +134,10 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks()
 				way->evaluationContext.closestWayRatio
 				way->evaluationContext.closestWayRatio
 					= way->evaluationContext.movementCost / closestWay->evaluationContext.movementCost;
 					= way->evaluationContext.movementCost / closestWay->evaluationContext.movementCost;
 
 
-				tasks.push_back(sptr(*way));
+				if(way->hero && ai->nullkiller->canMove(way->hero.h))
+				{
+					tasks.push_back(sptr(*way));
+				}
 			}
 			}
 		}
 		}
 	};
 	};

+ 42 - 11
AI/Nullkiller/Behaviors/DefenceBehavior.cpp

@@ -16,6 +16,7 @@
 #include "../Goals/BuyArmy.h"
 #include "../Goals/BuyArmy.h"
 #include "../Goals/VisitTile.h"
 #include "../Goals/VisitTile.h"
 #include "../Goals/ExecuteHeroChain.h"
 #include "../Goals/ExecuteHeroChain.h"
+#include "../Goals/DismissHero.h"
 #include "../Goals/ExchangeSwapTownHeroes.h"
 #include "../Goals/ExchangeSwapTownHeroes.h"
 #include "lib/mapping/CMap.h" //for victory conditions
 #include "lib/mapping/CMap.h" //for victory conditions
 #include "lib/CPathfinder.h"
 #include "lib/CPathfinder.h"
@@ -109,13 +110,6 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 
 
 	auto paths = ai->ah->getPathsToTile(town->visitablePos());
 	auto paths = ai->ah->getPathsToTile(town->visitablePos());
 
 
-	if(paths.empty())
-	{
-		logAi->debug("No ways to defend town %s", town->name);
-
-		return;
-	}
-
 	for(auto & treat : treats)
 	for(auto & treat : treats)
 	{
 	{
 		logAi->debug(
 		logAi->debug(
@@ -131,7 +125,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 		{
 		{
 			if(path.getHeroStrength() > treat.danger)
 			if(path.getHeroStrength() > treat.danger)
 			{
 			{
-				if(dayOfWeek + treat.turn < 6 && isSafeToVisit(path.targetHero, path.heroArmy, treat.danger)
+				if(path.turn() <= treat.turn && dayOfWeek + treat.turn < 6 && isSafeToVisit(path.targetHero, path.heroArmy, treat.danger)
 					|| path.exchangeCount == 1 && path.turn() < treat.turn
 					|| path.exchangeCount == 1 && path.turn() < treat.turn
 					|| path.turn() < treat.turn - 1)
 					|| path.turn() < treat.turn - 1)
 				{
 				{
@@ -150,7 +144,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 		if(treatIsUnderControl)
 		if(treatIsUnderControl)
 			continue;
 			continue;
 
 
-		if(ai->canRecruitAnyHero(town))
+		if(cb->getResourceAmount(Res::GOLD) > GameConstants::HERO_GOLD_COST)
 		{
 		{
 			auto heroesInTavern = cb->getAvailableHeroes(town);
 			auto heroesInTavern = cb->getAvailableHeroes(town);
 
 
@@ -158,12 +152,49 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 			{
 			{
 				if(hero->getTotalStrength() > treat.danger)
 				if(hero->getTotalStrength() > treat.danger)
 				{
 				{
-					tasks.push_back(Goals::sptr(Goals::RecruitHero().settown(town).setobjid(hero->id.getNum()).setpriority(1)));
-					continue;
+					auto myHeroes = cb->getHeroesInfo();
+
+					if(cb->getHeroesInfo().size() < ALLOWED_ROAMING_HEROES)
+					{
+						logAi->debug("Hero %s can be recruited to defend %s", hero->name, town->name);
+						tasks.push_back(Goals::sptr(Goals::RecruitHero().settown(town).setobjid(hero->id.getNum()).setpriority(1)));
+						continue;
+					}
+					else
+					{
+						const CGHeroInstance * weakestHero = nullptr;
+
+						for(auto existingHero : myHeroes)
+						{
+							if(ai->nullkiller->isHeroLocked(existingHero)
+								|| existingHero->getArmyStrength() > hero->getArmyStrength()
+								|| ai->ah->getHeroRole(existingHero) == HeroRole::MAIN
+								|| existingHero->movement
+								|| existingHero->artifactsWorn.size() > (existingHero->hasSpellbook() ? 2 : 1))
+								continue;
+
+							if(!weakestHero || weakestHero->getFightingStrength() > existingHero->getFightingStrength())
+							{
+								weakestHero = existingHero;
+							}
+
+							if(weakestHero)
+							{
+								tasks.push_back(Goals::sptr(Goals::DismissHero(weakestHero)));
+							}
+						}
+					}
 				}
 				}
 			}
 			}
 		}
 		}
 
 
+		if(paths.empty())
+		{
+			logAi->debug("No ways to defend town %s", town->name);
+
+			continue;
+		}
+
 		for(AIPath & path : paths)
 		for(AIPath & path : paths)
 		{
 		{
 #if AI_TRACE_LEVEL >= 1
 #if AI_TRACE_LEVEL >= 1

+ 2 - 0
AI/Nullkiller/CMakeLists.txt

@@ -30,6 +30,7 @@ set(VCAI_SRCS
 		Goals/BuildThis.cpp
 		Goals/BuildThis.cpp
 		Goals/Explore.cpp
 		Goals/Explore.cpp
 		Goals/GatherArmy.cpp
 		Goals/GatherArmy.cpp
+		Goals/DismissHero.cpp
 		Goals/GatherTroops.cpp
 		Goals/GatherTroops.cpp
 		Goals/BuyArmy.cpp
 		Goals/BuyArmy.cpp
 		Goals/AdventureSpellCast.cpp
 		Goals/AdventureSpellCast.cpp
@@ -96,6 +97,7 @@ set(VCAI_HEADERS
 		Goals/BuildThis.h
 		Goals/BuildThis.h
 		Goals/Explore.h
 		Goals/Explore.h
 		Goals/GatherArmy.h
 		Goals/GatherArmy.h
+		Goals/DismissHero.h
 		Goals/GatherTroops.h
 		Goals/GatherTroops.h
 		Goals/BuyArmy.h
 		Goals/BuyArmy.h
 		Goals/AdventureSpellCast.h
 		Goals/AdventureSpellCast.h

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

@@ -31,6 +31,7 @@ public:
 	void setActive(const CGHeroInstance * hero) { activeHero = hero; }
 	void setActive(const CGHeroInstance * hero) { activeHero = hero; }
 	void lockHero(const CGHeroInstance * hero) { lockedHeroes.insert(hero); }
 	void lockHero(const CGHeroInstance * hero) { lockedHeroes.insert(hero); }
 	void unlockHero(const CGHeroInstance * hero) { lockedHeroes.erase(hero); }
 	void unlockHero(const CGHeroInstance * hero) { lockedHeroes.erase(hero); }
+	bool canMove(const CGHeroInstance * hero) { return hero->movement; }
 
 
 private:
 private:
 	void resetAiState();
 	void resetAiState();

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

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

+ 55 - 0
AI/Nullkiller/Goals/DismissHero.cpp

@@ -0,0 +1,55 @@
+/*
+* DismissHero.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 "DismissHero.h"
+#include "../VCAI.h"
+#include "../FuzzyHelper.h"
+#include "../AIhelper.h"
+#include "../../../lib/mapping/CMap.h" //for victory conditions
+#include "../../../lib/CPathfinder.h"
+
+extern boost::thread_specific_ptr<CCallback> cb;
+extern boost::thread_specific_ptr<VCAI> ai;
+extern FuzzyHelper * fh;
+
+using namespace Goals;
+
+bool DismissHero::operator==(const DismissHero & other) const
+{
+	return hero.h == other.hero.h;
+}
+
+TSubgoal DismissHero::whatToDoToAchieve()
+{
+	if(!hero.validAndSet())
+		throw cannotFulfillGoalException("Invalid hero!");
+
+	return iAmElementar();
+}
+
+void DismissHero::accept(VCAI * ai)
+{
+	if(!hero.validAndSet())
+		throw cannotFulfillGoalException("Invalid hero!");
+
+	cb->dismissHero(hero.h);
+
+	throw goalFulfilledException(sptr(*this));
+}
+
+std::string DismissHero::name() const
+{
+	return "DismissHero " + hero.name;
+}
+
+std::string DismissHero::completeMessage() const
+{
+	return "Hero dismissed successfully " + hero.name;
+}

+ 36 - 0
AI/Nullkiller/Goals/DismissHero.h

@@ -0,0 +1,36 @@
+/*
+* DismissHero.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 DismissHero : public CGoal<DismissHero>
+	{
+	public:
+		DismissHero(HeroPtr hero)
+			: CGoal(Goals::DISMISS_HERO)
+		{
+			sethero(hero);
+		}
+
+		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 DismissHero & other) const override;
+	};
+}

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

@@ -126,6 +126,10 @@ void ExecuteHeroChain::accept(VCAI * ai)
 				return;
 				return;
 			}
 			}
 
 
+			// do not lock hero if it is simple one hero chain
+			if(chainPath.exchangeCount == 1)
+				return;
+
 			// no exception means we were not able to rich the tile
 			// no exception means we were not able to rich the tile
 			ai->nullkiller->lockHero(hero.get());
 			ai->nullkiller->lockHero(hero.get());
 			blockedIndexes.insert(node.parentIndex);
 			blockedIndexes.insert(node.parentIndex);

+ 5 - 1
AI/Nullkiller/VCAI.cpp

@@ -2035,7 +2035,11 @@ void VCAI::tryRealize(Goals::Explore & g)
 
 
 void VCAI::tryRealize(Goals::RecruitHero & g)
 void VCAI::tryRealize(Goals::RecruitHero & g)
 {
 {
-	if(const CGTownInstance * t = findTownWithTavern())
+	const CGTownInstance * t = g.town;
+
+	if(!t) t = findTownWithTavern();
+
+	if(t)
 	{
 	{
 		recruitHero(t, true);
 		recruitHero(t, true);
 		//TODO try to free way to blocked town
 		//TODO try to free way to blocked town