浏览代码

Fix defence crashes and hangs

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

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

@@ -155,14 +155,14 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const
 		logAi->debug("Scanning objects, count %d", objs.size());
 
 		for(auto objToVisit : objs)
-		{			
+		{
+			if(!objectMatchesFilter(objToVisit))
+				continue;
+	
 #if AI_TRACE_LEVEL >= 1
 			logAi->trace("Checking object %s, %s", objToVisit->getObjectName(), objToVisit->visitablePos().toString());
 #endif
 
-			if(!objectMatchesFilter(objToVisit))
-				continue;
-
 			const int3 pos = objToVisit->visitablePos();
 
 			auto paths = ai->nullkiller->pathfinder->getPathInfo(pos);

+ 56 - 45
AI/Nullkiller/Behaviors/DefenceBehavior.cpp

@@ -14,8 +14,11 @@
 #include "../AIUtility.h"
 #include "../Goals/BuyArmy.h"
 #include "../Goals/ExecuteHeroChain.h"
+#include "../Behaviors/CaptureObjectsBehavior.h"
 #include "../Goals/RecruitHero.h"
 #include "../Goals/DismissHero.h"
+#include "../Goals/Composition.h"
+#include "../Markers/DefendTown.h"
 #include "../Goals/ExchangeSwapTownHeroes.h"
 #include "lib/mapping/CMap.h" //for victory conditions
 #include "lib/CPathfinder.h"
@@ -42,28 +45,9 @@ Goals::TGoalVec DefenceBehavior::decompose() const
 	return tasks;
 }
 
-uint64_t townArmyIncome(const CGTownInstance * town)
-{
-	uint64_t result = 0;
-
-	for(auto creatureInfo : town->creatures)
-	{
-		if(creatureInfo.second.empty())
-			continue;
-
-		auto creature = creatureInfo.second.back().toCreature();
-		result += creature->AIValue * town->getGrowthInfo(creature->level).totalGrowth();
-	}
-
-	return result;
-}
-
 void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInstance * town) const
 {
-	auto basicPriority = 0.3f + std::sqrt(townArmyIncome(town) / 40000.0f)
-		+ town->dailyIncome()[Res::GOLD] / 10000.0f;
-
-	logAi->debug("Evaluating defence for %s, basic priority %f", town->name, basicPriority);
+	logAi->debug("Evaluating defence for %s", town->name);
 
 	auto treatNode = ai->nullkiller->dangerHitMap->getObjectTreat(town);
 	auto treats = { treatNode.fastestDanger, treatNode.maximumDanger };
@@ -195,11 +179,13 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 			continue;
 		}
 
-		std::vector<Goals::ExecuteHeroChain> pathsToDefend;
-		std::map<const CGHeroInstance *, std::vector<AIPath>> defferedPaths;
+		std::vector<int> pathsToDefend;
+		std::map<const CGHeroInstance *, std::vector<int>> defferedPaths;
 
-		for(AIPath & path : paths)
+		for(int i = 0; i < paths.size(); i++)
 		{
+			auto & path = paths[i];
+
 #if AI_TRACE_LEVEL >= 1
 			logAi->trace(
 				"Hero %s can defend town with force %lld in %s turns, cost: %f, path: %s",
@@ -215,27 +201,29 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 					town->name,
 					path.targetHero->name);
 
-				defferedPaths[path.targetHero].push_back(path);
+				defferedPaths[path.targetHero].push_back(i);
 
 				continue;
 			}
 
-			float priority = basicPriority * std::min(SAFE_ATTACK_CONSTANT, (float)path.getHeroStrength() / treat.danger) 
-				- treat.turn * 0.2f;
-
-			if(treat.turn < path.turn())
-				priority /= (path.turn() - treat.turn) * 2;
-
 			if(path.targetHero == town->visitingHero && path.exchangeCount == 1)
 			{
 #if AI_TRACE_LEVEL >= 1
-				logAi->trace("Put %s to garrison of town %s with priority %f",
+				logAi->trace("Put %s to garrison of town %s",
 					path.targetHero->name,
-					town->name,
-					priority);
+					town->name);
 #endif
 
-				tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, town->visitingHero.get(), HeroLockedReason::DEFENCE).setpriority(priority)));
+				// dismiss creatures we are not able to pick to be able to hide in garrison
+				if(town->garrisonHero
+					|| town->getUpperArmy()->stacksCount() == 0
+					|| town->getUpperArmy()->getArmyStrength() < 500 && town->fortLevel() >= CGTownInstance::CITADEL)
+				{
+					tasks.push_back(
+						Goals::sptr(Composition()
+							.addNext(DefendTown(town, treat, path.targetHero))
+							.addNext(ExchangeSwapTownHeroes(town, town->visitingHero.get(), HeroLockedReason::DEFENCE))));
+				}
 
 				continue;
 			}
@@ -245,25 +233,26 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 				if(ai->nullkiller->arePathHeroesLocked(path))
 				{
 #if AI_TRACE_LEVEL >= 1
-					logAi->trace("Can not move %s to defend town %s with priority %f. Path is locked.",
+					logAi->trace("Can not move %s to defend town %s. Path is locked.",
 						path.targetHero->name,
-						town->name,
-						priority);
+						town->name);
 
 #endif
 					continue;
 				}
 
-				pathsToDefend.push_back(Goals::ExecuteHeroChain(path, town).setpriority(priority));
+				pathsToDefend.push_back(i);
 			}
 		}
 
-		for(Goals::ExecuteHeroChain & chain : pathsToDefend)
+		for(int i : pathsToDefend)
 		{
-			auto path = chain.getPath();
+			AIPath & path = paths[i];
 
-			for(AIPath & defferedPath : defferedPaths[path.targetHero])
+			for(int j : defferedPaths[path.targetHero])
 			{
+				AIPath & defferedPath = paths[j];
+
 				if(defferedPath.getHeroStrength() >= path.getHeroStrength()
 					&& defferedPath.turn() <= path.turn())
 				{
@@ -272,13 +261,35 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 			}
 
 #if AI_TRACE_LEVEL >= 1
-			logAi->trace("Move %s to defend town %s with priority %f",
+			logAi->trace("Move %s to defend town %s",
 				path.targetHero->name,
-				town->name,
-				chain.priority);
+				town->name);
+#endif
+			Composition composition;
+
+			composition.addNext(DefendTown(town, treat, path)).addNext(ExecuteHeroChain(path, town));
+
+			auto firstBlockedAction = path.getFirstBlockedAction();
+			if(firstBlockedAction)
+			{
+				auto subGoal = firstBlockedAction->decompose(path.targetHero);
+
+#if AI_TRACE_LEVEL >= 2
+				logAi->trace("Decomposing special action %s returns %s", firstBlockedAction->toString(), subGoal->toString());
 #endif
 
-			tasks.push_back(Goals::sptr(chain));
+				if(subGoal->invalid())
+				{
+#if AI_TRACE_LEVEL >= 1
+					logAi->trace("Path is invalid. Skipping");
+#endif
+					continue;
+				}
+
+				composition.addNext(subGoal);
+			}
+
+			tasks.push_back(Goals::sptr(composition));
 		}
 	}
 

+ 7 - 1
AI/Nullkiller/Behaviors/StartupBehavior.cpp

@@ -176,7 +176,13 @@ Goals::TGoalVec StartupBehavior::decompose() const
 			}
 			else if(canRecruitHero)
 			{
-				tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, visitingHero, HeroLockedReason::STARTUP).setpriority(100)));
+				auto canPickTownArmy = startupTown->stacksCount() == 0
+					|| ai->nullkiller->armyManager->howManyReinforcementsCanGet(startupTown->visitingHero, startupTown) > 0;
+
+				if(canPickTownArmy)
+				{
+					tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, visitingHero, HeroLockedReason::STARTUP).setpriority(100)));
+				}
 			}
 		}
 	}

+ 2 - 0
AI/Nullkiller/CMakeLists.txt

@@ -37,6 +37,7 @@ set(VCAI_SRCS
 		Markers/ArmyUpgrade.cpp
 		Markers/HeroExchange.cpp
 		Markers/UnlockCluster.cpp
+		Markers/DefendTown.cpp
 		Engine/Nullkiller.cpp
 		Engine/DeepDecomposer.cpp
 		Engine/PriorityEvaluator.cpp
@@ -97,6 +98,7 @@ set(VCAI_HEADERS
 		Markers/ArmyUpgrade.h
 		Markers/HeroExchange.h
 		Markers/UnlockCluster.h
+		Markers/DefendTown.h
 		Engine/Nullkiller.h
 		Engine/DeepDecomposer.h
 		Engine/PriorityEvaluator.h

+ 48 - 0
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -24,6 +24,7 @@
 #include "../Goals/BuildThis.h"
 #include "../Markers/HeroExchange.h"
 #include "../Markers/ArmyUpgrade.h"
+#include "../Markers/DefendTown.h"
 
 #define MIN_AI_STRENGHT (0.5f) //lower when combat AI gets smarter
 #define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
@@ -493,6 +494,51 @@ public:
 	}
 };
 
+class DefendTownEvaluator : public IEvaluationContextBuilder
+{
+private:
+	uint64_t townArmyIncome(const CGTownInstance * town) const
+	{
+		uint64_t result = 0;
+
+		for(auto creatureInfo : town->creatures)
+		{
+			if(creatureInfo.second.empty())
+				continue;
+
+			auto creature = creatureInfo.second.back().toCreature();
+			result += creature->AIValue * town->getGrowthInfo(creature->level).totalGrowth();
+		}
+
+		return result;
+	}
+
+public:
+	virtual void buildEvaluationContext(EvaluationContext & evaluationContext, Goals::TSubgoal task) const override
+	{
+		if(task->goalType != Goals::DEFEND_TOWN)
+			return;
+
+		Goals::DefendTown & defendTown = dynamic_cast<Goals::DefendTown &>(*task);
+		const CGTownInstance * town = defendTown.town;
+		auto & treat = defendTown.getTreat();
+
+		auto armyIncome = townArmyIncome(town);
+		auto dailyIncome = town->dailyIncome()[Res::GOLD];
+
+		auto strategicalValue = std::sqrt(armyIncome / 20000.0f) + dailyIncome / 10000.0f;
+
+		float multiplier = 1;
+
+		if(treat.turn < defendTown.getTurn())
+			multiplier /= 1 + (defendTown.getTurn() - treat.turn);
+
+		evaluationContext.armyReward += armyIncome * multiplier;
+		evaluationContext.goldReward += dailyIncome * 5 * multiplier;
+		evaluationContext.strategicalValue += strategicalValue * multiplier;
+	}
+};
+
 class ExecuteHeroChainEvaluationContextBuilder : public IEvaluationContextBuilder
 {
 public:
@@ -653,6 +699,8 @@ PriorityEvaluator::PriorityEvaluator(const Nullkiller * ai)
 	evaluationContextBuilders.push_back(std::make_shared<BuildThisEvaluationContextBuilder>());
 	evaluationContextBuilders.push_back(std::make_shared<ClusterEvaluationContextBuilder>());
 	evaluationContextBuilders.push_back(std::make_shared<HeroExchangeEvaluator>());
+	evaluationContextBuilders.push_back(std::make_shared<ArmyUpgradeEvaluator>());
+	evaluationContextBuilders.push_back(std::make_shared<ArmyUpgradeEvaluator>());
 }
 
 EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal) const

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

@@ -65,7 +65,8 @@ namespace Goals
 		CLUSTER_BEHAVIOR,
 		UNLOCK_CLUSTER,
 		HERO_EXCHANGE,
-		ARMY_UPGRADE
+		ARMY_UPGRADE,
+		DEFEND_TOWN
 	};
 
 	class DLL_EXPORT TSubgoal : public std::shared_ptr<AbstractGoal>

+ 1 - 1
AI/Nullkiller/Goals/CompleteQuest.cpp

@@ -199,7 +199,7 @@ TGoalVec CompleteQuest::missionKeymaster() const
 
 TGoalVec CompleteQuest::missionResources() const
 {
-	TGoalVec solutions;
+	TGoalVec solutions = tryCompleteQuest();
 
 	/*auto heroes = cb->getHeroesInfo(); //TODO: choose best / free hero from among many possibilities?
 

+ 5 - 14
AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp

@@ -61,24 +61,15 @@ void ExchangeSwapTownHeroes::accept(VCAI * ai)
 
 	auto upperArmy = town->getUpperArmy();
 	
-	if(!town->garrisonHero && upperArmy->stacksCount() != 0)
+	if(!town->garrisonHero)
 	{
-		// dismiss creatures we are not able to pick to be able to hide in garrison
-		if(upperArmy->getArmyStrength() < 500 
-			&& town->fortLevel() >= CGTownInstance::CITADEL)
+		while(upperArmy->stacksCount() != 0)
 		{
-			for(auto slot : upperArmy->Slots())
-			{
-				cb->dismissCreature(upperArmy, slot.first);
-			}
-
-			cb->swapGarrisonHero(town);
+			cb->dismissCreature(upperArmy, upperArmy->Slots().begin()->first);
 		}
 	}
-	else
-	{
-		cb->swapGarrisonHero(town); // selected hero left in garrison with strongest army
-	}
+	
+	cb->swapGarrisonHero(town);
 
 	ai->nullkiller->lockHero(garrisonHero, lockingReason);
 

+ 40 - 0
AI/Nullkiller/Markers/DefendTown.cpp

@@ -0,0 +1,40 @@
+/*
+* ArmyUpgrade.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 "DefendTown.h"
+#include "../VCAI.h"
+#include "../Engine/Nullkiller.h"
+#include "../AIUtility.h"
+
+using namespace Goals;
+
+DefendTown::DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const AIPath & defencePath)
+	: CGoal(Goals::DEFEND_TOWN), treat(treat), defenceArmyStrength(defencePath.getHeroStrength()), turn(defencePath.turn())
+{
+	settown(town);
+	sethero(defencePath.targetHero);
+}
+
+DefendTown::DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const CGHeroInstance * defender)
+	: CGoal(Goals::DEFEND_TOWN), treat(treat), defenceArmyStrength(defender->getTotalStrength()), turn(0)
+{
+	settown(town);
+	sethero(defender);
+}
+
+bool DefendTown::operator==(const DefendTown & other) const
+{
+	return false;
+}
+
+std::string DefendTown::toString() const
+{
+	return "Defend town " + town->getObjectName();
+}

+ 39 - 0
AI/Nullkiller/Markers/DefendTown.h

@@ -0,0 +1,39 @@
+/*
+* ArmyUpgrade.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 "../Goals/CGoal.h"
+#include "../Pathfinding/AINodeStorage.h"
+#include "../Analyzers/ArmyManager.h"
+#include "../Analyzers/DangerHitMapAnalyzer.h"
+
+namespace Goals
+{
+	class DLL_EXPORT DefendTown : public CGoal<DefendTown>
+	{
+	private:
+		uint64_t defenceArmyStrength;
+		HitMapInfo treat;
+		uint8_t turn;
+
+	public:
+		DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const AIPath & defencePath);
+		DefendTown(const CGTownInstance * town, const HitMapInfo & treat, const CGHeroInstance * defender);
+
+		virtual bool operator==(const DefendTown & other) const override;
+		virtual std::string toString() const override;
+
+		const HitMapInfo & getTreat() const { return treat; }
+
+		uint64_t getDefenceStrength() const { return defenceArmyStrength; }
+
+		uint8_t getTurn() const { return turn; }
+	};
+}

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

@@ -11,7 +11,7 @@
 #pragma once
 
 #define PATHFINDER_TRACE_LEVEL 0
-#define AI_TRACE_LEVEL 1
+#define AI_TRACE_LEVEL 0
 #define SCOUT_TURN_DISTANCE_LIMIT 3
 
 #include "../../../lib/CPathfinder.h"

+ 1 - 0
AI/Nullkiller/Pathfinding/Actors.cpp

@@ -283,6 +283,7 @@ HeroActor * HeroExchangeMap::exchange(const ChainActor * other)
 			{
 				newArmy = pickBestCreatures(upgradedInitialArmy, other->creatureSet);
 				newArmy->armyCost = upgradedInitialArmy->armyCost;
+				newArmy->requireBuyArmy = upgradedInitialArmy->requireBuyArmy;
 
 				delete upgradedInitialArmy;
 			}