2
0
Эх сурвалжийг харах

NKAI: improve army gathering

Andrii Danylchenko 2 жил өмнө
parent
commit
148c3436df

+ 13 - 7
AI/Nullkiller/AIGateway.cpp

@@ -590,13 +590,18 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
 
 			if(hero.validAndSet() && target.valid() && objects.size())
 			{
-				auto objType = objects.back()->ID;
-
+				auto topObj = objects.front()->id == hero->id ? objects.back() : objects.front();
+				auto objType = topObj->ID; // top object should be our hero
+				auto goalObjectID = nullkiller->getTargetObject();
 				auto ratio = (float)nullkiller->dangerEvaluator->evaluateDanger(target, hero.get()) / (float)hero->getTotalStrength();
-				bool dangerUnknown = ratio == 0;
-				bool dangerTooHigh = ratio > (1 / SAFE_ATTACK_CONSTANT);
 
-				answer = objects.back()->id == nullkiller->getTargetObject(); // no if we do not aim to visit this object
+				answer = topObj->id == goalObjectID; // no if we do not aim to visit this object
+				logAi->trace("Query hook: %s(%s) by %s danger ratio %f", target.toString(), topObj->getObjectName(), hero.name, ratio);
+
+				if(cb->getObj(goalObjectID, false))
+				{
+					logAi->trace("AI expected %s", cb->getObj(goalObjectID, false)->getObjectName());
+				}
 
 				if(objType == Obj::BORDERGUARD || objType == Obj::QUEST_GUARD)
 				{
@@ -604,9 +609,10 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
 				}
 				else if(objType == Obj::ARTIFACT || objType == Obj::RESOURCE)
 				{
-					logAi->trace("Guarded object query hook: %s by %s danger ratio %f", target.toString(), hero.name, ratio);
+					bool dangerUnknown = ratio == 0;
+					bool dangerTooHigh = ratio > (1 / SAFE_ATTACK_CONSTANT);
 
-					answer = dangerUnknown || dangerTooHigh;
+					answer = !dangerUnknown && !dangerTooHigh;
 				}
 			}
 

+ 1 - 1
AI/Nullkiller/Analyzers/HeroManager.cpp

@@ -109,7 +109,7 @@ void HeroManager::update()
 		return scores.at(h1) > scores.at(h2);
 	};
 
-	int globalMainCount = std::min(((int)myHeroes.size() + 2) / 3, cb->getMapSize().x / 100 + 1);
+	int globalMainCount = std::min(((int)myHeroes.size() + 2) / 3, cb->getMapSize().x / 50 + 1);
 
 	std::sort(myHeroes.begin(), myHeroes.end(), scoreSort);
 

+ 13 - 6
AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp

@@ -81,7 +81,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
 		auto hero = path.targetHero;
 		auto danger = path.getTotalDanger();
 
-		if(ai->nullkiller->heroManager->getHeroRole(hero) == HeroRole::SCOUT && danger == 0 && path.exchangeCount > 1)
+		if(ai->nullkiller->heroManager->getHeroRole(hero) == HeroRole::SCOUT && path.exchangeCount > 1)
 			continue;
 
 		auto firstBlockedAction = path.getFirstBlockedAction();
@@ -126,8 +126,13 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
 
 			sharedPtr.reset(newWay);
 
-			if(!closestWay || closestWay->movementCost() > path.movementCost())
+			auto heroRole = ai->nullkiller->heroManager->getHeroRole(path.targetHero);
+
+			if(heroRole == HeroRole::SCOUT
+				&& (!closestWay || closestWay->movementCost() > path.movementCost()))
+			{
 				closestWay = &path;
+			}
 
 			if(!ai->nullkiller->arePathHeroesLocked(path))
 			{
@@ -137,11 +142,13 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
 		}
 	}
 
-	assert(closestWay || waysToVisitObj.empty());
-	for(auto way : waysToVisitObj)
+	if(closestWay)
 	{
-		way->closestWayRatio
-			= closestWay->movementCost() / way->getPath().movementCost();
+		for(auto way : waysToVisitObj)
+		{
+			way->closestWayRatio
+				= closestWay->movementCost() / way->getPath().movementCost();
+		}
 	}
 
 	return tasks;

+ 45 - 18
AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp

@@ -45,8 +45,7 @@ Goals::TGoalVec GatherArmyBehavior::decompose() const
 
 	for(const CGHeroInstance * hero : heroes)
 	{
-		if(ai->nullkiller->heroManager->getHeroRole(hero) == HeroRole::MAIN
-			&& hero->getArmyStrength() >= 300)
+		if(ai->nullkiller->heroManager->getHeroRole(hero) == HeroRole::MAIN)
 		{
 			vstd::concatenate(tasks, deliverArmyToHero(hero));
 		}
@@ -70,13 +69,6 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
 #if NKAI_TRACE_LEVEL >= 1
 	logAi->trace("Checking ways to gaher army for hero %s, %s", hero->getObjectName(), pos.toString());
 #endif
-	if(ai->nullkiller->isHeroLocked(hero))
-	{
-#if NKAI_TRACE_LEVEL >= 1
-		logAi->trace("Skipping locked hero %s, %s", hero->getObjectName(), pos.toString());
-#endif
-		return tasks;
-	}
 
 	auto paths = ai->nullkiller->pathfinder->getPathInfo(pos);
 
@@ -92,6 +84,14 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
 		
 		if(path.containsHero(hero)) continue;
 
+		if(path.turn() == 0 && hero->inTownGarrison)
+		{
+#if NKAI_TRACE_LEVEL >= 1
+			logAi->trace("Skipping garnisoned hero %s, %s", hero->getObjectName(), pos.toString());
+#endif
+			continue;
+		}
+
 		if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
 		{
 #if NKAI_TRACE_LEVEL >= 2
@@ -124,14 +124,32 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
 		// avoid trying to move bigger army to the weaker one.
 		if(armyValue > 1)
 		{
+			bool hasOtherMainInPath = false;
+
+			for(auto node : path.nodes)
+			{
+				if(!node.targetHero) continue;
+
+				auto heroRole = ai->nullkiller->heroManager->getHeroRole(node.targetHero);
+
+				if(heroRole == HeroRole::MAIN)
+				{
+					hasOtherMainInPath = true;
+
+					break;
+				}
+			}
+
+			if(hasOtherMainInPath)
+			{
 #if NKAI_TRACE_LEVEL >= 2
-			logAi->trace("Army value is too large.");
+				logAi->trace("Army value is too large.");
 #endif
-			continue;
+				continue;
+			}
 		}
 
 		auto danger = path.getTotalDanger();
-
 		auto isSafe = isSafeToVisit(hero, path.heroArmy, danger);
 
 #if NKAI_TRACE_LEVEL >= 2
@@ -194,7 +212,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
 #if NKAI_TRACE_LEVEL >= 2
 		logAi->trace("Path found %s", path.toString());
 #endif
-		if(upgrader->visitingHero != path.targetHero)
+		if(upgrader->visitingHero && upgrader->visitingHero.get() != path.targetHero)
 		{
 #if NKAI_TRACE_LEVEL >= 2
 			logAi->trace("Ignore path. Town has visiting hero.");
@@ -219,7 +237,10 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
 			continue;
 		}
 
-		if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
+		auto heroRole = ai->nullkiller->heroManager->getHeroRole(path.targetHero);
+
+		if(heroRole == HeroRole::SCOUT
+			&& ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
 		{
 #if NKAI_TRACE_LEVEL >= 2
 			logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength());
@@ -228,16 +249,22 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
 		}
 
 		auto upgrade = ai->nullkiller->armyManager->calculateCreaturesUpgrade(path.heroArmy, upgrader, availableResources);
-		auto armyValue = (float)upgrade.upgradeValue / path.getHeroStrength();
 
 		if(ai->nullkiller->heroManager->getHeroRole(path.targetHero) == HeroRole::MAIN)
 		{
-			upgrade.upgradeValue +=
-				ai->nullkiller->armyManager->howManyReinforcementsCanGet(path.targetHero, path.heroArmy, upgrader);
+			upgrade.upgradeValue +=	
+				ai->nullkiller->armyManager->howManyReinforcementsCanGet(path.targetHero, path.heroArmy, upgrader);	
 		}
 
-		if(armyValue < 0.1f || upgrade.upgradeValue < 300) // avoid small upgrades
+		auto armyValue = (float)upgrade.upgradeValue / path.getHeroStrength();
+
+		if(armyValue < 0.25f || upgrade.upgradeValue < 300) // avoid small upgrades
+		{
+#if NKAI_TRACE_LEVEL >= 2
+			logAi->trace("Ignore path. Army value is too small (%f)", armyValue);
+#endif
 			continue;
+		}
 
 		auto danger = path.getTotalDanger();
 

+ 14 - 2
AI/Nullkiller/Engine/FuzzyHelper.cpp

@@ -115,8 +115,20 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
 	{
 	case Obj::TOWN:
 	{
-		const CGTownInstance * cre = dynamic_cast<const CGTownInstance *>(obj);
-		return cre->getUpperArmy()->getArmyStrength();
+		const CGTownInstance * town = dynamic_cast<const CGTownInstance *>(obj);
+		auto danger = town->getUpperArmy()->getArmyStrength();
+
+		if(danger || town->visitingHero)
+		{
+			auto fortLevel = town->fortLevel();
+
+			if(fortLevel == CGTownInstance::EFortLevel::CASTLE)
+				danger += 10000;
+			else if(fortLevel == CGTownInstance::EFortLevel::CITADEL)
+				danger += 4000;
+		}
+
+		return danger;
 	}
 	case Obj::ARTIFACT:
 	case Obj::RESOURCE:

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

@@ -222,7 +222,7 @@ void Nullkiller::makeTurn()
 	boost::lock_guard<boost::mutex> sharedStorageLock(AISharedStorage::locker);
 
 	const int MAX_DEPTH = 10;
-	const int FAST_TASK_MINIMAL_PRIORITY = 0.7;
+	const float FAST_TASK_MINIMAL_PRIORITY = 0.7;
 
 	resetAiState();
 

+ 19 - 9
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -81,6 +81,12 @@ void PriorityEvaluator::initVisitTile()
 	value = engine->getOutputVariable("Value");
 }
 
+bool isAnotherAi(const CGObjectInstance * obj, const CPlayerSpecificInfoCallback & cb)
+{
+	return obj->getOwner().isValidPlayer()
+		&& cb.getStartInfo()->getIthPlayersSettings(obj->getOwner()).isControlledByAI();
+}
+
 int32_t estimateTownIncome(CCallback * cb, const CGObjectInstance * target, const CGHeroInstance * hero)
 {
 	auto relations = cb->getPlayerRelations(hero->tempOwner, target->tempOwner);
@@ -88,15 +94,17 @@ int32_t estimateTownIncome(CCallback * cb, const CGObjectInstance * target, cons
 	if(relations != PlayerRelations::ENEMIES)
 		return 0; // if we already own it, no additional reward will be received by just visiting it
 
+	auto booster = isAnotherAi(target, *cb) ? 1 : 2;
+
 	auto town = cb->getTown(target->id);
 	auto fortLevel = town->fortLevel();
 
-	if(town->hasCapitol()) return 4000;
+	if(town->hasCapitol()) return booster * 2000;
 
 	// probably well developed town will have city hall
-	if(fortLevel == CGTownInstance::CASTLE) return 1500;
+	if(fortLevel == CGTownInstance::CASTLE) return booster * 750;
 	
-	return town->hasFort() && town->tempOwner != PlayerColor::NEUTRAL  ? 1000 : 500;
+	return booster * (town->hasFort() && town->tempOwner != PlayerColor::NEUTRAL  ? booster * 500 : 250);
 }
 
 TResources getCreatureBankResources(const CGObjectInstance * target, const CGHeroInstance * hero)
@@ -247,11 +255,12 @@ uint64_t RewardEvaluator::getArmyReward(
 	{
 		auto town = dynamic_cast<const CGTownInstance *>(target);
 		auto fortLevel = town->fortLevel();
+		auto booster = isAnotherAi(town, *ai->cb) ? 1 : 2;
 
 		if(fortLevel < CGTownInstance::CITADEL)
-			return town->hasFort() ? 1000 : 0;
+			return town->hasFort() ? booster * 500 : 0;
 		else
-			return fortLevel == CGTownInstance::CASTLE ? 10000 : 4000;
+			return booster * (fortLevel == CGTownInstance::CASTLE ? 5000 : 2000);
 	}
 
 	case Obj::HILL_FORT:
@@ -395,13 +404,14 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
 
 		auto town = dynamic_cast<const CGTownInstance *>(target);
 		auto fortLevel = town->fortLevel();
+		auto booster = isAnotherAi(town, *ai->cb) ? 0.3 : 1;
 
 		if(town->hasCapitol()) return 1;
 
 		if(fortLevel < CGTownInstance::CITADEL)
-			return town->hasFort() ? 0.6 : 0.4;
+			return booster * (town->hasFort() ? 0.6 : 0.4);
 		else
-			return fortLevel == CGTownInstance::CASTLE ? 0.9 : 0.8;
+			return booster * (fortLevel == CGTownInstance::CASTLE ? 0.9 : 0.8);
 	}
 
 	case Obj::HERO:
@@ -579,6 +589,7 @@ public:
 		uint64_t upgradeValue = armyUpgrade.getUpgradeValue();
 
 		evaluationContext.armyReward += upgradeValue;
+		evaluationContext.strategicalValue += upgradeValue / armyUpgrade.hero->getTotalStrength();
 	}
 };
 
@@ -590,8 +601,7 @@ void addTileDanger(EvaluationContext & evaluationContext, const int3 & tile, uin
 	{
 		auto dangerRatio = enemyDanger.danger / (double)ourStrength;
 		auto enemyHero = evaluationContext.evaluator.ai->cb->getObj(enemyDanger.hero.hid, false);
-		bool isAI =enemyHero
-			&& evaluationContext.evaluator.ai->cb->getStartInfo()->getIthPlayersSettings(enemyHero->getOwner()).isControlledByAI();
+		bool isAI = enemyHero && isAnotherAi(enemyHero, *evaluationContext.evaluator.ai->cb);
 
 		if(isAI)
 		{