Andrii Danylchenko 2 anni fa
parent
commit
6490c65490

+ 25 - 0
AI/Nullkiller/Analyzers/HeroManager.cpp

@@ -229,6 +229,31 @@ const CGHeroInstance * HeroManager::findHeroWithGrail() const
 	return nullptr;
 }
 
+const CGHeroInstance * HeroManager::findWeakHeroToDismiss(uint64_t armyLimit) const
+{
+	const CGHeroInstance * weakestHero = nullptr;
+	auto myHeroes = ai->cb->getHeroesInfo();
+
+	for(auto existingHero : myHeroes)
+	{
+		if(ai->isHeroLocked(existingHero)
+			|| existingHero->getArmyStrength() >armyLimit
+			|| getHeroRole(existingHero) == HeroRole::MAIN
+			|| existingHero->movementPointsRemaining()
+			|| existingHero->artifactsWorn.size() > (existingHero->hasSpellbook() ? 2 : 1))
+		{
+			continue;
+		}
+
+		if(!weakestHero || weakestHero->getFightingStrength() > existingHero->getFightingStrength())
+		{
+			weakestHero = existingHero;
+		}
+	}
+
+	return weakestHero;
+}
+
 SecondarySkillScoreMap::SecondarySkillScoreMap(std::map<SecondarySkill, float> scoreMap)
 	:scoreMap(scoreMap)
 {

+ 2 - 0
AI/Nullkiller/Analyzers/HeroManager.h

@@ -33,6 +33,7 @@ public:
 	virtual bool canRecruitHero(const CGTownInstance * t = nullptr) const = 0;
 	virtual bool heroCapReached() const = 0;
 	virtual const CGHeroInstance * findHeroWithGrail() const = 0;
+	virtual const CGHeroInstance * findWeakHeroToDismiss(uint64_t armyLimit) const = 0;
 };
 
 class DLL_EXPORT ISecondarySkillRule
@@ -74,6 +75,7 @@ public:
 	bool canRecruitHero(const CGTownInstance * t = nullptr) const override;
 	bool heroCapReached() const override;
 	const CGHeroInstance * findHeroWithGrail() const override;
+	const CGHeroInstance * findWeakHeroToDismiss(uint64_t armyLimit) const override;
 
 private:
 	float evaluateFightingStrength(const CGHeroInstance * hero) const;

+ 31 - 39
AI/Nullkiller/Behaviors/DefenceBehavior.cpp

@@ -121,16 +121,31 @@ bool handleGarrisonHeroFromPreviousTurn(const CGTownInstance * town, Goals::TGoa
 		return true;
 	}
 
-	if(!town->visitingHero && cb->getHeroCount(ai->playerID, false) < GameConstants::MAX_HEROES_PER_PLAYER)
+	if(!town->visitingHero)
 	{
-		logAi->trace(
-			"Extracting hero %s from garrison of town %s",
-			town->garrisonHero->getNameTranslated(),
-			town->getNameTranslated());
+		if(cb->getHeroCount(ai->playerID, false) < GameConstants::MAX_HEROES_PER_PLAYER)
+		{
+			logAi->trace(
+				"Extracting hero %s from garrison of town %s",
+				town->garrisonHero->getNameTranslated(),
+				town->getNameTranslated());
 
-		tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, nullptr).setpriority(5)));
+			tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, nullptr).setpriority(5)));
 
-		return true;
+			return true;
+		}
+		else if(ai->nullkiller->heroManager->getHeroRole(town->garrisonHero.get()) == HeroRole::MAIN)
+		{
+			auto armyDismissLimit = 1000;
+			auto heroToDismiss = ai->nullkiller->heroManager->findWeakHeroToDismiss(armyDismissLimit);
+
+			if(heroToDismiss)
+			{
+				tasks.push_back(Goals::sptr(Goals::DismissHero(heroToDismiss).setpriority(5)));
+
+				return true;
+			}
+		}
 	}
 
 	return false;
@@ -141,14 +156,6 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 	logAi->trace("Evaluating defence for %s", town->getNameTranslated());
 
 	auto treatNode = ai->nullkiller->dangerHitMap->getObjectTreat(town);
-
-	if(!treatNode.fastestDanger.hero)
-	{
-		logAi->trace("No treat found for town %s", town->getNameTranslated());
-
-		return;
-	}
-
 	std::vector<HitMapInfo> treats = ai->nullkiller->dangerHitMap->getTownTreats(town);
 	
 	treats.push_back(treatNode.fastestDanger); // no guarantee that fastest danger will be there
@@ -157,6 +164,13 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 	{
 		return;
 	}
+
+	if(!treatNode.fastestDanger.hero)
+	{
+		logAi->trace("No treat found for town %s", town->getNameTranslated());
+
+		return;
+	}
 	
 	uint64_t reinforcement = ai->nullkiller->armyManager->howManyReinforcementsCanBuy(town->getUpperArmy(), town);
 
@@ -224,11 +238,6 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 				if(path.getHeroStrength() < townDefenseStrength)
 					continue;
 			}
-			else
-			{
-				if(town->visitingHero)
-					continue;
-			}
 
 			if(path.turn() <= treat.turn - 2)
 			{
@@ -440,27 +449,10 @@ void DefenceBehavior::evaluateRecruitingHero(Goals::TGoalVec & tasks, const HitM
 			}
 			else if(ai->nullkiller->heroManager->heroCapReached())
 			{
-				const CGHeroInstance * weakestHero = nullptr;
-
-				for(auto existingHero : myHeroes)
-				{
-					if(ai->nullkiller->isHeroLocked(existingHero)
-						|| existingHero->getArmyStrength() > hero->getArmyStrength()
-						|| ai->nullkiller->heroManager->getHeroRole(existingHero) == HeroRole::MAIN
-						|| existingHero->movementPointsRemaining()
-						|| existingHero->artifactsWorn.size() > (existingHero->hasSpellbook() ? 2 : 1))
-						continue;
+				heroToDismiss = ai->nullkiller->heroManager->findWeakHeroToDismiss(hero->getArmyStrength());
 
-					if(!weakestHero || weakestHero->getFightingStrength() > existingHero->getFightingStrength())
-					{
-						weakestHero = existingHero;
-					}
-				}
-
-				if(!weakestHero)
+				if(!heroToDismiss)
 					continue;
-				
-				heroToDismiss = weakestHero;
 			}
 
 			TGoalVec sequence;

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

@@ -592,7 +592,6 @@ int32_t getArmyCost(const CArmedInstance * army)
 	return value;
 }
 
-/// Gets aproximated reward in gold. Daily income is multiplied by 5
 int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CGHeroInstance * hero) const
 {
 	if(!target)
@@ -713,9 +712,6 @@ public:
 
 		auto strategicalValue = evaluationContext.evaluator.getStrategicalValue(town);
 
-		if(evaluationContext.evaluator.ai->buildAnalyzer->getDevelopmentInfo().size() == 1)
-			vstd::amax(evaluationContext.strategicalValue, 10.0);
-
 		float multiplier = 1;
 
 		if(treat.turn < defendTown.getTurn())
@@ -736,7 +732,12 @@ public:
 
 		evaluationContext.armyGrowth += armyGrowth * multiplier;
 		evaluationContext.goldReward += dailyIncome * 5 * multiplier;
-		evaluationContext.addNonCriticalStrategicalValue(1.7f * multiplier * strategicalValue);
+
+		if(evaluationContext.evaluator.ai->buildAnalyzer->getDevelopmentInfo().size() == 1)
+			vstd::amax(evaluationContext.strategicalValue, 2.5f * multiplier * strategicalValue);
+		else
+			evaluationContext.addNonCriticalStrategicalValue(1.7f * multiplier * strategicalValue);
+
 		vstd::amax(evaluationContext.danger, defendTown.getTreat().danger);
 		addTileDanger(evaluationContext, town->visitablePos(), defendTown.getTurn(), defendTown.getDefenceStrength());
 	}

+ 14 - 1
config/ai/object-priorities.txt

@@ -171,6 +171,10 @@ RuleBlock: basic
   rule: if heroRole is SCOUT and turn is NEXT and mainTurnDistance is LONG then Value is BAD
   rule: if heroRole is SCOUT and turn is NOW and scoutTurnDistance is LONG then Value is BAD
   rule: if heroRole is SCOUT and turn is NOW and scoutTurnDistance is MEDIUM then Value is BAD with 0.3
+  rule: if heroRole is SCOUT and fear is HIGH then Value is BAD with 0.8
+  rule: if heroRole is SCOUT and fear is MEDIUM then Value is BAD with 0.5
+  rule: if heroRole is MAIN and fear is HIGH then Value is BAD with 0.5
+  rule: if heroRole is MAIN and fear is MEDIUM then Value is BAD with 0.2
 RuleBlock: strategicalValue
   enabled: true
   conjunction: AlgebraicProduct
@@ -205,7 +209,8 @@ RuleBlock: strategicalValue
   rule: if heroRole is SCOUT and strategicalValue is LOW and danger is NONE and scoutTurnDistance is MEDIUM and fear is not HIGH then Value is SMALL
   rule: if armyLoss is HIGH and strategicalValue is LOW then Value is BAD
   rule: if armyLoss is HIGH and strategicalValue is MEDIUM then Value is BAD with 0.7
-  rule: if strategicalValue is CRITICAL then Value is CRITICAL
+  rule: if strategicalValue is CRITICAL and heroRole is MAIN then Value is CRITICAL
+  rule: if strategicalValue is CRITICAL and heroRole is SCOUT then Value is CRITICAL with 0.7
 RuleBlock: armyReward
   enabled: true
   conjunction: AlgebraicProduct
@@ -220,6 +225,14 @@ RuleBlock: armyReward
   rule: if heroRole is MAIN and armyReward is MEDIUM and mainTurnDistance is LONG and fear is not HIGH then Value is MEDIUM
   rule: if heroRole is MAIN and armyReward is LOW and mainTurnDistance is LOW and fear is not HIGH then Value is MEDIUM
   rule: if heroRole is MAIN and armyReward is LOW and mainTurnDistance is MEDIUM and fear is not HIGH then Value is SMALL
+  rule: if heroRole is SCOUT and armyReward is HIGH and danger is NONE and scoutTurnDistance is LOW and fear is not HIGH then Value is HIGH
+  rule: if heroRole is SCOUT and armyReward is HIGH and danger is NONE and scoutTurnDistance is MEDIUM and fear is not HIGH then Value is HIGH with 0.7
+  rule: if heroRole is SCOUT and armyReward is HIGH and danger is NONE and scoutTurnDistance is LONG and fear is not HIGH then Value is BITHIGH
+  rule: if heroRole is SCOUT and armyReward is MEDIUM and danger is NONE and scoutTurnDistance is LOW then Value is HIGH with 0.7
+  rule: if heroRole is SCOUT and armyReward is MEDIUM and danger is NONE and scoutTurnDistance is MEDIUM and fear is not HIGH then Value is BITHIGH
+  rule: if heroRole is SCOUT and armyReward is MEDIUM and danger is NONE and scoutTurnDistance is LONG and fear is not HIGH then Value is MEDIUM
+  rule: if heroRole is SCOUT and armyReward is LOW and danger is NONE and scoutTurnDistance is LOW and fear is not HIGH then Value is MEDIUM
+  rule: if heroRole is SCOUT and armyReward is LOW and danger is NONE and scoutTurnDistance is MEDIUM and fear is not HIGH then Value is SMALL
 RuleBlock: gold
   enabled: true
   conjunction: AlgebraicProduct