mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-25 22:42:04 +02:00
nkai: fixes and skill rewards
This commit is contained in:
@@ -1058,6 +1058,11 @@ void AIGateway::recruitCreatures(const CGDwelling * d, const CArmedInstance * re
|
|||||||
int count = d->creatures[i].first;
|
int count = d->creatures[i].first;
|
||||||
CreatureID creID = d->creatures[i].second.back();
|
CreatureID creID = d->creatures[i].second.back();
|
||||||
|
|
||||||
|
if(!recruiter->getSlotFor(creID).validSlot())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
vstd::amin(count, cb->getResourceAmount() / creID.toCreature()->getFullRecruitCost());
|
vstd::amin(count, cb->getResourceAmount() / creID.toCreature()->getFullRecruitCost());
|
||||||
if(count > 0)
|
if(count > 0)
|
||||||
cb->recruitCreatures(d, recruiter, creID, count, i);
|
cb->recruitCreatures(d, recruiter, creID, count, i);
|
||||||
|
|||||||
@@ -68,19 +68,22 @@ void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo)
|
|||||||
logAi->trace("Checking other buildings");
|
logAi->trace("Checking other buildings");
|
||||||
|
|
||||||
std::vector<std::vector<BuildingID>> otherBuildings = {
|
std::vector<std::vector<BuildingID>> otherBuildings = {
|
||||||
{BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL}
|
{BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL},
|
||||||
|
{BuildingID::MAGES_GUILD_3, BuildingID::MAGES_GUILD_5}
|
||||||
};
|
};
|
||||||
|
|
||||||
if(developmentInfo.existingDwellings.size() >= 2 && ai->cb->getDate(Date::DAY_OF_WEEK) > boost::date_time::Friday)
|
if(developmentInfo.existingDwellings.size() >= 2 && ai->cb->getDate(Date::DAY_OF_WEEK) > boost::date_time::Friday)
|
||||||
{
|
{
|
||||||
otherBuildings.push_back({BuildingID::CITADEL, BuildingID::CASTLE});
|
otherBuildings.push_back({BuildingID::CITADEL, BuildingID::CASTLE});
|
||||||
|
otherBuildings.push_back({BuildingID::HORDE_1});
|
||||||
|
otherBuildings.push_back({BuildingID::HORDE_2});
|
||||||
}
|
}
|
||||||
|
|
||||||
for(auto & buildingSet : otherBuildings)
|
for(auto & buildingSet : otherBuildings)
|
||||||
{
|
{
|
||||||
for(auto & buildingID : buildingSet)
|
for(auto & buildingID : buildingSet)
|
||||||
{
|
{
|
||||||
if(!developmentInfo.town->hasBuilt(buildingID))
|
if(!developmentInfo.town->hasBuilt(buildingID) && developmentInfo.town->town->buildings.count(buildingID))
|
||||||
{
|
{
|
||||||
developmentInfo.addBuildingToBuild(getBuildingOrPrerequisite(developmentInfo.town, buildingID));
|
developmentInfo.addBuildingToBuild(getBuildingOrPrerequisite(developmentInfo.town, buildingID));
|
||||||
|
|
||||||
@@ -190,12 +193,28 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
|||||||
const CCreature * creature = nullptr;
|
const CCreature * creature = nullptr;
|
||||||
CreatureID baseCreatureID;
|
CreatureID baseCreatureID;
|
||||||
|
|
||||||
|
int creatureLevel = -1;
|
||||||
|
int creatureUpgrade = 0;
|
||||||
|
|
||||||
if(BuildingID::DWELL_FIRST <= toBuild && toBuild <= BuildingID::DWELL_UP_LAST)
|
if(BuildingID::DWELL_FIRST <= toBuild && toBuild <= BuildingID::DWELL_UP_LAST)
|
||||||
{
|
{
|
||||||
int level = toBuild - BuildingID::DWELL_FIRST;
|
creatureLevel = (toBuild - BuildingID::DWELL_FIRST) % GameConstants::CREATURES_PER_TOWN;
|
||||||
auto creatures = townInfo->creatures.at(level % GameConstants::CREATURES_PER_TOWN);
|
creatureUpgrade = (toBuild - BuildingID::DWELL_FIRST) / GameConstants::CREATURES_PER_TOWN;
|
||||||
auto creatureID = creatures.size() > level / GameConstants::CREATURES_PER_TOWN
|
}
|
||||||
? creatures.at(level / GameConstants::CREATURES_PER_TOWN)
|
else if(toBuild == BuildingID::HORDE_1 || toBuild == BuildingID::HORDE_1_UPGR)
|
||||||
|
{
|
||||||
|
creatureLevel = townInfo->hordeLvl.at(0);
|
||||||
|
}
|
||||||
|
else if(toBuild == BuildingID::HORDE_2 || toBuild == BuildingID::HORDE_2_UPGR)
|
||||||
|
{
|
||||||
|
creatureLevel = townInfo->hordeLvl.at(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(creatureLevel >= 0)
|
||||||
|
{
|
||||||
|
auto creatures = townInfo->creatures.at(creatureLevel);
|
||||||
|
auto creatureID = creatures.size() > creatureUpgrade
|
||||||
|
? creatures.at(creatureUpgrade)
|
||||||
: creatures.front();
|
: creatures.front();
|
||||||
|
|
||||||
baseCreatureID = creatures.front();
|
baseCreatureID = creatures.front();
|
||||||
@@ -366,12 +385,19 @@ BuildingInfo::BuildingInfo(
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
creatureGrows = creature->getGrowth();
|
if(BuildingID::DWELL_FIRST <= id && id <= BuildingID::DWELL_UP_LAST)
|
||||||
|
{
|
||||||
|
creatureGrows = creature->getGrowth();
|
||||||
|
|
||||||
if(town->hasBuilt(BuildingID::CASTLE))
|
if(town->hasBuilt(BuildingID::CASTLE))
|
||||||
creatureGrows *= 2;
|
creatureGrows *= 2;
|
||||||
else if(town->hasBuilt(BuildingID::CITADEL))
|
else if(town->hasBuilt(BuildingID::CITADEL))
|
||||||
creatureGrows += creatureGrows / 2;
|
creatureGrows += creatureGrows / 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
creatureGrows = creature->getHorde();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
armyStrength = ai->armyManager->evaluateStackPower(creature, creatureGrows);
|
armyStrength = ai->armyManager->evaluateStackPower(creature, creatureGrows);
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const
|
|||||||
{
|
{
|
||||||
captureObjects(ai->nullkiller->objectClusterizer->getNearbyObjects());
|
captureObjects(ai->nullkiller->objectClusterizer->getNearbyObjects());
|
||||||
|
|
||||||
if(tasks.empty() || ai->nullkiller->getScanDepth() == ScanDepth::FULL)
|
if(tasks.empty() || ai->nullkiller->getScanDepth() != ScanDepth::SMALL)
|
||||||
captureObjects(ai->nullkiller->objectClusterizer->getFarObjects());
|
captureObjects(ai->nullkiller->objectClusterizer->getFarObjects());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,41 +49,98 @@ Goals::TGoalVec DefenceBehavior::decompose() const
|
|||||||
return tasks;
|
return tasks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isTreatUnderControl(const CGTownInstance * town, const HitMapInfo & treat, const std::vector<AIPath> & paths)
|
||||||
|
{
|
||||||
|
int dayOfWeek = cb->getDate(Date::DAY_OF_WEEK);
|
||||||
|
|
||||||
|
for(const AIPath & path : paths)
|
||||||
|
{
|
||||||
|
bool treatIsWeak = path.getHeroStrength() / (float)treat.danger > TREAT_IGNORE_RATIO;
|
||||||
|
bool needToSaveGrowth = treat.turn == 0 && dayOfWeek == 7;
|
||||||
|
|
||||||
|
if(treatIsWeak && !needToSaveGrowth)
|
||||||
|
{
|
||||||
|
if((path.exchangeCount == 1 && path.turn() < treat.turn)
|
||||||
|
|| path.turn() < treat.turn - 1
|
||||||
|
|| (path.turn() < treat.turn && treat.turn >= 2))
|
||||||
|
{
|
||||||
|
#if NKAI_TRACE_LEVEL >= 1
|
||||||
|
logAi->trace(
|
||||||
|
"Hero %s can eliminate danger for town %s using path %s.",
|
||||||
|
path.targetHero->getObjectName(),
|
||||||
|
town->getObjectName(),
|
||||||
|
path.toString());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleCounterAttack(
|
||||||
|
const CGTownInstance * town,
|
||||||
|
const HitMapInfo & treat,
|
||||||
|
const HitMapInfo & maximumDanger,
|
||||||
|
Goals::TGoalVec & tasks)
|
||||||
|
{
|
||||||
|
if(treat.hero.validAndSet()
|
||||||
|
&& treat.turn <= 1
|
||||||
|
&& (treat.danger == maximumDanger.danger || treat.turn < maximumDanger.turn))
|
||||||
|
{
|
||||||
|
auto heroCapturingPaths = ai->nullkiller->pathfinder->getPathInfo(treat.hero->visitablePos());
|
||||||
|
auto goals = CaptureObjectsBehavior::getVisitGoals(heroCapturingPaths, treat.hero.get());
|
||||||
|
|
||||||
|
for(int i = 0; i < heroCapturingPaths.size(); i++)
|
||||||
|
{
|
||||||
|
AIPath & path = heroCapturingPaths[i];
|
||||||
|
TSubgoal goal = goals[i];
|
||||||
|
|
||||||
|
if(!goal || goal->invalid() || !goal->isElementar()) continue;
|
||||||
|
|
||||||
|
Composition composition;
|
||||||
|
|
||||||
|
composition.addNext(DefendTown(town, treat, path, true)).addNext(goal);
|
||||||
|
|
||||||
|
tasks.push_back(Goals::sptr(composition));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool handleGarrisonHeroFromPreviousTurn(const CGTownInstance * town, Goals::TGoalVec & tasks)
|
||||||
|
{
|
||||||
|
if(ai->nullkiller->isHeroLocked(town->garrisonHero.get()))
|
||||||
|
{
|
||||||
|
logAi->trace(
|
||||||
|
"Hero %s in garrison of town %s is suposed to defend the town",
|
||||||
|
town->garrisonHero->getNameTranslated(),
|
||||||
|
town->getNameTranslated());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!town->visitingHero && cb->getHeroCount(ai->playerID, false) < GameConstants::MAX_HEROES_PER_PLAYER)
|
||||||
|
{
|
||||||
|
logAi->trace(
|
||||||
|
"Extracting hero %s from garrison of town %s",
|
||||||
|
town->garrisonHero->getNameTranslated(),
|
||||||
|
town->getNameTranslated());
|
||||||
|
|
||||||
|
tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, nullptr).setpriority(5)));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInstance * town) const
|
void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInstance * town) const
|
||||||
{
|
{
|
||||||
logAi->trace("Evaluating defence for %s", town->getNameTranslated());
|
logAi->trace("Evaluating defence for %s", town->getNameTranslated());
|
||||||
|
|
||||||
auto treatNode = ai->nullkiller->dangerHitMap->getObjectTreat(town);
|
auto treatNode = ai->nullkiller->dangerHitMap->getObjectTreat(town);
|
||||||
std::vector<HitMapInfo> treats = ai->nullkiller->dangerHitMap->getTownTreats(town);
|
|
||||||
|
|
||||||
treats.push_back(treatNode.fastestDanger); // no guarantee that fastest danger will be there
|
|
||||||
|
|
||||||
int dayOfWeek = cb->getDate(Date::DAY_OF_WEEK);
|
|
||||||
|
|
||||||
if(town->garrisonHero)
|
|
||||||
{
|
|
||||||
if(ai->nullkiller->isHeroLocked(town->garrisonHero.get()))
|
|
||||||
{
|
|
||||||
logAi->trace(
|
|
||||||
"Hero %s in garrison of town %s is suposed to defend the town",
|
|
||||||
town->garrisonHero->getNameTranslated(),
|
|
||||||
town->getNameTranslated());
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!town->visitingHero && cb->getHeroCount(ai->playerID, false) < GameConstants::MAX_HEROES_PER_PLAYER)
|
|
||||||
{
|
|
||||||
logAi->trace(
|
|
||||||
"Extracting hero %s from garrison of town %s",
|
|
||||||
town->garrisonHero->getNameTranslated(),
|
|
||||||
town->getNameTranslated());
|
|
||||||
|
|
||||||
tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, nullptr).setpriority(5)));
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!treatNode.fastestDanger.hero)
|
if(!treatNode.fastestDanger.hero)
|
||||||
{
|
{
|
||||||
@@ -91,6 +148,15 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<HitMapInfo> treats = ai->nullkiller->dangerHitMap->getTownTreats(town);
|
||||||
|
|
||||||
|
treats.push_back(treatNode.fastestDanger); // no guarantee that fastest danger will be there
|
||||||
|
|
||||||
|
if(town->garrisonHero && handleGarrisonHeroFromPreviousTurn(town, tasks))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t reinforcement = ai->nullkiller->armyManager->howManyReinforcementsCanBuy(town->getUpperArmy(), town);
|
uint64_t reinforcement = ai->nullkiller->armyManager->howManyReinforcementsCanBuy(town->getUpperArmy(), town);
|
||||||
|
|
||||||
@@ -111,74 +177,12 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
|||||||
std::to_string(treat.turn),
|
std::to_string(treat.turn),
|
||||||
treat.hero->getNameTranslated());
|
treat.hero->getNameTranslated());
|
||||||
|
|
||||||
bool treatIsUnderControl = false;
|
handleCounterAttack(town, treat, treatNode.maximumDanger, tasks);
|
||||||
|
|
||||||
for(AIPath & path : paths)
|
if(isTreatUnderControl(town, treat, paths))
|
||||||
{
|
{
|
||||||
if(town->visitingHero && path.targetHero == town->visitingHero.get())
|
|
||||||
{
|
|
||||||
if(path.getHeroStrength() < town->visitingHero->getHeroStrength())
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if(town->garrisonHero && path.targetHero == town->garrisonHero.get())
|
|
||||||
{
|
|
||||||
if(path.getHeroStrength() < town->visitingHero->getHeroStrength())
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if(town->visitingHero)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(treat.hero.validAndSet()
|
|
||||||
&& treat.turn <= 1
|
|
||||||
&& (treat.danger == treatNode.maximumDanger.danger || treat.turn < treatNode.maximumDanger.turn))
|
|
||||||
{
|
|
||||||
auto heroCapturingPaths = ai->nullkiller->pathfinder->getPathInfo(treat.hero->visitablePos());
|
|
||||||
auto goals = CaptureObjectsBehavior::getVisitGoals(heroCapturingPaths, treat.hero.get());
|
|
||||||
|
|
||||||
for(int i = 0; i < heroCapturingPaths.size(); i++)
|
|
||||||
{
|
|
||||||
AIPath & path = heroCapturingPaths[i];
|
|
||||||
TSubgoal goal = goals[i];
|
|
||||||
|
|
||||||
if(!goal || goal->invalid() || !goal->isElementar()) continue;
|
|
||||||
|
|
||||||
Composition composition;
|
|
||||||
|
|
||||||
composition.addNext(DefendTown(town, treat, path, true)).addNext(goal);
|
|
||||||
|
|
||||||
tasks.push_back(Goals::sptr(composition));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool treatIsWeak = path.getHeroStrength() / (float)treat.danger > TREAT_IGNORE_RATIO;
|
|
||||||
bool needToSaveGrowth = treat.turn == 0 && dayOfWeek == 7;
|
|
||||||
|
|
||||||
if(treatIsWeak && !needToSaveGrowth)
|
|
||||||
{
|
|
||||||
if((path.exchangeCount == 1 && path.turn() < treat.turn)
|
|
||||||
|| path.turn() < treat.turn - 1
|
|
||||||
|| (path.turn() < treat.turn && treat.turn >= 2))
|
|
||||||
{
|
|
||||||
#if NKAI_TRACE_LEVEL >= 1
|
|
||||||
logAi->trace(
|
|
||||||
"Hero %s can eliminate danger for town %s using path %s.",
|
|
||||||
path.targetHero->getObjectName(),
|
|
||||||
town->getObjectName(),
|
|
||||||
path.toString());
|
|
||||||
#endif
|
|
||||||
|
|
||||||
treatIsUnderControl = true;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(treatIsUnderControl)
|
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
evaluateRecruitingHero(tasks, treat, town);
|
evaluateRecruitingHero(tasks, treat, town);
|
||||||
|
|
||||||
@@ -205,6 +209,27 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
|||||||
path.movementCost(),
|
path.movementCost(),
|
||||||
path.toString());
|
path.toString());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
auto townDefenseStrength = town->garrisonHero
|
||||||
|
? town->garrisonHero->getTotalStrength()
|
||||||
|
: (town->visitingHero ? town->visitingHero->getTotalStrength() : town->getUpperArmy()->getArmyStrength());
|
||||||
|
|
||||||
|
if(town->visitingHero && path.targetHero == town->visitingHero.get())
|
||||||
|
{
|
||||||
|
if(path.getHeroStrength() < townDefenseStrength)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if(town->garrisonHero && path.targetHero == town->garrisonHero.get())
|
||||||
|
{
|
||||||
|
if(path.getHeroStrength() < townDefenseStrength)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(town->visitingHero)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if(path.turn() <= treat.turn - 2)
|
if(path.turn() <= treat.turn - 2)
|
||||||
{
|
{
|
||||||
#if NKAI_TRACE_LEVEL >= 1
|
#if NKAI_TRACE_LEVEL >= 1
|
||||||
@@ -296,7 +321,20 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
|
|||||||
composition.addNext(DefendTown(town, treat, path));
|
composition.addNext(DefendTown(town, treat, path));
|
||||||
TGoalVec sequence;
|
TGoalVec sequence;
|
||||||
|
|
||||||
if(town->visitingHero && path.targetHero != town->visitingHero && !path.containsHero(town->visitingHero))
|
if(town->garrisonHero && path.targetHero == town->garrisonHero.get() && path.exchangeCount == 1)
|
||||||
|
{
|
||||||
|
composition.addNext(ExchangeSwapTownHeroes(town, town->garrisonHero.get(), HeroLockedReason::DEFENCE));
|
||||||
|
tasks.push_back(Goals::sptr(composition));
|
||||||
|
|
||||||
|
#if NKAI_TRACE_LEVEL >= 1
|
||||||
|
logAi->trace("Locking hero %s in garrison of %s",
|
||||||
|
town->garrisonHero.get()->getObjectName(),
|
||||||
|
town->getObjectName());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if(town->visitingHero && path.targetHero != town->visitingHero && !path.containsHero(town->visitingHero))
|
||||||
{
|
{
|
||||||
if(town->garrisonHero)
|
if(town->garrisonHero)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include "../Markers/HeroExchange.h"
|
#include "../Markers/HeroExchange.h"
|
||||||
#include "../Markers/ArmyUpgrade.h"
|
#include "../Markers/ArmyUpgrade.h"
|
||||||
#include "GatherArmyBehavior.h"
|
#include "GatherArmyBehavior.h"
|
||||||
|
#include "CaptureObjectsBehavior.h"
|
||||||
#include "../AIUtility.h"
|
#include "../AIUtility.h"
|
||||||
#include "../Goals/ExchangeSwapTownHeroes.h"
|
#include "../Goals/ExchangeSwapTownHeroes.h"
|
||||||
|
|
||||||
@@ -235,6 +236,8 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto paths = ai->nullkiller->pathfinder->getPathInfo(pos);
|
auto paths = ai->nullkiller->pathfinder->getPathInfo(pos);
|
||||||
|
auto goals = CaptureObjectsBehavior::getVisitGoals(paths);
|
||||||
|
|
||||||
std::vector<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj;
|
std::vector<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj;
|
||||||
|
|
||||||
#if NKAI_TRACE_LEVEL >= 1
|
#if NKAI_TRACE_LEVEL >= 1
|
||||||
@@ -251,11 +254,23 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
|
|||||||
hasMainAround = true;
|
hasMainAround = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(const AIPath & path : paths)
|
for(int i = 0; i < paths.size(); i++)
|
||||||
{
|
{
|
||||||
|
auto & path = paths[i];
|
||||||
|
auto visitGoal = goals[i];
|
||||||
|
|
||||||
#if NKAI_TRACE_LEVEL >= 2
|
#if NKAI_TRACE_LEVEL >= 2
|
||||||
logAi->trace("Path found %s, %s, %lld", path.toString(), path.targetHero->getObjectName(), path.heroArmy->getArmyStrength());
|
logAi->trace("Path found %s, %s, %lld", path.toString(), path.targetHero->getObjectName(), path.heroArmy->getArmyStrength());
|
||||||
#endif
|
#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(upgrader->visitingHero && (upgrader->visitingHero.get() != path.targetHero || path.exchangeCount == 1))
|
||||||
{
|
{
|
||||||
#if NKAI_TRACE_LEVEL >= 2
|
#if NKAI_TRACE_LEVEL >= 2
|
||||||
@@ -370,11 +385,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
|
|||||||
|
|
||||||
if(isSafe)
|
if(isSafe)
|
||||||
{
|
{
|
||||||
ExecuteHeroChain newWay(path, upgrader);
|
tasks.push_back(sptr(Composition().addNext(ArmyUpgrade(path, upgrader, upgrade)).addNext(visitGoal)));
|
||||||
|
|
||||||
newWay.closestWayRatio = 1;
|
|
||||||
|
|
||||||
tasks.push_back(sptr(Composition().addNext(ArmyUpgrade(path, upgrader, upgrade)).addNext(newWay)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TSubgoal behavior, int decompositi
|
|||||||
void Nullkiller::resetAiState()
|
void Nullkiller::resetAiState()
|
||||||
{
|
{
|
||||||
lockedResources = TResources();
|
lockedResources = TResources();
|
||||||
scanDepth = ScanDepth::FULL;
|
scanDepth = ScanDepth::MAIN_FULL;
|
||||||
playerID = ai->playerID;
|
playerID = ai->playerID;
|
||||||
lockedHeroes.clear();
|
lockedHeroes.clear();
|
||||||
dangerHitMap->reset();
|
dangerHitMap->reset();
|
||||||
@@ -158,11 +158,15 @@ void Nullkiller::updateAiState(int pass, bool fast)
|
|||||||
|
|
||||||
PathfinderSettings cfg;
|
PathfinderSettings cfg;
|
||||||
cfg.useHeroChain = useHeroChain;
|
cfg.useHeroChain = useHeroChain;
|
||||||
cfg.scoutTurnDistanceLimit = SCOUT_TURN_DISTANCE_LIMIT;
|
|
||||||
|
|
||||||
if(scanDepth != ScanDepth::FULL)
|
if(scanDepth == ScanDepth::SMALL)
|
||||||
{
|
{
|
||||||
cfg.mainTurnDistanceLimit = MAIN_TURN_DISTANCE_LIMIT * ((int)scanDepth + 1);
|
cfg.mainTurnDistanceLimit = MAIN_TURN_DISTANCE_LIMIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(scanDepth != ScanDepth::ALL_FULL)
|
||||||
|
{
|
||||||
|
cfg.scoutTurnDistanceLimit = SCOUT_TURN_DISTANCE_LIMIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::this_thread::interruption_point();
|
boost::this_thread::interruption_point();
|
||||||
@@ -233,8 +237,8 @@ void Nullkiller::makeTurn()
|
|||||||
updateAiState(i);
|
updateAiState(i);
|
||||||
|
|
||||||
Goals::TTask bestTask = taskptr(Goals::Invalid());
|
Goals::TTask bestTask = taskptr(Goals::Invalid());
|
||||||
|
|
||||||
do
|
for(;i <= MAXPASS; i++)
|
||||||
{
|
{
|
||||||
Goals::TTaskVec fastTasks = {
|
Goals::TTaskVec fastTasks = {
|
||||||
choseBestTask(sptr(BuyArmyBehavior()), 1),
|
choseBestTask(sptr(BuyArmyBehavior()), 1),
|
||||||
@@ -248,7 +252,11 @@ void Nullkiller::makeTurn()
|
|||||||
executeTask(bestTask);
|
executeTask(bestTask);
|
||||||
updateAiState(i, true);
|
updateAiState(i, true);
|
||||||
}
|
}
|
||||||
} while(bestTask->priority >= FAST_TASK_MINIMAL_PRIORITY);
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Goals::TTaskVec bestTasks = {
|
Goals::TTaskVec bestTasks = {
|
||||||
bestTask,
|
bestTask,
|
||||||
@@ -267,7 +275,6 @@ void Nullkiller::makeTurn()
|
|||||||
bestTask = choseBestTask(bestTasks);
|
bestTask = choseBestTask(bestTasks);
|
||||||
|
|
||||||
HeroPtr hero = bestTask->getHero();
|
HeroPtr hero = bestTask->getHero();
|
||||||
|
|
||||||
HeroRole heroRole = HeroRole::MAIN;
|
HeroRole heroRole = HeroRole::MAIN;
|
||||||
|
|
||||||
if(hero.validAndSet())
|
if(hero.validAndSet())
|
||||||
@@ -276,20 +283,39 @@ void Nullkiller::makeTurn()
|
|||||||
if(heroRole != HeroRole::MAIN || bestTask->getHeroExchangeCount() <= 1)
|
if(heroRole != HeroRole::MAIN || bestTask->getHeroExchangeCount() <= 1)
|
||||||
useHeroChain = false;
|
useHeroChain = false;
|
||||||
|
|
||||||
|
// TODO: better to check turn distance here instead of priority
|
||||||
if((heroRole != HeroRole::MAIN || bestTask->priority < SMALL_SCAN_MIN_PRIORITY)
|
if((heroRole != HeroRole::MAIN || bestTask->priority < SMALL_SCAN_MIN_PRIORITY)
|
||||||
&& scanDepth == ScanDepth::FULL)
|
&& scanDepth == ScanDepth::MAIN_FULL)
|
||||||
{
|
{
|
||||||
useHeroChain = false;
|
useHeroChain = false;
|
||||||
scanDepth = ScanDepth::SMALL;
|
scanDepth = ScanDepth::SMALL;
|
||||||
|
|
||||||
logAi->trace(
|
logAi->trace(
|
||||||
"Goal %s has too low priority %f so increasing scan depth",
|
"Goal %s has low priority %f so decreasing scan depth to gain performance.",
|
||||||
bestTask->toString(),
|
bestTask->toString(),
|
||||||
bestTask->priority);
|
bestTask->priority);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(bestTask->priority < MIN_PRIORITY)
|
if(bestTask->priority < MIN_PRIORITY)
|
||||||
{
|
{
|
||||||
|
auto heroes = cb->getHeroesInfo();
|
||||||
|
auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool
|
||||||
|
{
|
||||||
|
return h->movementPointsRemaining() > 100;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(hasMp && scanDepth != ScanDepth::ALL_FULL)
|
||||||
|
{
|
||||||
|
logAi->trace(
|
||||||
|
"Goal %s has too low priority %f so increasing scan depth to full.",
|
||||||
|
bestTask->toString(),
|
||||||
|
bestTask->priority);
|
||||||
|
|
||||||
|
scanDepth = ScanDepth::ALL_FULL;
|
||||||
|
useHeroChain = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
logAi->trace("Goal %s has too low priority. It is not worth doing it. Ending turn.", bestTask->toString());
|
logAi->trace("Goal %s has too low priority. It is not worth doing it. Ending turn.", bestTask->toString());
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -40,9 +40,11 @@ enum class HeroLockedReason
|
|||||||
|
|
||||||
enum class ScanDepth
|
enum class ScanDepth
|
||||||
{
|
{
|
||||||
FULL = 0,
|
MAIN_FULL = 0,
|
||||||
|
|
||||||
SMALL = 1
|
SMALL = 1,
|
||||||
|
|
||||||
|
ALL_FULL = 2
|
||||||
};
|
};
|
||||||
|
|
||||||
class Nullkiller
|
class Nullkiller
|
||||||
|
|||||||
@@ -915,6 +915,7 @@ public:
|
|||||||
evaluationContext.heroRole = HeroRole::MAIN;
|
evaluationContext.heroRole = HeroRole::MAIN;
|
||||||
evaluationContext.movementCostByRole[evaluationContext.heroRole] += bi.prerequisitesCount;
|
evaluationContext.movementCostByRole[evaluationContext.heroRole] += bi.prerequisitesCount;
|
||||||
evaluationContext.goldCost += bi.buildCostWithPrerequisits[EGameResID::GOLD];
|
evaluationContext.goldCost += bi.buildCostWithPrerequisits[EGameResID::GOLD];
|
||||||
|
evaluationContext.closestWayRatio = 1;
|
||||||
|
|
||||||
if(bi.creatureID != CreatureID::NONE)
|
if(bi.creatureID != CreatureID::NONE)
|
||||||
{
|
{
|
||||||
@@ -938,7 +939,12 @@ public:
|
|||||||
evaluationContext.addNonCriticalStrategicalValue(buildThis.town->creatures.size() * 0.2f);
|
evaluationContext.addNonCriticalStrategicalValue(buildThis.town->creatures.size() * 0.2f);
|
||||||
evaluationContext.armyReward += buildThis.townInfo.armyStrength / 2;
|
evaluationContext.armyReward += buildThis.townInfo.armyStrength / 2;
|
||||||
}
|
}
|
||||||
else
|
else if(bi.id >= BuildingID::MAGES_GUILD_1 && bi.id <= BuildingID::MAGES_GUILD_5)
|
||||||
|
{
|
||||||
|
evaluationContext.skillReward += 2 * (bi.id - BuildingID::MAGES_GUILD_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(evaluationContext.goldReward)
|
||||||
{
|
{
|
||||||
auto goldPreasure = evaluationContext.evaluator.ai->buildAnalyzer->getGoldPreasure();
|
auto goldPreasure = evaluationContext.evaluator.ai->buildAnalyzer->getGoldPreasure();
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ InputVariable: mainTurnDistance
|
|||||||
enabled: true
|
enabled: true
|
||||||
range: 0.000 10.000
|
range: 0.000 10.000
|
||||||
lock-range: true
|
lock-range: true
|
||||||
term: LOWEST Ramp 0.250 0.000
|
term: LOWEST Ramp 0.400 0.000
|
||||||
term: LOW Discrete 0.000 1.000 0.500 0.800 0.800 0.300 2.000 0.000
|
term: LOW Discrete 0.000 1.000 0.500 0.800 0.800 0.300 2.000 0.000
|
||||||
term: MEDIUM Discrete 0.000 0.000 0.500 0.200 0.800 0.700 2.000 1.000 6.000 0.000
|
term: MEDIUM Discrete 0.000 0.000 0.500 0.200 0.800 0.700 2.000 1.000 6.000 0.000
|
||||||
term: LONG Discrete 2.000 0.000 6.000 1.000 10.000 0.800
|
term: LONG Discrete 2.000 0.000 6.000 1.000 10.000 0.800
|
||||||
@@ -238,4 +238,22 @@ RuleBlock: gold
|
|||||||
rule: if goldReward is MEDIUM and goldPreasure is HIGH and heroRole is SCOUT and danger is not NONE then Value is SMALL
|
rule: if goldReward is MEDIUM and goldPreasure is HIGH and heroRole is SCOUT and danger is not NONE then Value is SMALL
|
||||||
rule: if goldReward is MEDIUM and goldPreasure is HIGH and heroRole is MAIN and danger is not NONE and armyLoss is LOW then Value is BITHIGH
|
rule: if goldReward is MEDIUM and goldPreasure is HIGH and heroRole is MAIN and danger is not NONE and armyLoss is LOW then Value is BITHIGH
|
||||||
rule: if goldReward is SMALL and goldPreasure is HIGH and heroRole is SCOUT and danger is NONE then Value is MEDIUM
|
rule: if goldReward is SMALL and goldPreasure is HIGH and heroRole is SCOUT and danger is NONE then Value is MEDIUM
|
||||||
rule: if goldReward is SMALL and goldPreasure is HIGH and heroRole is MAIN and danger is not NONE and armyLoss is LOW then Value is SMALL
|
rule: if goldReward is SMALL and goldPreasure is HIGH and heroRole is MAIN and danger is not NONE and armyLoss is LOW then Value is SMALL
|
||||||
|
RuleBlock: skill reward
|
||||||
|
enabled: true
|
||||||
|
conjunction: AlgebraicProduct
|
||||||
|
disjunction: AlgebraicSum
|
||||||
|
implication: AlgebraicProduct
|
||||||
|
activation: General
|
||||||
|
rule: if heroRole is MAIN and skillReward is LOW and mainTurnDistance is LOWEST and fear is not HIGH then Value is HIGH
|
||||||
|
rule: if heroRole is MAIN and skillReward is MEDIUM and mainTurnDistance is LOWEST and fear is not HIGH then Value is HIGHEST
|
||||||
|
rule: if heroRole is MAIN and skillReward is HIGH and mainTurnDistance is LOWEST and fear is not HIGH then Value is HIGHEST
|
||||||
|
rule: if heroRole is MAIN and skillReward is LOW and mainTurnDistance is LOW and fear is not HIGH then Value is BITHIGH
|
||||||
|
rule: if heroRole is MAIN and skillReward is MEDIUM and mainTurnDistance is LOW and fear is not HIGH then Value is HIGH
|
||||||
|
rule: if heroRole is MAIN and skillReward is HIGH and mainTurnDistance is LOW and fear is not HIGH then Value is HIGHEST
|
||||||
|
rule: if heroRole is MAIN and skillReward is LOW and mainTurnDistance is MEDIUM and fear is not HIGH then Value is MEDIUM
|
||||||
|
rule: if heroRole is MAIN and skillReward is MEDIUM and mainTurnDistance is MEDIUM and fear is not HIGH then Value is BITHIGH
|
||||||
|
rule: if heroRole is MAIN and skillReward is HIGH and mainTurnDistance is MEDIUM and fear is not HIGH then Value is HIGH
|
||||||
|
rule: if heroRole is MAIN and skillReward is LOW and mainTurnDistance is LONG and fear is not HIGH then Value is SMALL
|
||||||
|
rule: if heroRole is MAIN and skillReward is MEDIUM and mainTurnDistance is LONG and fear is not HIGH then Value is MEDIUM
|
||||||
|
rule: if heroRole is MAIN and skillReward is HIGH and mainTurnDistance is LONG and fear is not HIGH then Value is BITHIGH
|
||||||
Reference in New Issue
Block a user