瀏覽代碼

nkai: fixes and skill rewards

Andrii Danylchenko 2 年之前
父節點
當前提交
c93bb0a502

+ 5 - 0
AI/Nullkiller/AIGateway.cpp

@@ -1058,6 +1058,11 @@ void AIGateway::recruitCreatures(const CGDwelling * d, const CArmedInstance * re
 		int count = d->creatures[i].first;
 		CreatureID creID = d->creatures[i].second.back();
 
+		if(!recruiter->getSlotFor(creID).validSlot())
+		{
+			continue;
+		}
+
 		vstd::amin(count, cb->getResourceAmount() / creID.toCreature()->getFullRecruitCost());
 		if(count > 0)
 			cb->recruitCreatures(d, recruiter, creID, count, i);

+ 37 - 11
AI/Nullkiller/Analyzers/BuildAnalyzer.cpp

@@ -68,19 +68,22 @@ void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo)
 	logAi->trace("Checking other buildings");
 
 	std::vector<std::vector<BuildingID>> otherBuildings = {
-		{BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL}
+		{BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL},
+		{BuildingID::MAGES_GUILD_3, BuildingID::MAGES_GUILD_5}
 	};
 
 	if(developmentInfo.existingDwellings.size() >= 2 && ai->cb->getDate(Date::DAY_OF_WEEK) > boost::date_time::Friday)
 	{
 		otherBuildings.push_back({BuildingID::CITADEL, BuildingID::CASTLE});
+		otherBuildings.push_back({BuildingID::HORDE_1});
+		otherBuildings.push_back({BuildingID::HORDE_2});
 	}
 
 	for(auto & buildingSet : otherBuildings)
 	{
 		for(auto & buildingID : buildingSet)
 		{
-			if(!developmentInfo.town->hasBuilt(buildingID))
+			if(!developmentInfo.town->hasBuilt(buildingID) && developmentInfo.town->town->buildings.count(buildingID))
 			{
 				developmentInfo.addBuildingToBuild(getBuildingOrPrerequisite(developmentInfo.town, buildingID));
 
@@ -190,12 +193,28 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
 	const CCreature * creature = nullptr;
 	CreatureID baseCreatureID;
 
+	int creatureLevel = -1;
+	int creatureUpgrade = 0;
+
 	if(BuildingID::DWELL_FIRST <= toBuild && toBuild <= BuildingID::DWELL_UP_LAST)
 	{
-		int level = toBuild - BuildingID::DWELL_FIRST;
-		auto creatures = townInfo->creatures.at(level % GameConstants::CREATURES_PER_TOWN);
-		auto creatureID = creatures.size() > level / GameConstants::CREATURES_PER_TOWN
-			? creatures.at(level / GameConstants::CREATURES_PER_TOWN)
+		creatureLevel = (toBuild - BuildingID::DWELL_FIRST) % GameConstants::CREATURES_PER_TOWN;
+		creatureUpgrade = (toBuild - BuildingID::DWELL_FIRST) / GameConstants::CREATURES_PER_TOWN;
+	}
+	else if(toBuild == BuildingID::HORDE_1 || toBuild == BuildingID::HORDE_1_UPGR)
+	{
+		creatureLevel = townInfo->hordeLvl.at(0);
+	}
+	else if(toBuild == BuildingID::HORDE_2 || toBuild == BuildingID::HORDE_2_UPGR)
+	{
+		creatureLevel = townInfo->hordeLvl.at(1);
+	}
+
+	if(creatureLevel >=  0)
+	{
+		auto creatures = townInfo->creatures.at(creatureLevel);
+		auto creatureID = creatures.size() > creatureUpgrade
+			? creatures.at(creatureUpgrade)
 			: creatures.front();
 
 		baseCreatureID = creatures.front();
@@ -366,12 +385,19 @@ BuildingInfo::BuildingInfo(
 		}
 		else
 		{
-			creatureGrows = creature->getGrowth();
+			if(BuildingID::DWELL_FIRST <= id && id <= BuildingID::DWELL_UP_LAST)
+			{
+				creatureGrows = creature->getGrowth();
 
-			if(town->hasBuilt(BuildingID::CASTLE))
-				creatureGrows *= 2;
-			else if(town->hasBuilt(BuildingID::CITADEL))
-				creatureGrows += creatureGrows / 2;
+				if(town->hasBuilt(BuildingID::CASTLE))
+					creatureGrows *= 2;
+				else if(town->hasBuilt(BuildingID::CITADEL))
+					creatureGrows += creatureGrows / 2;
+			}
+			else
+			{
+				creatureGrows = creature->getHorde();
+			}
 		}
 
 		armyStrength = ai->armyManager->evaluateStackPower(creature, creatureGrows);

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

@@ -213,7 +213,7 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const
 	{
 		captureObjects(ai->nullkiller->objectClusterizer->getNearbyObjects());
 
-		if(tasks.empty() || ai->nullkiller->getScanDepth() == ScanDepth::FULL)
+		if(tasks.empty() || ai->nullkiller->getScanDepth() != ScanDepth::SMALL)
 			captureObjects(ai->nullkiller->objectClusterizer->getFarObjects());
 	}
 

+ 126 - 88
AI/Nullkiller/Behaviors/DefenceBehavior.cpp

@@ -49,41 +49,98 @@ Goals::TGoalVec DefenceBehavior::decompose() const
 	return tasks;
 }
 
-void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInstance * town) const
+bool isTreatUnderControl(const CGTownInstance * town, const HitMapInfo & treat, const std::vector<AIPath> & paths)
 {
-	logAi->trace("Evaluating defence for %s", town->getNameTranslated());
-
-	auto treatNode = ai->nullkiller->dangerHitMap->getObjectTreat(town);
-	std::vector<HitMapInfo> treats = ai->nullkiller->dangerHitMap->getTownTreats(town);
-	
-	treats.push_back(treatNode.fastestDanger); // no guarantee that fastest danger will be there
-
 	int dayOfWeek = cb->getDate(Date::DAY_OF_WEEK);
 
-	if(town->garrisonHero)
+	for(const AIPath & path : paths)
 	{
-		if(ai->nullkiller->isHeroLocked(town->garrisonHero.get()))
+		bool treatIsWeak = path.getHeroStrength() / (float)treat.danger > TREAT_IGNORE_RATIO;
+		bool needToSaveGrowth = treat.turn == 0 && dayOfWeek == 7;
+
+		if(treatIsWeak && !needToSaveGrowth)
 		{
-			logAi->trace(
-				"Hero %s in garrison of town %s is suposed to defend the town",
-				town->garrisonHero->getNameTranslated(),
-				town->getNameTranslated());
+			if((path.exchangeCount == 1 && path.turn() < treat.turn)
+				|| path.turn() < treat.turn - 1
+				|| (path.turn() < treat.turn && treat.turn >= 2))
+			{
+#if NKAI_TRACE_LEVEL >= 1
+				logAi->trace(
+					"Hero %s can eliminate danger for town %s using path %s.",
+					path.targetHero->getObjectName(),
+					town->getObjectName(),
+					path.toString());
+#endif
 
-			return;
+				return true;
+			}
 		}
+	}
+
+	return false;
+}
 
-		if(!town->visitingHero && cb->getHeroCount(ai->playerID, false) < GameConstants::MAX_HEROES_PER_PLAYER)
+void handleCounterAttack(
+	const CGTownInstance * town,
+	const HitMapInfo & treat,
+	const HitMapInfo & maximumDanger,
+	Goals::TGoalVec & tasks)
+{
+	if(treat.hero.validAndSet()
+		&& treat.turn <= 1
+		&& (treat.danger == maximumDanger.danger || treat.turn < maximumDanger.turn))
+	{
+		auto heroCapturingPaths = ai->nullkiller->pathfinder->getPathInfo(treat.hero->visitablePos());
+		auto goals = CaptureObjectsBehavior::getVisitGoals(heroCapturingPaths, treat.hero.get());
+
+		for(int i = 0; i < heroCapturingPaths.size(); i++)
 		{
-			logAi->trace(
-				"Extracting hero %s from garrison of town %s",
-				town->garrisonHero->getNameTranslated(),
-				town->getNameTranslated());
+			AIPath & path = heroCapturingPaths[i];
+			TSubgoal goal = goals[i];
 
-			tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, nullptr).setpriority(5)));
+			if(!goal || goal->invalid() || !goal->isElementar()) continue;
 
-			return;
+			Composition composition;
+
+			composition.addNext(DefendTown(town, treat, path, true)).addNext(goal);
+
+			tasks.push_back(Goals::sptr(composition));
 		}
 	}
+}
+
+bool handleGarrisonHeroFromPreviousTurn(const CGTownInstance * town, Goals::TGoalVec & tasks)
+{
+	if(ai->nullkiller->isHeroLocked(town->garrisonHero.get()))
+	{
+		logAi->trace(
+			"Hero %s in garrison of town %s is suposed to defend the town",
+			town->garrisonHero->getNameTranslated(),
+			town->getNameTranslated());
+
+		return true;
+	}
+
+	if(!town->visitingHero && 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)));
+
+		return true;
+	}
+
+	return false;
+}
+
+void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInstance * town) const
+{
+	logAi->trace("Evaluating defence for %s", town->getNameTranslated());
+
+	auto treatNode = ai->nullkiller->dangerHitMap->getObjectTreat(town);
 
 	if(!treatNode.fastestDanger.hero)
 	{
@@ -91,6 +148,15 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 
 		return;
 	}
+
+	std::vector<HitMapInfo> treats = ai->nullkiller->dangerHitMap->getTownTreats(town);
+	
+	treats.push_back(treatNode.fastestDanger); // no guarantee that fastest danger will be there
+
+	if(town->garrisonHero && handleGarrisonHeroFromPreviousTurn(town, tasks))
+	{
+		return;
+	}
 	
 	uint64_t reinforcement = ai->nullkiller->armyManager->howManyReinforcementsCanBuy(town->getUpperArmy(), town);
 
@@ -111,74 +177,12 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 			std::to_string(treat.turn),
 			treat.hero->getNameTranslated());
 
-		bool treatIsUnderControl = false;
+		handleCounterAttack(town, treat, treatNode.maximumDanger, tasks);
 
-		for(AIPath & path : paths)
+		if(isTreatUnderControl(town, treat, paths))
 		{
-			if(town->visitingHero && path.targetHero == town->visitingHero.get())
-			{
-				if(path.getHeroStrength() < town->visitingHero->getHeroStrength())
-					continue;
-			}
-			else if(town->garrisonHero && path.targetHero == town->garrisonHero.get())
-			{
-				if(path.getHeroStrength() < town->visitingHero->getHeroStrength())
-					continue;
-			}
-			else
-			{
-				if(town->visitingHero)
-					continue;
-			}
-
-			if(treat.hero.validAndSet()
-				&& treat.turn <= 1
-				&& (treat.danger == treatNode.maximumDanger.danger || treat.turn < treatNode.maximumDanger.turn))
-			{
-				auto heroCapturingPaths = ai->nullkiller->pathfinder->getPathInfo(treat.hero->visitablePos());
-				auto goals = CaptureObjectsBehavior::getVisitGoals(heroCapturingPaths, treat.hero.get());
-
-				for(int i = 0; i < heroCapturingPaths.size(); i++)
-				{
-					AIPath & path = heroCapturingPaths[i];
-					TSubgoal goal = goals[i];
-
-					if(!goal || goal->invalid() || !goal->isElementar()) continue;
-
-					Composition composition;
-
-					composition.addNext(DefendTown(town, treat, path, true)).addNext(goal);
-
-					tasks.push_back(Goals::sptr(composition));
-				}
-			}
-
-			bool treatIsWeak = path.getHeroStrength() / (float)treat.danger > TREAT_IGNORE_RATIO;
-			bool needToSaveGrowth = treat.turn == 0 && dayOfWeek == 7;
-
-			if(treatIsWeak && !needToSaveGrowth)
-			{
-				if((path.exchangeCount == 1 && path.turn() < treat.turn)
-					|| path.turn() < treat.turn - 1
-					|| (path.turn() < treat.turn && treat.turn >= 2))
-				{
-#if NKAI_TRACE_LEVEL >= 1
-					logAi->trace(
-						"Hero %s can eliminate danger for town %s using path %s.",
-						path.targetHero->getObjectName(),
-						town->getObjectName(),
-						path.toString());
-#endif
-
-					treatIsUnderControl = true;
-
-					break;
-				}
-			}
-		}
-
-		if(treatIsUnderControl)
 			continue;
+		}
 
 		evaluateRecruitingHero(tasks, treat, town);
 
@@ -205,6 +209,27 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 				path.movementCost(),
 				path.toString());
 #endif
+
+			auto townDefenseStrength = town->garrisonHero
+				? town->garrisonHero->getTotalStrength()
+				: (town->visitingHero ? town->visitingHero->getTotalStrength() : town->getUpperArmy()->getArmyStrength());
+
+			if(town->visitingHero && path.targetHero == town->visitingHero.get())
+			{
+				if(path.getHeroStrength() < townDefenseStrength)
+					continue;
+			}
+			else if(town->garrisonHero && path.targetHero == town->garrisonHero.get())
+			{
+				if(path.getHeroStrength() < townDefenseStrength)
+					continue;
+			}
+			else
+			{
+				if(town->visitingHero)
+					continue;
+			}
+
 			if(path.turn() <= treat.turn - 2)
 			{
 #if NKAI_TRACE_LEVEL >= 1
@@ -296,7 +321,20 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 			composition.addNext(DefendTown(town, treat, path));
 			TGoalVec sequence;
 
-			if(town->visitingHero && path.targetHero != town->visitingHero && !path.containsHero(town->visitingHero))
+			if(town->garrisonHero && path.targetHero == town->garrisonHero.get() && path.exchangeCount == 1)
+			{
+				composition.addNext(ExchangeSwapTownHeroes(town, town->garrisonHero.get(), HeroLockedReason::DEFENCE));
+				tasks.push_back(Goals::sptr(composition));
+
+#if NKAI_TRACE_LEVEL >= 1
+				logAi->trace("Locking hero %s in garrison of %s",
+					town->garrisonHero.get()->getObjectName(),
+					town->getObjectName());
+#endif
+
+				continue;
+			}
+			else if(town->visitingHero && path.targetHero != town->visitingHero && !path.containsHero(town->visitingHero))
 			{
 				if(town->garrisonHero)
 				{

+ 17 - 6
AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp

@@ -16,6 +16,7 @@
 #include "../Markers/HeroExchange.h"
 #include "../Markers/ArmyUpgrade.h"
 #include "GatherArmyBehavior.h"
+#include "CaptureObjectsBehavior.h"
 #include "../AIUtility.h"
 #include "../Goals/ExchangeSwapTownHeroes.h"
 
@@ -235,6 +236,8 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
 #endif
 	
 	auto paths = ai->nullkiller->pathfinder->getPathInfo(pos);
+	auto goals = CaptureObjectsBehavior::getVisitGoals(paths);
+
 	std::vector<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj;
 
 #if NKAI_TRACE_LEVEL >= 1
@@ -251,11 +254,23 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
 			hasMainAround = true;
 	}
 
-	for(const AIPath & path : paths)
+	for(int i = 0; i < paths.size(); i++)
 	{
+		auto & path = paths[i];
+		auto visitGoal = goals[i];
+
 #if NKAI_TRACE_LEVEL >= 2
 		logAi->trace("Path found %s, %s, %lld", path.toString(), path.targetHero->getObjectName(), path.heroArmy->getArmyStrength());
 #endif
+
+		if(visitGoal->invalid())
+		{
+#if NKAI_TRACE_LEVEL >= 2
+			logAi->trace("Ignore path. Not valid way.");
+#endif
+			continue;
+		}
+
 		if(upgrader->visitingHero && (upgrader->visitingHero.get() != path.targetHero || path.exchangeCount == 1))
 		{
 #if NKAI_TRACE_LEVEL >= 2
@@ -370,11 +385,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
 
 		if(isSafe)
 		{
-			ExecuteHeroChain newWay(path, upgrader);
-			
-			newWay.closestWayRatio = 1;
-
-			tasks.push_back(sptr(Composition().addNext(ArmyUpgrade(path, upgrader, upgrade)).addNext(newWay)));
+			tasks.push_back(sptr(Composition().addNext(ArmyUpgrade(path, upgrader, upgrade)).addNext(visitGoal)));
 		}
 	}
 

+ 36 - 10
AI/Nullkiller/Engine/Nullkiller.cpp

@@ -118,7 +118,7 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior, int decompositi
 void Nullkiller::resetAiState()
 {
 	lockedResources = TResources();
-	scanDepth = ScanDepth::FULL;
+	scanDepth = ScanDepth::MAIN_FULL;
 	playerID = ai->playerID;
 	lockedHeroes.clear();
 	dangerHitMap->reset();
@@ -158,11 +158,15 @@ void Nullkiller::updateAiState(int pass, bool fast)
 
 		PathfinderSettings cfg;
 		cfg.useHeroChain = useHeroChain;
-		cfg.scoutTurnDistanceLimit = SCOUT_TURN_DISTANCE_LIMIT;
 
-		if(scanDepth != ScanDepth::FULL)
+		if(scanDepth == ScanDepth::SMALL)
 		{
-			cfg.mainTurnDistanceLimit = MAIN_TURN_DISTANCE_LIMIT * ((int)scanDepth + 1);
+			cfg.mainTurnDistanceLimit = MAIN_TURN_DISTANCE_LIMIT;
+		}
+
+		if(scanDepth != ScanDepth::ALL_FULL)
+		{
+			cfg.scoutTurnDistanceLimit = SCOUT_TURN_DISTANCE_LIMIT;
 		}
 
 		boost::this_thread::interruption_point();
@@ -233,8 +237,8 @@ void Nullkiller::makeTurn()
 		updateAiState(i);
 
 		Goals::TTask bestTask = taskptr(Goals::Invalid());
-		
-		do
+
+		for(;i <= MAXPASS; i++)
 		{
 			Goals::TTaskVec fastTasks = {
 				choseBestTask(sptr(BuyArmyBehavior()), 1),
@@ -248,7 +252,11 @@ void Nullkiller::makeTurn()
 				executeTask(bestTask);
 				updateAiState(i, true);
 			}
-		} while(bestTask->priority >= FAST_TASK_MINIMAL_PRIORITY);
+			else
+			{
+				break;
+			}
+		}
 
 		Goals::TTaskVec bestTasks = {
 			bestTask,
@@ -267,7 +275,6 @@ void Nullkiller::makeTurn()
 		bestTask = choseBestTask(bestTasks);
 
 		HeroPtr hero = bestTask->getHero();
-
 		HeroRole heroRole = HeroRole::MAIN;
 
 		if(hero.validAndSet())
@@ -276,20 +283,39 @@ void Nullkiller::makeTurn()
 		if(heroRole != HeroRole::MAIN || bestTask->getHeroExchangeCount() <= 1)
 			useHeroChain = false;
 
+		// TODO: better to check turn distance here instead of priority
 		if((heroRole != HeroRole::MAIN || bestTask->priority < SMALL_SCAN_MIN_PRIORITY)
-			&& scanDepth == ScanDepth::FULL)
+			&& scanDepth == ScanDepth::MAIN_FULL)
 		{
 			useHeroChain = false;
 			scanDepth = ScanDepth::SMALL;
 
 			logAi->trace(
-				"Goal %s has too low priority %f so increasing scan depth",
+				"Goal %s has low priority %f so decreasing  scan depth to gain performance.",
 				bestTask->toString(),
 				bestTask->priority);
 		}
 
 		if(bestTask->priority < MIN_PRIORITY)
 		{
+			auto heroes = cb->getHeroesInfo();
+			auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool
+				{
+					return h->movementPointsRemaining() > 100;
+				});
+
+			if(hasMp && scanDepth != ScanDepth::ALL_FULL)
+			{
+				logAi->trace(
+					"Goal %s has too low priority %f so increasing scan depth to full.",
+					bestTask->toString(),
+					bestTask->priority);
+
+				scanDepth = ScanDepth::ALL_FULL;
+				useHeroChain = false;
+				continue;
+			}
+
 			logAi->trace("Goal %s has too low priority. It is not worth doing it. Ending turn.", bestTask->toString());
 
 			return;

+ 4 - 2
AI/Nullkiller/Engine/Nullkiller.h

@@ -40,9 +40,11 @@ enum class HeroLockedReason
 
 enum class ScanDepth
 {
-	FULL = 0,
+	MAIN_FULL = 0,
 
-	SMALL = 1
+	SMALL = 1,
+
+	ALL_FULL = 2
 };
 
 class Nullkiller

+ 7 - 1
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -915,6 +915,7 @@ public:
 		evaluationContext.heroRole = HeroRole::MAIN;
 		evaluationContext.movementCostByRole[evaluationContext.heroRole] += bi.prerequisitesCount;
 		evaluationContext.goldCost += bi.buildCostWithPrerequisits[EGameResID::GOLD];
+		evaluationContext.closestWayRatio = 1;
 
 		if(bi.creatureID != CreatureID::NONE)
 		{
@@ -938,7 +939,12 @@ public:
 			evaluationContext.addNonCriticalStrategicalValue(buildThis.town->creatures.size() * 0.2f);
 			evaluationContext.armyReward += buildThis.townInfo.armyStrength / 2;
 		}
-		else
+		else if(bi.id >= BuildingID::MAGES_GUILD_1 && bi.id <= BuildingID::MAGES_GUILD_5)
+		{
+			evaluationContext.skillReward += 2 * (bi.id - BuildingID::MAGES_GUILD_1);
+		}
+		
+		if(evaluationContext.goldReward)
 		{
 			auto goldPreasure = evaluationContext.evaluator.ai->buildAnalyzer->getGoldPreasure();
 

+ 20 - 2
config/ai/object-priorities.txt

@@ -5,7 +5,7 @@ InputVariable: mainTurnDistance
   enabled: true
   range: 0.000 10.000
   lock-range: true
-  term: LOWEST Ramp 0.250 0.000
+  term: LOWEST Ramp 0.400 0.000
   term: LOW Discrete 0.000 1.000 0.500 0.800 0.800 0.300 2.000 0.000
   term: MEDIUM Discrete 0.000 0.000 0.500 0.200 0.800 0.700 2.000 1.000 6.000 0.000
   term: LONG Discrete 2.000 0.000 6.000 1.000 10.000 0.800
@@ -238,4 +238,22 @@ RuleBlock: gold
   rule: if goldReward is MEDIUM and goldPreasure is HIGH and heroRole is SCOUT and danger is not NONE then Value is SMALL
   rule: if goldReward is MEDIUM and goldPreasure is HIGH and heroRole is MAIN and danger is not NONE and armyLoss is LOW then Value is BITHIGH
   rule: if goldReward is SMALL and goldPreasure is HIGH and heroRole is SCOUT and danger is NONE then Value is MEDIUM
-  rule: if goldReward is SMALL and goldPreasure is HIGH and heroRole is MAIN and danger is not NONE and armyLoss is LOW then Value is SMALL
+  rule: if goldReward is SMALL and goldPreasure is HIGH and heroRole is MAIN and danger is not NONE and armyLoss is LOW then Value is SMALL
+RuleBlock: skill reward
+  enabled: true
+  conjunction: AlgebraicProduct
+  disjunction: AlgebraicSum
+  implication: AlgebraicProduct
+  activation: General
+  rule: if heroRole is MAIN and skillReward is LOW and mainTurnDistance is LOWEST and fear is not HIGH then Value is HIGH
+  rule: if heroRole is MAIN and skillReward is MEDIUM and mainTurnDistance is LOWEST and fear is not HIGH then Value is HIGHEST
+  rule: if heroRole is MAIN and skillReward is HIGH and mainTurnDistance is LOWEST and fear is not HIGH then Value is HIGHEST
+  rule: if heroRole is MAIN and skillReward is LOW and mainTurnDistance is LOW and fear is not HIGH then Value is BITHIGH
+  rule: if heroRole is MAIN and skillReward is MEDIUM and mainTurnDistance is LOW and fear is not HIGH then Value is HIGH
+  rule: if heroRole is MAIN and skillReward is HIGH and mainTurnDistance is LOW and fear is not HIGH then Value is HIGHEST
+  rule: if heroRole is MAIN and skillReward is LOW and mainTurnDistance is MEDIUM and fear is not HIGH then Value is MEDIUM
+  rule: if heroRole is MAIN and skillReward is MEDIUM and mainTurnDistance is MEDIUM and fear is not HIGH then Value is BITHIGH
+  rule: if heroRole is MAIN and skillReward is HIGH and mainTurnDistance is MEDIUM and fear is not HIGH then Value is HIGH
+  rule: if heroRole is MAIN and skillReward is LOW and mainTurnDistance is LONG and fear is not HIGH then Value is SMALL
+  rule: if heroRole is MAIN and skillReward is MEDIUM and mainTurnDistance is LONG and fear is not HIGH then Value is MEDIUM
+  rule: if heroRole is MAIN and skillReward is HIGH and mainTurnDistance is LONG and fear is not HIGH then Value is BITHIGH