Преглед изворни кода

Nullkiller: stabilisation, improve gather army

Andrii Danylchenko пре 4 година
родитељ
комит
df78e3243b

+ 160 - 71
AI/Nullkiller/Behaviors/GatherArmyBehavior.cpp

@@ -40,122 +40,211 @@ Goals::TGoalVec GatherArmyBehavior::getTasks()
 	{
 		return tasks;
 	}
-	
+
 	for(const CGHeroInstance * hero : heroes)
 	{
-		if(ai->ah->getHeroRole(hero) != HeroRole::MAIN
-			|| hero->getArmyStrength() < 300)
+		if(ai->ah->getHeroRole(hero) == HeroRole::MAIN
+			&& hero->getArmyStrength() >= 300)
 		{
-#ifdef AI_TRACE_LEVEL >= 1
-			logAi->trace("Skipping hero %s", hero->name);
-#endif
-			continue;
+			vstd::concatenate(tasks, deliverArmyToHero(hero));
 		}
+	}
 
-		const int3 pos = hero->visitablePos();
+	auto towns = cb->getTownsInfo();
+
+	for(const CGTownInstance * town : towns)
+	{
+		vstd::concatenate(tasks, upgradeArmy(town));
+	}
+}
+
+Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * hero) const
+{
+	Goals::TGoalVec tasks;
+	const int3 pos = hero->visitablePos();
 
 #ifdef AI_TRACE_LEVEL >= 1
-		logAi->trace("Checking ways to gaher army for hero %s, %s", hero->getObjectName(), pos.toString());
+	logAi->trace("Checking ways to gaher army for hero %s, %s", hero->getObjectName(), pos.toString());
 #endif
-		if(ai->nullkiller->isHeroLocked(hero))
-		{
+	if(ai->nullkiller->isHeroLocked(hero))
+	{
 #ifdef AI_TRACE_LEVEL >= 1
-			logAi->trace("Skipping locked hero %s, %s", hero->getObjectName(), pos.toString());
+		logAi->trace("Skipping locked hero %s, %s", hero->getObjectName(), pos.toString());
 #endif
-			continue;
-		}
+		return tasks;
+	}
 
-		auto paths = ai->ah->getPathsToTile(pos);
-		std::vector<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj;
+	auto paths = ai->ah->getPathsToTile(pos);
+	std::vector<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj;
 
 #ifdef AI_TRACE_LEVEL >= 1
-		logAi->trace("Found %d paths", paths.size());
+	logAi->trace("Found %d paths", paths.size());
+#endif
+
+	for(const AIPath & path : paths)
+	{
+#ifdef AI_TRACE_LEVEL >= 2
+		logAi->trace("Path found %s", path.toString());
 #endif
+		
+		if(path.containsHero(hero)) continue;
 
-		for(auto & path : paths)
+		if(path.getFirstBlockedAction())
 		{
 #ifdef AI_TRACE_LEVEL >= 2
-			logAi->trace("Path found %s", path.toString());
+			// TODO: decomposition?
+			logAi->trace("Ignore path. Action is blocked.");
 #endif
-			bool skip = path.targetHero == hero;
+			continue;
+		}
 
-			for(auto node : path.nodes)
-			{
-				skip |= (node.targetHero == hero);
-			}
+		if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
+		{
+#ifdef AI_TRACE_LEVEL >= 2
+			logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength());
+#endif
+			continue;
+		}
 
-			if(skip) continue;
+		float armyValue = (float)ai->ah->howManyReinforcementsCanGet(hero, path.heroArmy) / hero->getArmyStrength();
+
+		// avoid transferring very small amount of army
+		if(armyValue < 0.1f)
+			continue;
+
+		// avoid trying to move bigger army to the weaker one.
+		if(armyValue > 0.5f)
+			continue;
+
+		auto danger = path.getTotalDanger();
+
+		auto isSafe = isSafeToVisit(hero, path.heroArmy, danger);
 
 #ifdef AI_TRACE_LEVEL >= 2
-			logAi->trace("Path found %s", path.toString());
+		logAi->trace(
+			"It is %s to visit %s by %s with army %lld, danger %lld and army loss %lld",
+			isSafe ? "safe" : "not safe",
+			hero->name,
+			path.targetHero->name,
+			path.getHeroStrength(),
+			danger,
+			path.getTotalArmyLoss());
+#endif
+
+		if(isSafe)
+		{
+			auto newWay = std::make_shared<ExecuteHeroChain>(path, hero);
+
+			newWay->evaluationContext.strategicalValue = armyValue;
+			waysToVisitObj.push_back(newWay);
+		}
+	}
+
+	if(waysToVisitObj.empty())
+		return tasks;
+
+	for(auto way : waysToVisitObj)
+	{
+		if(ai->nullkiller->arePathHeroesLocked(way->getPath()))
+			continue;
+
+		if(ai->nullkiller->getHeroLockedReason(way->hero.get()) == HeroLockedReason::STARTUP)
+			continue;
+
+		way->evaluationContext.closestWayRatio = 1;
+
+		tasks.push_back(sptr(*way));
+	}
+
+	return tasks;
+}
+
+Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGObjectInstance * upgrader) const
+{
+	Goals::TGoalVec tasks;
+	const int3 pos = upgrader->visitablePos();
+	TResources availableResources = cb->getResourceAmount();
+
+#ifdef AI_TRACE_LEVEL >= 1
+	logAi->trace("Checking ways to upgrade army in town %s, %s", upgrader->getObjectName(), pos.toString());
+#endif
+	
+	auto paths = ai->ah->getPathsToTile(pos);
+	std::vector<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj;
+
+#ifdef AI_TRACE_LEVEL >= 1
+	logAi->trace("Found %d paths", paths.size());
 #endif
 
-			if(path.getFirstBlockedAction())
-			{
+	for(const AIPath & path : paths)
+	{
 #ifdef AI_TRACE_LEVEL >= 2
-				// TODO: decomposition?
-				logAi->trace("Ignore path. Action is blocked.");
+		logAi->trace("Path found %s", path.toString());
 #endif
-				continue;
-			}
 
-			if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
-			{
+		if(path.getFirstBlockedAction())
+		{
 #ifdef AI_TRACE_LEVEL >= 2
-				logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength());
+			// TODO: decomposition?
+			logAi->trace("Ignore path. Action is blocked.");
 #endif
-				continue;
-			}
+			continue;
+		}
 
-			float armyValue = (float)ai->ah->howManyReinforcementsCanGet(hero, path.heroArmy) / hero->getArmyStrength();
+		if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
+		{
+#ifdef AI_TRACE_LEVEL >= 2
+			logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength());
+#endif
+			continue;
+		}
 
-			// avoid transferring very small amount of army
-			if(armyValue < 0.1f)
-				continue;
+		auto upgrade = ai->ah->calculateCreateresUpgrade(path.heroArmy, upgrader, availableResources);
+		auto armyValue = (float)upgrade.upgradeValue / path.getHeroStrength();
 
-			// avoid trying to move bigger army to the weaker one.
-			if(armyValue > 0.5f)
-				continue;
+		if(armyValue < 0.1f || upgrade.upgradeValue < 300) // avoid small upgrades
+			continue;
 
-			auto danger = path.getTotalDanger();
+		auto danger = path.getTotalDanger();
 
-			auto isSafe = isSafeToVisit(hero, path.heroArmy, danger);
+		auto isSafe = isSafeToVisit(path.targetHero, path.heroArmy, danger);
 
 #ifdef AI_TRACE_LEVEL >= 2
-			logAi->trace(
-				"It is %s to visit %s by %s with army %lld, danger %lld and army loss %lld",
-				isSafe ? "safe" : "not safe",
-				hero->name,
-				path.targetHero->name,
-				path.getHeroStrength(),
-				danger,
-				path.getTotalArmyLoss());
+		logAi->trace(
+			"It is %s to visit %s by %s with army %lld, danger %lld and army loss %lld",
+			isSafe ? "safe" : "not safe",
+			upgrader->getObjectName(),
+			path.targetHero->name,
+			path.getHeroStrength(),
+			danger,
+			path.getTotalArmyLoss());
 #endif
 
-			if(isSafe)
-			{
-				auto newWay = std::make_shared<ExecuteHeroChain>(path, hero);
+		if(isSafe)
+		{
+			auto newWay = std::make_shared<ExecuteHeroChain>(path, upgrader);
+
+			newWay->evaluationContext.strategicalValue = armyValue;
+			newWay->evaluationContext.goldCost = upgrade.upgradeCost[Res::GOLD];
 
-				newWay->evaluationContext.strategicalValue = armyValue;
-				waysToVisitObj.push_back(newWay);
-			}
+			waysToVisitObj.push_back(newWay);
 		}
+	}
 
-		if(waysToVisitObj.empty())
-			continue;
+	if(waysToVisitObj.empty())
+		return tasks;
 
-		for(auto way : waysToVisitObj)
-		{
-			if(ai->nullkiller->arePathHeroesLocked(way->getPath()))
-				continue;
+	for(auto way : waysToVisitObj)
+	{
+		if(ai->nullkiller->arePathHeroesLocked(way->getPath()))
+			continue;
 
-			if(ai->nullkiller->getHeroLockedReason(way->hero.get()) == HeroLockedReason::STARTUP)
-				continue;
+		if(ai->nullkiller->getHeroLockedReason(way->hero.get()) == HeroLockedReason::STARTUP)
+			continue;
 
-			way->evaluationContext.closestWayRatio = 1;
+		way->evaluationContext.closestWayRatio = 1;
 
-			tasks.push_back(sptr(*way));
-		}
+		tasks.push_back(sptr(*way));
 	}
 
 	return tasks;

+ 4 - 0
AI/Nullkiller/Behaviors/GatherArmyBehavior.h

@@ -28,5 +28,9 @@ public:
 
 	virtual Goals::TGoalVec getTasks() override;
 	virtual std::string toString() const override;
+
+private:
+	Goals::TGoalVec deliverArmyToHero(const CGHeroInstance * hero) const;
+	Goals::TGoalVec upgradeArmy(const CGObjectInstance * upgrader) const;
 };
 

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

@@ -300,7 +300,7 @@ float getStrategicalValue(const CGObjectInstance * target)
 	case Obj::TOWN:
 		return dynamic_cast<const CGTownInstance *>(target)->hasFort()
 			? (target->tempOwner == PlayerColor::NEUTRAL ? 0.8f : 1.0f)
-			: 0.4f;
+			: 0.5f;
 
 	case Obj::HERO:
 		return cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES

+ 1 - 10
AI/Nullkiller/Goals/ExecuteHeroChain.cpp

@@ -96,16 +96,7 @@ void ExecuteHeroChain::accept(VCAI * ai)
 					{
 						auto specialGoal = node.specialAction->whatToDo(hero);
 
-						try
-						{
-							specialGoal->accept(ai);
-						}
-						catch(cannotFulfillGoalException e)
-						{
-							logAi->warn("Can not complete %s because of an exception: %s", specialGoal->name(), e.what());
-
-							throw;
-						}
+						specialGoal->accept(ai);
 					}
 					else
 					{

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

@@ -410,6 +410,12 @@ void AINodeStorage::calculateHeroChain(
 		if((node->actor->chainMask & chainMask) == 0 && (srcNode->actor->chainMask & chainMask) == 0)
 			continue;
 
+		if(node->action == CGPathNode::ENodeAction::BATTLE
+			|| node->action == CGPathNode::ENodeAction::TELEPORT_BATTLE)
+		{
+			continue;
+		}
+
 		if(node->turns > heroChainTurn 
 			|| (node->action == CGPathNode::ENodeAction::UNKNOWN && node->actor->hero)
 			|| (node->actor->chainMask & srcNode->actor->chainMask) != 0)
@@ -635,7 +641,9 @@ void AINodeStorage::setTownsAndDwellings(
 	{
 		uint64_t mask = 1 << actors.size();
 
-		if(!town->garrisonHero || ai->nullkiller->getHeroLockedReason(town->garrisonHero) != HeroLockedReason::DEFENCE)
+		// TODO: investigate logix of second condition || ai->nullkiller->getHeroLockedReason(town->garrisonHero) != HeroLockedReason::DEFENCE
+		// check defence imrove
+		if(!town->garrisonHero)
 		{
 			actors.push_back(std::make_shared<TownGarrisonActor>(town, mask));
 		}
@@ -1119,6 +1127,20 @@ uint64_t AIPath::getTotalDanger() const
 	return danger;
 }
 
+bool AIPath::containsHero(const CGHeroInstance * hero) const
+{
+	if(targetHero == hero)
+		return true;
+
+	for(auto node : nodes)
+	{
+		if(node.targetHero == hero)
+			return true;
+	}
+
+	return false;
+}
+
 uint64_t AIPath::getTotalArmyLoss() const
 {
 	return armyLoss + targetObjectArmyLoss;

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

@@ -82,6 +82,8 @@ struct AIPath
 	std::string toString() const;
 
 	std::shared_ptr<const ISpecialAction> AIPath::getFirstBlockedAction() const;
+
+	bool containsHero(const CGHeroInstance * hero) const;
 };
 
 struct ExchangeCandidate : public AIPathNode

+ 3 - 0
AI/Nullkiller/VCAI.cpp

@@ -1840,7 +1840,10 @@ bool VCAI::isAccessibleForHero(const int3 & pos, HeroPtr h, bool includeAllies)
 bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 {
 	if(h->inTownGarrison && h->visitedTown)
+	{
 		cb->swapGarrisonHero(h->visitedTown);
+		moveCreaturesToHero(h->visitedTown);
+	}
 
 	//TODO: consider if blockVisit objects change something in our checks: AIUtility::isBlockVisitObj()