mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-16 10:19:47 +02:00
638c1350b8
No longer excluding paths for exposing a hero to an enemy in the behaviors. There definitely are reasons for doing something anyways, even if threatened. The logic for that should be done in the PriorityEvaluator.
375 lines
9.0 KiB
C++
375 lines
9.0 KiB
C++
/*
|
|
* GatherArmyBehavior.cpp, part of VCMI engine
|
|
*
|
|
* Authors: listed in file AUTHORS in main folder
|
|
*
|
|
* License: GNU General Public License v2.0 or later
|
|
* Full text of license available in license.txt file, in main folder
|
|
*
|
|
*/
|
|
#include "StdInc.h"
|
|
#include "../AIGateway.h"
|
|
#include "../Engine/Nullkiller.h"
|
|
#include "../Goals/ExecuteHeroChain.h"
|
|
#include "../Goals/Composition.h"
|
|
#include "../Goals/RecruitHero.h"
|
|
#include "../Markers/HeroExchange.h"
|
|
#include "../Markers/ArmyUpgrade.h"
|
|
#include "GatherArmyBehavior.h"
|
|
#include "CaptureObjectsBehavior.h"
|
|
#include "../AIUtility.h"
|
|
#include "../Goals/ExchangeSwapTownHeroes.h"
|
|
|
|
namespace NKAI
|
|
{
|
|
|
|
using namespace Goals;
|
|
|
|
std::string GatherArmyBehavior::toString() const
|
|
{
|
|
return "Gather army";
|
|
}
|
|
|
|
Goals::TGoalVec GatherArmyBehavior::decompose(const Nullkiller * ai) const
|
|
{
|
|
Goals::TGoalVec tasks;
|
|
|
|
auto heroes = ai->cb->getHeroesInfo();
|
|
|
|
if(heroes.empty())
|
|
{
|
|
return tasks;
|
|
}
|
|
|
|
for(const CGHeroInstance * hero : heroes)
|
|
{
|
|
if(ai->heroManager->getHeroRole(hero) == HeroRole::MAIN)
|
|
{
|
|
vstd::concatenate(tasks, deliverArmyToHero(ai, hero));
|
|
}
|
|
}
|
|
|
|
auto towns = ai->cb->getTownsInfo();
|
|
|
|
for(const CGTownInstance * town : towns)
|
|
{
|
|
vstd::concatenate(tasks, upgradeArmy(ai, town));
|
|
}
|
|
|
|
return tasks;
|
|
}
|
|
|
|
Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const Nullkiller * ai, const CGHeroInstance * hero) const
|
|
{
|
|
Goals::TGoalVec tasks;
|
|
const int3 pos = hero->visitablePos();
|
|
auto targetHeroScore = ai->heroManager->evaluateHero(hero);
|
|
|
|
#if NKAI_TRACE_LEVEL >= 1
|
|
logAi->trace("Checking ways to gaher army for hero %s, %s", hero->getObjectName(), pos.toString());
|
|
#endif
|
|
|
|
auto paths = ai->pathfinder->getPathInfo(pos, ai->isObjectGraphAllowed());
|
|
|
|
#if NKAI_TRACE_LEVEL >= 1
|
|
logAi->trace("Gather army found %d paths", paths.size());
|
|
#endif
|
|
|
|
for(const AIPath & path : paths)
|
|
{
|
|
#if NKAI_TRACE_LEVEL >= 2
|
|
logAi->trace("Path found %s, %s, %lld", path.toString(), path.targetHero->getObjectName(), path.heroArmy->getArmyStrength());
|
|
#endif
|
|
|
|
if(path.containsHero(hero))
|
|
{
|
|
#if NKAI_TRACE_LEVEL >= 2
|
|
logAi->trace("Selfcontaining path. Ignore");
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
if(ai->arePathHeroesLocked(path))
|
|
{
|
|
#if NKAI_TRACE_LEVEL >= 2
|
|
logAi->trace("Ignore path because of locked hero");
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
HeroExchange heroExchange(hero, path);
|
|
|
|
uint64_t armyValue = heroExchange.getReinforcementArmyStrength(ai);
|
|
float armyRatio = (float)armyValue / hero->getArmyStrength();
|
|
|
|
// avoid transferring very small amount of army
|
|
if((armyRatio < 0.1f && armyValue < 20000) || armyValue < 500)
|
|
{
|
|
#if NKAI_TRACE_LEVEL >= 2
|
|
logAi->trace("Army value is too small.");
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
// avoid trying to move bigger army to the weaker one.
|
|
bool hasOtherMainInPath = false;
|
|
|
|
for(auto node : path.nodes)
|
|
{
|
|
if(!node.targetHero) continue;
|
|
|
|
auto heroRole = ai->heroManager->getHeroRole(node.targetHero);
|
|
|
|
if(heroRole == HeroRole::MAIN)
|
|
{
|
|
auto score = ai->heroManager->evaluateHero(node.targetHero);
|
|
|
|
if(score >= targetHeroScore)
|
|
{
|
|
hasOtherMainInPath = true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(hasOtherMainInPath)
|
|
{
|
|
#if NKAI_TRACE_LEVEL >= 2
|
|
logAi->trace("Army value is too large.");
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
auto danger = path.getTotalDanger();
|
|
auto isSafe = isSafeToVisit(hero, path.heroArmy, danger);
|
|
|
|
#if NKAI_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->getObjectName(),
|
|
path.targetHero->getObjectName(),
|
|
path.getHeroStrength(),
|
|
danger,
|
|
path.getTotalArmyLoss());
|
|
#endif
|
|
|
|
if(isSafe)
|
|
{
|
|
Composition composition;
|
|
ExecuteHeroChain exchangePath(path, hero);
|
|
|
|
exchangePath.closestWayRatio = 1;
|
|
|
|
composition.addNext(heroExchange);
|
|
|
|
if(hero->inTownGarrison && path.turn() == 0)
|
|
{
|
|
auto lockReason = ai->getHeroLockedReason(hero);
|
|
|
|
if(path.targetHero->visitedTown == hero->visitedTown)
|
|
{
|
|
composition.addNextSequence({
|
|
sptr(ExchangeSwapTownHeroes(hero->visitedTown, hero, lockReason))});
|
|
}
|
|
else
|
|
{
|
|
composition.addNextSequence({
|
|
sptr(ExchangeSwapTownHeroes(hero->visitedTown)),
|
|
sptr(exchangePath),
|
|
sptr(ExchangeSwapTownHeroes(hero->visitedTown, hero, lockReason))});
|
|
}
|
|
}
|
|
else
|
|
{
|
|
composition.addNext(exchangePath);
|
|
}
|
|
|
|
auto blockedAction = path.getFirstBlockedAction();
|
|
|
|
if(blockedAction)
|
|
{
|
|
#if NKAI_TRACE_LEVEL >= 2
|
|
logAi->trace("Action is blocked. Considering decomposition.");
|
|
#endif
|
|
auto subGoal = blockedAction->decompose(ai, path.targetHero);
|
|
|
|
if(subGoal->invalid())
|
|
{
|
|
#if NKAI_TRACE_LEVEL >= 1
|
|
logAi->trace("Path is invalid. Skipping");
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
composition.addNext(subGoal);
|
|
}
|
|
|
|
tasks.push_back(sptr(composition));
|
|
}
|
|
}
|
|
|
|
return tasks;
|
|
}
|
|
|
|
Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * ai, const CGTownInstance * upgrader) const
|
|
{
|
|
Goals::TGoalVec tasks;
|
|
const int3 pos = upgrader->visitablePos();
|
|
TResources availableResources = ai->getFreeResources();
|
|
|
|
#if NKAI_TRACE_LEVEL >= 1
|
|
logAi->trace("Checking ways to upgrade army in town %s, %s", upgrader->getObjectName(), pos.toString());
|
|
#endif
|
|
|
|
auto paths = ai->pathfinder->getPathInfo(pos, ai->isObjectGraphAllowed());
|
|
auto goals = CaptureObjectsBehavior::getVisitGoals(paths, ai);
|
|
|
|
std::vector<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj;
|
|
|
|
#if NKAI_TRACE_LEVEL >= 1
|
|
logAi->trace("Found %d paths", paths.size());
|
|
#endif
|
|
|
|
bool hasMainAround = false;
|
|
|
|
for(const AIPath & path : paths)
|
|
{
|
|
auto heroRole = ai->heroManager->getHeroRole(path.targetHero);
|
|
|
|
if(heroRole == HeroRole::MAIN && path.turn() < ai->settings->getScoutHeroTurnDistanceLimit())
|
|
hasMainAround = true;
|
|
}
|
|
|
|
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
|
|
logAi->trace("Ignore path. Town has visiting hero.");
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
if(ai->arePathHeroesLocked(path))
|
|
{
|
|
#if NKAI_TRACE_LEVEL >= 2
|
|
logAi->trace("Ignore path because of locked hero");
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
if(path.getFirstBlockedAction())
|
|
{
|
|
#if NKAI_TRACE_LEVEL >= 2
|
|
// TODO: decomposition?
|
|
logAi->trace("Ignore path. Action is blocked.");
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
auto heroRole = ai->heroManager->getHeroRole(path.targetHero);
|
|
|
|
auto upgrade = ai->armyManager->calculateCreaturesUpgrade(path.heroArmy, upgrader, availableResources);
|
|
|
|
if(!upgrader->garrisonHero
|
|
&& (
|
|
hasMainAround
|
|
|| ai->heroManager->getHeroRole(path.targetHero) == HeroRole::MAIN))
|
|
{
|
|
ArmyUpgradeInfo armyToGetOrBuy;
|
|
|
|
armyToGetOrBuy.addArmyToGet(
|
|
ai->armyManager->getBestArmy(
|
|
path.targetHero,
|
|
path.heroArmy,
|
|
upgrader->getUpperArmy()));
|
|
|
|
armyToGetOrBuy.upgradeValue -= path.heroArmy->getArmyStrength();
|
|
|
|
armyToGetOrBuy.addArmyToBuy(
|
|
ai->armyManager->toSlotInfo(
|
|
ai->armyManager->getArmyAvailableToBuy(
|
|
path.heroArmy,
|
|
upgrader,
|
|
ai->getFreeResources(),
|
|
path.turn())));
|
|
|
|
upgrade.upgradeValue += armyToGetOrBuy.upgradeValue;
|
|
upgrade.upgradeCost += armyToGetOrBuy.upgradeCost;
|
|
vstd::concatenate(upgrade.resultingArmy, armyToGetOrBuy.resultingArmy);
|
|
|
|
if(!upgrade.upgradeValue
|
|
&& armyToGetOrBuy.upgradeValue > 20000
|
|
&& ai->heroManager->canRecruitHero(upgrader)
|
|
&& path.turn() < ai->settings->getScoutHeroTurnDistanceLimit())
|
|
{
|
|
for(auto hero : cb->getAvailableHeroes(upgrader))
|
|
{
|
|
auto scoutReinforcement = ai->armyManager->howManyReinforcementsCanBuy(hero, upgrader)
|
|
+ ai->armyManager->howManyReinforcementsCanGet(hero, upgrader);
|
|
|
|
if(scoutReinforcement >= armyToGetOrBuy.upgradeValue
|
|
&& ai->getFreeGold() >20000
|
|
&& !ai->buildAnalyzer->isGoldPressureHigh())
|
|
{
|
|
Composition recruitHero;
|
|
|
|
recruitHero.addNext(ArmyUpgrade(path.targetHero, town, armyToGetOrBuy)).addNext(RecruitHero(upgrader, hero));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
auto armyValue = (float)upgrade.upgradeValue / path.getHeroStrength();
|
|
|
|
if((armyValue < 0.25f && upgrade.upgradeValue < 40000) || upgrade.upgradeValue < 2000) // 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();
|
|
|
|
auto isSafe = isSafeToVisit(path.targetHero, path.heroArmy, danger);
|
|
|
|
#if NKAI_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",
|
|
upgrader->getObjectName(),
|
|
path.targetHero->getObjectName(),
|
|
path.getHeroStrength(),
|
|
danger,
|
|
path.getTotalArmyLoss());
|
|
#endif
|
|
|
|
if(isSafe)
|
|
{
|
|
tasks.push_back(sptr(Composition().addNext(ArmyUpgrade(path, upgrader, upgrade)).addNext(visitGoal)));
|
|
}
|
|
}
|
|
|
|
return tasks;
|
|
}
|
|
|
|
}
|