浏览代码

Nullkiller AI: basic hill fort support and hero chain reworked to start from stronger army

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

+ 8 - 0
AI/Nullkiller/AIhelper.cpp

@@ -213,6 +213,14 @@ std::vector<creInfo> AIhelper::getArmyAvailableToBuy(const CCreatureSet * hero,
 	return armyManager->getArmyAvailableToBuy(hero, dwelling);
 }
 
+ArmyUpgradeInfo AIhelper::calculateCreateresUpgrade(
+	const CCreatureSet * army,
+	const CGObjectInstance * upgrader,
+	const TResources & availableResources) const
+{
+	return armyManager->calculateCreateresUpgrade(army, upgrader, availableResources);
+}
+
 int AIhelper::selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const
 {
 	return heroManager->selectBestSkill(hero, skills);

+ 4 - 0
AI/Nullkiller/AIhelper.h

@@ -82,6 +82,10 @@ public:
 	std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const override;
 	uint64_t evaluateStackPower(const CCreature * creature, int count) const override;
 	SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const override;
+	ArmyUpgradeInfo calculateCreateresUpgrade(
+		const CCreatureSet * army,
+		const CGObjectInstance * upgrader,
+		const TResources & availableResources) const override;
 
 	const std::map<HeroPtr, HeroRole> & getHeroRoles() const override;
 	HeroRole getHeroRole(const HeroPtr & hero) const override;

+ 140 - 0
AI/Nullkiller/ArmyManager.cpp

@@ -207,4 +207,144 @@ void ArmyManager::update()
 		army.second.creature = army.first.toCreature();
 		army.second.power = evaluateStackPower(army.second.creature, army.second.count);
 	}
+}
+
+struct UpgradeInfo
+{
+	const CCreature * initialCreature;
+	const CCreature * upgradedCreature;
+	TResources cost;
+	int count;
+	uint64_t upgradeValue;
+
+	UpgradeInfo(CreatureID initial, CreatureID upgraded, int count)
+		:initialCreature(initial.toCreature()), upgradedCreature(upgraded.toCreature()), count(count)
+	{
+		cost = (upgradedCreature->cost - initialCreature->cost) * count;
+		upgradeValue = (upgradedCreature->AIValue - initialCreature->AIValue) * count;
+	}
+};
+
+std::vector<SlotInfo> ArmyManager::convertToSlots(const CCreatureSet * army) const
+{
+	std::vector<SlotInfo> result;
+
+	for(auto slot : army->Slots())
+	{
+		SlotInfo slotInfo;
+
+		slotInfo.creature = slot.second->getCreatureID().toCreature();
+		slotInfo.count = slot.second->count;
+		slotInfo.power = evaluateStackPower(slotInfo.creature, slotInfo.count);
+
+		result.push_back(slotInfo);
+	}
+
+	return result;
+}
+
+std::vector<UpgradeInfo> ArmyManager::getHillFortUpgrades(const CCreatureSet * army) const
+{
+	std::vector<UpgradeInfo> upgrades;
+
+	for(auto creature : army->Slots())
+	{
+		CreatureID initial = creature.second->getCreatureID();
+		auto possibleUpgrades = initial.toCreature()->upgrades;
+
+		if(possibleUpgrades.empty())
+			continue;
+
+		CreatureID strongestUpgrade = *vstd::minElementByFun(possibleUpgrades, [](CreatureID cre) -> uint64_t
+		{
+			return cre.toCreature()->AIValue;
+		});
+
+		UpgradeInfo upgrade = UpgradeInfo(initial, strongestUpgrade, creature.second->count);
+
+		if(initial.toCreature()->level == 1)
+			upgrade.cost = TResources();
+
+		upgrades.push_back(upgrade);
+	}
+
+	return upgrades;
+}
+
+std::vector<UpgradeInfo> ArmyManager::getDwellingUpgrades(const CCreatureSet * army, const CGDwelling * dwelling) const
+{
+	std::vector<UpgradeInfo> upgrades;
+
+	return upgrades;
+}
+
+std::vector<UpgradeInfo> ArmyManager::getPossibleUpgrades(const CCreatureSet * army, const CGObjectInstance * upgrader) const
+{
+	std::vector<UpgradeInfo> upgrades;
+
+	if(upgrader->ID == Obj::HILL_FORT)
+	{
+		upgrades = getHillFortUpgrades(army);
+	}
+	else
+	{
+		auto dwelling = dynamic_cast<const CGDwelling *>(upgrader);
+
+		if(dwelling)
+		{
+			upgrades = getDwellingUpgrades(army, dwelling);
+		}
+	}
+
+	return upgrades;
+}
+
+ArmyUpgradeInfo ArmyManager::calculateCreateresUpgrade(
+	const CCreatureSet * army,
+	const CGObjectInstance * upgrader,
+	const TResources & availableResources) const
+{
+	std::vector<UpgradeInfo> upgrades = getPossibleUpgrades(army, upgrader);
+
+	vstd::erase_if(upgrades, [&](const UpgradeInfo & u) -> bool
+	{
+		return !availableResources.canAfford(u.cost);
+	});
+
+	if(upgrades.empty())
+		return ArmyUpgradeInfo();
+
+	std::sort(upgrades.begin(), upgrades.end(), [](const UpgradeInfo & u1, const UpgradeInfo & u2) -> bool
+	{
+		return u1.upgradeValue > u2.upgradeValue;
+	});
+
+	TResources resourcesLeft = availableResources;
+	ArmyUpgradeInfo result;
+	
+	result.resultingArmy = convertToSlots(army);
+
+	for(auto upgrade : upgrades)
+	{
+		if(resourcesLeft.canAfford(upgrade.cost))
+		{
+			SlotInfo upgradedArmy;
+
+			upgradedArmy.creature = upgrade.upgradedCreature;
+			upgradedArmy.count = upgrade.count;
+			upgradedArmy.power = evaluateStackPower(upgradedArmy.creature, upgradedArmy.count);
+
+			auto slotToReplace = std::find_if(result.resultingArmy.begin(), result.resultingArmy.end(), [&](const SlotInfo & slot) -> bool {
+				return slot.count == upgradedArmy.count && slot.creature == upgrade.initialCreature;
+			});
+
+			resourcesLeft -= upgrade.cost;
+			result.upgradeCost += upgrade.cost;
+			result.upgradeValue += upgrade.upgradeValue;
+
+			*slotToReplace = upgradedArmy;
+		}
+	}
+
+	return result;
 }

+ 28 - 0
AI/Nullkiller/ArmyManager.h

@@ -25,6 +25,18 @@ struct SlotInfo
 	uint64_t power;
 };
 
+struct ArmyUpgradeInfo
+{
+	std::vector<SlotInfo> resultingArmy;
+	uint64_t upgradeValue;
+	TResources upgradeCost;
+
+	ArmyUpgradeInfo()
+		: resultingArmy(), upgradeValue(0), upgradeCost()
+	{
+	}
+};
+
 class DLL_EXPORT IArmyManager //: public: IAbstractManager
 {
 public:
@@ -40,8 +52,14 @@ public:
 	virtual std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const = 0;
 	virtual uint64_t evaluateStackPower(const CCreature * creature, int count) const = 0;
 	virtual SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const = 0;
+	virtual ArmyUpgradeInfo calculateCreateresUpgrade(
+		const CCreatureSet * army,
+		const CGObjectInstance * upgrader,
+		const TResources & availableResources) const = 0;
 };
 
+struct UpgradeInfo;
+
 class DLL_EXPORT ArmyManager : public IArmyManager
 {
 private:
@@ -63,4 +81,14 @@ public:
 	std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const override;
 	uint64_t evaluateStackPower(const CCreature * creature, int count) const override;
 	SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const override;
+	ArmyUpgradeInfo calculateCreateresUpgrade(
+		const CCreatureSet * army, 
+		const CGObjectInstance * upgrader,
+		const TResources & availableResources) const override;
+
+private:
+	std::vector<SlotInfo> convertToSlots(const CCreatureSet * army) const;
+	std::vector<UpgradeInfo> getPossibleUpgrades(const CCreatureSet * army, const CGObjectInstance * upgrader) const;
+	std::vector<UpgradeInfo> getHillFortUpgrades(const CCreatureSet * army) const;
+	std::vector<UpgradeInfo> getDwellingUpgrades(const CCreatureSet * army, const CGDwelling * dwelling) const;
 };

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

@@ -172,8 +172,13 @@ Goals::TGoalVec StartupBehavior::getTasks()
 	{
 		for(const CGTownInstance * town : towns)
 		{
-			if(town->garrisonHero && town->garrisonHero->movement && ai->nullkiller->getHeroLockedReason(town->garrisonHero) != HeroLockedReason::DEFENCE)
+			if(town->garrisonHero
+				&& town->garrisonHero->movement
+				&& !town->visitingHero
+				&& ai->nullkiller->getHeroLockedReason(town->garrisonHero) != HeroLockedReason::DEFENCE)
+			{
 				tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(town, nullptr).setpriority(0.0001f)));
+			}
 		}
 	}
 

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

@@ -22,6 +22,7 @@
 #include "../VCAI.h"
 #include "../AIhelper.h"
 #include "../Engine/Nullkiller.h"
+#include "../Goals/ExecuteHeroChain.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
@@ -153,7 +154,7 @@ uint64_t evaluateArtifactArmyValue(CArtifactInstance * art)
 	return statsValue > classValue ? statsValue : classValue;
 }
 
-uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero, bool checkGold)
+uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army, bool checkGold)
 {
 	const float enemyArmyEliminationRewardRatio = 0.5f;
 
@@ -164,6 +165,8 @@ uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * h
 	{
 	case Obj::TOWN:
 		return target->tempOwner == PlayerColor::NEUTRAL ? 1000 : 10000;
+	case Obj::HILL_FORT:
+		return ai->ah->calculateCreateresUpgrade(army, target, cb->getResourceAmount()).upgradeValue;
 	case Obj::CREATURE_BANK:
 		return getCreatureBankArmyReward(target, hero);
 	case Obj::CREATURE_GENERATOR1:
@@ -377,15 +380,11 @@ class ExecuteHeroChainEvaluationContextBuilder : public IEvaluationContextBuilde
 public:
 	virtual Goals::EvaluationContext buildEvaluationContext(Goals::TSubgoal task) const override
 	{
+		Goals::ExecuteHeroChain & chain = dynamic_cast<Goals::ExecuteHeroChain &>(*task);
 		auto evaluationContext = task->evaluationContext;
 
-		int objId = task->objid;
-
-		if(task->parent)
-			objId = task->parent->objid;
-
 		auto heroPtr = task->hero;
-		const CGObjectInstance * target = cb->getObj((ObjectInstanceID)objId, false);
+		const CGObjectInstance * target = cb->getObj((ObjectInstanceID)task->objid, false);
 		auto day = cb->getDate(Date::DAY);
 		auto hero = heroPtr.get();
 		bool checkGold = evaluationContext.danger == 0;
@@ -393,7 +392,7 @@ public:
 		evaluationContext.armyLossPersentage = task->evaluationContext.armyLoss / (double)task->evaluationContext.heroStrength;
 		evaluationContext.heroRole = ai->ah->getHeroRole(heroPtr);
 		evaluationContext.goldReward = getGoldReward(target, hero);
-		evaluationContext.armyReward = getArmyReward(target, hero, checkGold);
+		evaluationContext.armyReward = getArmyReward(target, hero, chain.getPath().heroArmy, checkGold);
 		evaluationContext.skillReward = getSkillReward(target, hero, evaluationContext.heroRole);
 		evaluationContext.strategicalValue = getStrategicalValue(target);
 

+ 5 - 20
AI/Nullkiller/FuzzyHelper.cpp

@@ -281,32 +281,17 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj, const VCAI * ai)
 
 	switch(obj->ID)
 	{
-	case Obj::HERO:
-	{
-		InfoAboutHero iah;
-		cb->getHeroInfo(obj, iah);
-		return iah.army.getStrength();
-	}
 	case Obj::TOWN:
-	case Obj::GARRISON:
-	case Obj::GARRISON2:
 	{
-		InfoAboutTown iat;
-		cb->getTownInfo(obj, iat);
-		return iat.army.getStrength();
+		const CGTownInstance * cre = dynamic_cast<const CGTownInstance *>(obj);
+		return cre->getUpperArmy()->getArmyStrength();
 	}
 	case Obj::MONSTER:
-	{
-		//TODO!!!!!!!!
-		const CGCreature * cre = dynamic_cast<const CGCreature *>(obj);
-		return cre->getArmyStrength();
-	}
+	case Obj::HERO:
+	case Obj::GARRISON:
+	case Obj::GARRISON2:
 	case Obj::CREATURE_GENERATOR1:
 	case Obj::CREATURE_GENERATOR4:
-	{
-		const CGDwelling * d = dynamic_cast<const CGDwelling *>(obj);
-		return d->getArmyStrength();
-	}
 	case Obj::MINE:
 	case Obj::ABANDONED_MINE:
 	{

+ 54 - 1
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -279,7 +279,8 @@ bool AINodeStorage::calculateHeroChainFinal()
 			{
 				if(node.turns > heroChainTurn
 					&& node.action != CGPathNode::ENodeAction::UNKNOWN
-					&& node.actor->actorExchangeCount > 1)
+					&& node.actor->actorExchangeCount > 1
+					&& !hasBetterChain(&node, &node, chains))
 				{
 					heroChain.push_back(&node);
 				}
@@ -332,6 +333,55 @@ bool AINodeStorage::calculateHeroChain()
 	return heroChain.size();
 }
 
+bool AINodeStorage::selectFirstActor()
+{
+	if(!actors.size())
+		return false;
+
+	auto strongest = *vstd::maxElementByFun(actors, [](std::shared_ptr<ChainActor> actor) -> uint64_t
+	{
+		return actor->armyValue;
+	});
+
+	chainMask = strongest->chainMask;
+
+	return true;
+}
+
+bool AINodeStorage::selectNextActor()
+{
+	auto currentActor = std::find_if(actors.begin(), actors.end(), [&](std::shared_ptr<ChainActor> actor)-> bool
+	{
+		return actor->chainMask == chainMask;
+	});
+
+	auto nextActor = actors.end();
+
+	for(auto actor = actors.begin(); actor != actors.end(); actor++)
+	{
+		if(actor->get()->armyValue > currentActor->get()->armyValue
+			|| actor->get()->armyValue == currentActor->get()->armyValue && actor <= currentActor)
+		{
+			continue;
+		}
+
+		if(nextActor == actors.end()
+			|| actor->get()->armyValue > nextActor->get()->armyValue)
+		{
+			nextActor = actor;
+		}
+	}
+
+	if(nextActor != actors.end())
+	{
+		chainMask = nextActor->get()->chainMask;
+
+		return true;
+	}
+
+	return false;
+}
+
 void AINodeStorage::cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const
 {
 	vstd::erase_if(result, [&](const ExchangeCandidate & chainInfo) -> bool
@@ -354,6 +404,9 @@ void AINodeStorage::calculateHeroChain(
 		if(node == srcNode || !node->actor)
 			continue;
 
+		if((node->actor->chainMask & chainMask) == 0 && (srcNode->actor->chainMask & chainMask) == 0)
+			continue;
+
 		if(node->turns > heroChainTurn 
 			|| (node->action == CGPathNode::ENodeAction::UNKNOWN && node->actor->hero)
 			|| (node->actor->chainMask & srcNode->actor->chainMask) != 0)

+ 3 - 0
AI/Nullkiller/Pathfinding/AINodeStorage.h

@@ -112,6 +112,7 @@ private:
 	std::vector<std::shared_ptr<ChainActor>> actors;
 	std::vector<CGPathNode *> heroChain;
 	EHeroChainPass heroChainPass; // true if we need to calculate hero chain
+	uint64_t chainMask;
 	int heroChainTurn;
 	int heroChainMaxTurns;
 	PlayerColor playerID;
@@ -126,6 +127,8 @@ public:
 	void initialize(const PathfinderOptions & options, const CGameState * gs) override;
 
 	bool increaseHeroChainTurnLimit();
+	bool selectFirstActor();
+	bool selectNextActor();
 
 	virtual std::vector<CGPathNode *> getInitialNodes() override;
 

+ 16 - 9
AI/Nullkiller/Pathfinding/AIPathfinder.cpp

@@ -62,25 +62,32 @@ void AIPathfinder::updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain)
 	}
 
 	auto config = std::make_shared<AIPathfinding::AIPathfinderConfig>(cb, ai, storage);
-	bool continueCalculation = false;
+
+	logAi->trace("Recalculate paths pass %d", pass++);
+	cb->calculatePaths(config);
+
+	if(!useHeroChain)
+		return;
 
 	do
 	{
+		storage->selectFirstActor();
+
 		do
 		{
-			logAi->trace("Recalculate paths pass %d", pass++);
-			cb->calculatePaths(config);
-		} while(useHeroChain && storage->calculateHeroChain());
+			while(storage->calculateHeroChain())
+			{
+				logAi->trace("Recalculate paths pass %d", pass++);
+				cb->calculatePaths(config);
+			}
 
-		if(!useHeroChain)
-			break;
+			logAi->trace("Select next actor");
+		} while(storage->selectNextActor());
 
 		if(storage->calculateHeroChainFinal())
 		{
 			logAi->trace("Recalculate paths pass final");
 			cb->calculatePaths(config);
 		}
-
-		continueCalculation = storage->increaseHeroChainTurnLimit() && storage->calculateHeroChain();
-	} while(continueCalculation);
+	} while(storage->increaseHeroChainTurnLimit());
 }

+ 4 - 5
AI/Nullkiller/VCAI.cpp

@@ -456,11 +456,6 @@ void VCAI::showHillFortWindow(const CGObjectInstance * object, const CGHeroInsta
 {
 	LOG_TRACE(logAi);
 	NET_EVENT_HANDLER;
-
-	requestActionASAP([=]()
-	{
-		makePossibleUpgrades(visitor);
-	});
 }
 
 void VCAI::playerBonusChanged(const Bonus & bonus, bool gain)
@@ -781,6 +776,7 @@ void makePossibleUpgrades(const CArmedInstance * obj)
 			if(ui.oldID >= 0 && cb->getResourceAmount().canAfford(ui.cost[0] * s->count))
 			{
 				cb->upgradeCreature(obj, SlotID(i), ui.newID[0]);
+				logAi->debug("Upgraded %d %s to %s", s->count, ui.oldID.toCreature()->namePl, ui.newID[0].toCreature()->namePl);
 			}
 		}
 	}
@@ -1092,6 +1088,9 @@ void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
 			}
 		}
 		break;
+	case Obj::HILL_FORT:
+		makePossibleUpgrades(h.get());
+		break;
 	}
 	completeGoal(sptr(Goals::VisitObj(obj->id.getNum()).sethero(h)));
 }