浏览代码

Merge pull request #5412 from IvanSavenko/ai_scouts

NKAI - Prefer giving fast units to scouts
Ivan Savenko 8 月之前
父节点
当前提交
32a2413b5e

+ 3 - 2
AI/Nullkiller/AIGateway.cpp

@@ -411,6 +411,7 @@ void AIGateway::heroCreated(const CGHeroInstance * h)
 {
 	LOG_TRACE(logAi);
 	NET_EVENT_HANDLER;
+	nullkiller->invalidatePathfinderData(); // new hero needs to look around
 }
 
 void AIGateway::advmapSpellCast(const CGHeroInstance * caster, SpellID spellID)
@@ -929,7 +930,7 @@ void AIGateway::pickBestCreatures(const CArmedInstance * destinationArmy, const
 
 	const CArmedInstance * armies[] = {destinationArmy, source};
 
-	auto bestArmy = nullkiller->armyManager->getBestArmy(destinationArmy, destinationArmy, source);
+	auto bestArmy = nullkiller->armyManager->getBestArmy(destinationArmy, destinationArmy, source, myCb->getTile(source->visitablePos())->getTerrainID());
 
 	for(auto army : armies)
 	{
@@ -983,7 +984,7 @@ void AIGateway::pickBestCreatures(const CArmedInstance * destinationArmy, const
 						&& source->stacksCount() == 1
 						&& (!destinationArmy->hasStackAtSlot(i) || destinationArmy->getCreature(i) == targetCreature))
 					{
-						auto weakest = nullkiller->armyManager->getWeakestCreature(bestArmy);
+						auto weakest = nullkiller->armyManager->getBestUnitForScout(bestArmy, myCb->getTile(source->visitablePos())->getTerrainID());
 						
 						if(weakest->creature == targetCreature)
 						{

+ 56 - 12
AI/Nullkiller/Analyzers/ArmyManager.cpp

@@ -13,8 +13,10 @@
 #include "../Engine/Nullkiller.h"
 #include "../../../CCallback.h"
 #include "../../../lib/mapObjects/MapObjects.h"
+#include "../../../lib/mapping/CMapDefines.h"
 #include "../../../lib/IGameSettings.h"
 #include "../../../lib/GameConstants.h"
+#include "../../../lib/TerrainHandler.h"
 
 namespace NKAI
 {
@@ -76,7 +78,7 @@ std::vector<SlotInfo> ArmyManager::toSlotInfo(std::vector<creInfo> army) const
 
 uint64_t ArmyManager::howManyReinforcementsCanGet(const CGHeroInstance * hero, const CCreatureSet * source) const
 {
-	return howManyReinforcementsCanGet(hero, hero, source);
+	return howManyReinforcementsCanGet(hero, hero, source, ai->cb->getTile(hero->visitablePos())->getTerrainID());
 }
 
 std::vector<SlotInfo> ArmyManager::getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const
@@ -111,17 +113,59 @@ std::vector<SlotInfo> ArmyManager::getSortedSlots(const CCreatureSet * target, c
 	return resultingArmy;
 }
 
-std::vector<SlotInfo>::iterator ArmyManager::getWeakestCreature(std::vector<SlotInfo> & army) const
+std::vector<SlotInfo>::iterator ArmyManager::getBestUnitForScout(std::vector<SlotInfo> & army, const TerrainId & armyTerrain) const
 {
-	auto weakest = boost::min_element(army, [](const SlotInfo & left, const SlotInfo & right) -> bool
+	uint64_t totalPower = 0;
+
+	for (const auto & unit : army)
+		totalPower += unit.power;
+
+	// TODO: replace with EGameSettings::HEROES_MOVEMENT_COST_BASE in 1.7
+	bool terrainHasPenalty = armyTerrain.hasValue() && armyTerrain.toEntity(VLC)->moveCost != GameConstants::BASE_MOVEMENT_COST;
+
+	// arbitrary threshold - don't give scout more than specified part of total AI value of our army
+	uint64_t maxUnitValue = totalPower / 100;
+
+	const auto & movementPointsLimits = cb->getSettings().getVector(EGameSettings::HEROES_MOVEMENT_POINTS_LAND);
+
+	auto fastest = boost::min_element(army, [&](const SlotInfo & left, const SlotInfo & right) -> bool
 	{
-		if(left.creature->getLevel() != right.creature->getLevel())
-			return left.creature->getLevel() < right.creature->getLevel();
-		
-		return left.creature->getMovementRange() > right.creature->getMovementRange();
+		uint64_t leftUnitPower = left.power / left.count;
+		uint64_t rightUnitPower = left.power / left.count;
+		bool leftUnitIsWeak = leftUnitPower < maxUnitValue || left.creature->getLevel() < 4;
+		bool rightUnitIsWeak = rightUnitPower < maxUnitValue || right.creature->getLevel() < 4;
+
+		if (leftUnitIsWeak != rightUnitIsWeak)
+			return leftUnitIsWeak;
+
+		if (terrainHasPenalty)
+		{
+			auto leftNativeTerrain = left.creature->getFactionID().toFaction()->nativeTerrain;
+			auto rightNativeTerrain = right.creature->getFactionID().toFaction()->nativeTerrain;
+
+			if (leftNativeTerrain != rightNativeTerrain)
+			{
+				if (leftNativeTerrain == armyTerrain)
+					return true;
+
+				if (rightNativeTerrain == armyTerrain)
+					return false;
+			}
+		}
+
+		int leftEffectiveMovement = std::min<int>(movementPointsLimits.size() - 1, left.creature->getMovementRange());
+		int rightEffectiveMovement = std::min<int>(movementPointsLimits.size() - 1, right.creature->getMovementRange());
+
+		int leftMovementPointsLimit = movementPointsLimits[leftEffectiveMovement];
+		int rightMovementPointsLimit = movementPointsLimits[rightEffectiveMovement];
+
+		if (leftMovementPointsLimit != rightMovementPointsLimit)
+			return leftMovementPointsLimit > rightMovementPointsLimit;
+
+		return leftUnitPower < rightUnitPower;
 	});
 
-	return weakest;
+	return fastest;
 }
 
 class TemporaryArmy : public CArmedInstance
@@ -134,7 +178,7 @@ public:
 	}
 };
 
-std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const
+std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source, const TerrainId & armyTerrain) const
 {
 	auto sortedSlots = getSortedSlots(target, source);
 
@@ -218,7 +262,7 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
 		&& allowedFactions.size() == alignmentMap.size()
 		&& source->needsLastStack())
 	{
-		auto weakest = getWeakestCreature(resultingArmy);
+		auto weakest = getBestUnitForScout(resultingArmy, armyTerrain);
 
 		if(weakest->count == 1) 
 		{
@@ -398,14 +442,14 @@ std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(
 	return creaturesInDwellings;
 }
 
-ui64 ArmyManager::howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const
+ui64 ArmyManager::howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source, const TerrainId & armyTerrain) const
 {
 	if(source->stacksCount() == 0)
 	{
 		return 0;
 	}
 
-	auto bestArmy = getBestArmy(armyCarrier, target, source);
+	auto bestArmy = getBestArmy(armyCarrier, target, source, armyTerrain);
 	uint64_t newArmy = 0;
 	uint64_t oldArmy = target->getArmyStrength();
 

+ 7 - 6
AI/Nullkiller/Analyzers/ArmyManager.h

@@ -53,10 +53,11 @@ public:
 	virtual ui64 howManyReinforcementsCanGet(
 		const IBonusBearer * armyCarrier,
 		const CCreatureSet * target,
-		const CCreatureSet * source) const = 0;
+		const CCreatureSet * source,
+		const TerrainId & armyTerrain) const = 0;
 
-	virtual std::vector<SlotInfo> getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const = 0;
-	virtual std::vector<SlotInfo>::iterator getWeakestCreature(std::vector<SlotInfo> & army) const = 0;
+	virtual std::vector<SlotInfo> getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source, const TerrainId & armyTerrain) const = 0;
+	virtual std::vector<SlotInfo>::iterator getBestUnitForScout(std::vector<SlotInfo> & army, const TerrainId & armyTerrain) const = 0;
 	virtual std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const = 0;
 	virtual std::vector<SlotInfo> toSlotInfo(std::vector<creInfo> creatures) const = 0;
 
@@ -97,9 +98,9 @@ public:
 		uint8_t turn = 0) const override;
 
 	ui64 howManyReinforcementsCanGet(const CGHeroInstance * hero, const CCreatureSet * source) const override;
-	ui64 howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const override;
-	std::vector<SlotInfo> getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const override;
-	std::vector<SlotInfo>::iterator getWeakestCreature(std::vector<SlotInfo> & army) const override;
+	ui64 howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source, const TerrainId & armyTerrain) const override;
+	std::vector<SlotInfo> getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source, const TerrainId & armyTerrain) const override;
+	std::vector<SlotInfo>::iterator getBestUnitForScout(std::vector<SlotInfo> & army, const TerrainId & armyTerrain) const override;
 	std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const override;
 	std::vector<SlotInfo> toSlotInfo(std::vector<creInfo> creatures) const override;
 

+ 2 - 1
AI/Nullkiller/Behaviors/BuyArmyBehavior.cpp

@@ -57,7 +57,8 @@ Goals::TGoalVec BuyArmyBehavior::decompose(const Nullkiller * ai) const
 				auto reinforcement = ai->armyManager->howManyReinforcementsCanGet(
 					targetHero,
 					targetHero,
-					&*townArmyAvailableToBuy);
+					&*townArmyAvailableToBuy,
+					TerrainId::NONE);
 
 				if(reinforcement)
 					vstd::amin(reinforcement, ai->armyManager->howManyReinforcementsCanBuy(town->getUpperArmy(), town));

+ 2 - 1
AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp

@@ -300,7 +300,8 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * ai, const CGT
 				ai->armyManager->getBestArmy(
 					path.targetHero,
 					path.heroArmy,
-					upgrader->getUpperArmy()));
+					upgrader->getUpperArmy(),
+					TerrainId::NONE));
 
 			armyToGetOrBuy.upgradeValue -= path.heroArmy->getArmyStrength();
 

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

@@ -149,7 +149,7 @@ Goals::TGoalVec StartupBehavior::decompose(const Nullkiller * ai) const
 	{
 		if(!startupTown->visitingHero)
 		{
-			if(ai->armyManager->howManyReinforcementsCanGet(startupTown->getUpperArmy(), startupTown->getUpperArmy(), closestHero) > 200)
+			if(ai->armyManager->howManyReinforcementsCanGet(startupTown->getUpperArmy(), startupTown->getUpperArmy(), closestHero, TerrainId::NONE) > 200)
 			{
 				auto paths = ai->pathfinder->getPathInfo(startupTown->visitablePos());
 

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

@@ -13,6 +13,7 @@
 #include "../Engine/Nullkiller.h"
 #include "../../../CCallback.h"
 #include "../../../lib/mapObjects/MapObjects.h"
+#include "../../../lib/mapping/CMapDefines.h"
 #include "../../../lib/pathfinder/TurnInfo.h"
 #include "Actions/BuyArmyAction.h"
 
@@ -394,7 +395,7 @@ HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
 HeroExchangeArmy * HeroExchangeMap::pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const
 {
 	auto * target = new HeroExchangeArmy();
-	auto bestArmy = ai->armyManager->getBestArmy(actor->hero, army1, army2);
+	auto bestArmy = ai->armyManager->getBestArmy(actor->hero, army1, army2, ai->cb->getTile(actor->hero->visitablePos())->getTerrainID());
 
 	for(auto & slotInfo : bestArmy)
 	{