1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-02-03 13:01:33 +02:00

Nullkiller: gold preasure and turn variables for priority evaluation. Tweaking building behavior

This commit is contained in:
Andrii Danylchenko 2021-05-16 14:19:00 +03:00 committed by Andrii Danylchenko
parent a39fa51e14
commit 17a960e850
23 changed files with 479 additions and 194 deletions

View File

@ -156,6 +156,12 @@ void BuildAnalyzer::update()
return val1 > val2; return val1 > val2;
}); });
updateDailyIncome();
goldPreasure = (float)armyCost[Res::GOLD] / (1 + cb->getResourceAmount(Res::GOLD) + (float)dailyIncome[Res::GOLD] * 7.0f);
logAi->trace("Gold preasure: %f", goldPreasure);
} }
void BuildAnalyzer::reset() void BuildAnalyzer::reset()
@ -254,11 +260,12 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
return info; return info;
} }
TResources BuildAnalyzer::getDailyIncome() const void BuildAnalyzer::updateDailyIncome()
{ {
auto objects = cb->getMyObjects(); auto objects = cb->getMyObjects();
auto towns = cb->getTownsInfo(); auto towns = cb->getTownsInfo();
TResources dailyIncome = TResources();
dailyIncome = TResources();
for(const CGObjectInstance* obj : objects) for(const CGObjectInstance* obj : objects)
{ {
@ -274,8 +281,6 @@ TResources BuildAnalyzer::getDailyIncome() const
{ {
dailyIncome += town->dailyIncome(); dailyIncome += town->dailyIncome();
} }
return dailyIncome;
} }
void TownDevelopmentInfo::addExistingDwelling(const BuildingInfo & existingDwelling) void TownDevelopmentInfo::addExistingDwelling(const BuildingInfo & existingDwelling)

View File

@ -70,6 +70,8 @@ private:
TResources totalDevelopmentCost; TResources totalDevelopmentCost;
std::vector<TownDevelopmentInfo> developmentInfos; std::vector<TownDevelopmentInfo> developmentInfos;
TResources armyCost; TResources armyCost;
TResources dailyIncome;
float goldPreasure;
public: public:
void update(); void update();
@ -77,8 +79,8 @@ public:
TResources getResourcesRequiredNow() const; TResources getResourcesRequiredNow() const;
TResources getTotalResourcesRequired() const; TResources getTotalResourcesRequired() const;
const std::vector<TownDevelopmentInfo> & getDevelopmentInfo() const { return developmentInfos; } const std::vector<TownDevelopmentInfo> & getDevelopmentInfo() const { return developmentInfos; }
TResources getDailyIncome() const { return dailyIncome; }
TResources getDailyIncome() const; float getGoldPreasure() const { return goldPreasure; }
private: private:
BuildingInfo getBuildingOrPrerequisite( BuildingInfo getBuildingOrPrerequisite(
@ -89,5 +91,6 @@ private:
void updateTownDwellings(TownDevelopmentInfo & developmentInfo); void updateTownDwellings(TownDevelopmentInfo & developmentInfo);
void updateOtherBuildings(TownDevelopmentInfo & developmentInfo); void updateOtherBuildings(TownDevelopmentInfo & developmentInfo);
void updateDailyIncome();
void reset(); void reset();
}; };

View File

@ -275,6 +275,35 @@ std::vector<UpgradeInfo> ArmyManager::getDwellingUpgrades(const CCreatureSet * a
{ {
std::vector<UpgradeInfo> upgrades; std::vector<UpgradeInfo> upgrades;
for(auto creature : army->Slots())
{
CreatureID initial = creature.second->getCreatureID();
auto possibleUpgrades = initial.toCreature()->upgrades;
vstd::erase_if(possibleUpgrades, [&](CreatureID creID) -> bool
{
for(auto pair : dwelling->creatures)
{
if(vstd::contains(pair.second, creID))
return false;
}
return true;
});
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);
upgrades.push_back(upgrade);
}
return upgrades; return upgrades;
} }
@ -304,6 +333,9 @@ ArmyUpgradeInfo ArmyManager::calculateCreateresUpgrade(
const CGObjectInstance * upgrader, const CGObjectInstance * upgrader,
const TResources & availableResources) const const TResources & availableResources) const
{ {
if(!upgrader)
return ArmyUpgradeInfo();
std::vector<UpgradeInfo> upgrades = getPossibleUpgrades(army, upgrader); std::vector<UpgradeInfo> upgrades = getPossibleUpgrades(army, upgrader);
vstd::erase_if(upgrades, [&](const UpgradeInfo & u) -> bool vstd::erase_if(upgrades, [&](const UpgradeInfo & u) -> bool

View File

@ -49,6 +49,7 @@ Goals::TGoalVec BuildingBehavior::getTasks()
totalDevelopmentCost.toString()); totalDevelopmentCost.toString());
auto & developmentInfos = ai->nullkiller->buildAnalyzer->getDevelopmentInfo(); auto & developmentInfos = ai->nullkiller->buildAnalyzer->getDevelopmentInfo();
auto goldPreasure = ai->nullkiller->buildAnalyzer->getGoldPreasure();
for(auto & developmentInfo : developmentInfos) for(auto & developmentInfo : developmentInfos)
{ {
@ -56,7 +57,8 @@ Goals::TGoalVec BuildingBehavior::getTasks()
for(auto & buildingInfo : developmentInfo.toBuild) for(auto & buildingInfo : developmentInfo.toBuild)
{ {
tasks.push_back(sptr(BuildThis(buildingInfo, developmentInfo))); if(goldPreasure < MAX_GOLD_PEASURE || buildingInfo.dailyIncome[Res::GOLD] > 0)
tasks.push_back(sptr(BuildThis(buildingInfo, developmentInfo)));
} }
} }

View File

@ -14,6 +14,7 @@
#include "../AIUtility.h" #include "../AIUtility.h"
#include "../Goals/BuyArmy.h" #include "../Goals/BuyArmy.h"
#include "../Goals/VisitTile.h" #include "../Goals/VisitTile.h"
#include "../Engine/Nullkiller.h"
#include "lib/mapping/CMap.h" //for victory conditions #include "lib/mapping/CMap.h" //for victory conditions
#include "lib/CPathfinder.h" #include "lib/CPathfinder.h"
@ -34,6 +35,9 @@ Goals::TGoalVec BuyArmyBehavior::getTasks()
if(cb->getDate(Date::DAY) == 1) if(cb->getDate(Date::DAY) == 1)
return tasks; return tasks;
if(ai->nullkiller->buildAnalyzer->getGoldPreasure() > MAX_GOLD_PEASURE)
return tasks;
auto heroes = cb->getHeroesInfo(); auto heroes = cb->getHeroesInfo();

View File

@ -38,11 +38,11 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks()
return; return;
} }
logAi->trace("Scanning objects, count %d", objs.size()); logAi->debug("Scanning objects, count %d", objs.size());
for(auto objToVisit : objs) for(auto objToVisit : objs)
{ {
#ifdef VCMI_TRACE_PATHFINDER #ifdef AI_TRACE_LEVEL >= 1
logAi->trace("Checking object %s, %s", objToVisit->getObjectName(), objToVisit->visitablePos().toString()); logAi->trace("Checking object %s, %s", objToVisit->getObjectName(), objToVisit->visitablePos().toString());
#endif #endif
@ -55,19 +55,19 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks()
std::vector<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj; std::vector<std::shared_ptr<ExecuteHeroChain>> waysToVisitObj;
std::shared_ptr<ExecuteHeroChain> closestWay; std::shared_ptr<ExecuteHeroChain> closestWay;
#ifdef VCMI_TRACE_PATHFINDER #ifdef AI_TRACE_LEVEL >= 1
logAi->trace("Found %d paths", paths.size()); logAi->trace("Found %d paths", paths.size());
#endif #endif
for(auto & path : paths) for(auto & path : paths)
{ {
#ifdef VCMI_TRACE_PATHFINDER #ifdef AI_TRACE_LEVEL >= 2
logAi->trace("Path found %s", path.toString()); logAi->trace("Path found %s", path.toString());
#endif #endif
if(path.getFirstBlockedAction()) if(path.getFirstBlockedAction())
{ {
#ifdef VCMI_TRACE_PATHFINDER #ifdef AI_TRACE_LEVEL >= 2
// TODO: decomposition? // TODO: decomposition?
logAi->trace("Ignore path. Action is blocked."); logAi->trace("Ignore path. Action is blocked.");
#endif #endif
@ -76,8 +76,8 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks()
if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path)) if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
{ {
#ifdef VCMI_TRACE_PATHFINDER #ifdef AI_TRACE_LEVEL >= 2
logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %d", path.heroArmy->getArmyStrength()); logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength());
#endif #endif
continue; continue;
} }
@ -93,11 +93,11 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks()
auto isSafe = isSafeToVisit(hero, path.heroArmy, danger); auto isSafe = isSafeToVisit(hero, path.heroArmy, danger);
#ifdef VCMI_TRACE_PATHFINDER #ifdef AI_TRACE_LEVEL >= 2
logAi->trace( logAi->trace(
"It is %s to visit %s by %s with army %lld, danger %lld and army loss %lld", "It is %s to visit %s by %s with army %lld, danger %lld and army loss %lld",
isSafe ? "safe" : "not safe", isSafe ? "safe" : "not safe",
objToVisit->instanceName, objToVisit->getObjectName(),
hero->name, hero->name,
path.getHeroStrength(), path.getHeroStrength(),
danger, danger,
@ -123,8 +123,11 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks()
if(ai->nullkiller->arePathHeroesLocked(way->getPath())) if(ai->nullkiller->arePathHeroesLocked(way->getPath()))
continue; continue;
if(ai->nullkiller->getHeroLockedReason(way->hero.get()) == HeroLockedReason::STARTUP)
continue;
way->evaluationContext.closestWayRatio way->evaluationContext.closestWayRatio
= way->evaluationContext.movementCost / closestWay->evaluationContext.movementCost; = closestWay->evaluationContext.movementCost / way->evaluationContext.movementCost;
tasks.push_back(sptr(*way)); tasks.push_back(sptr(*way));
} }

View File

@ -177,7 +177,7 @@ Goals::TGoalVec StartupBehavior::getTasks()
&& !town->visitingHero && !town->visitingHero
&& ai->nullkiller->getHeroLockedReason(town->garrisonHero) != HeroLockedReason::DEFENCE) && ai->nullkiller->getHeroLockedReason(town->garrisonHero) != HeroLockedReason::DEFENCE)
{ {
tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(town, nullptr).setpriority(0.0001f))); tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(town, nullptr).setpriority(MIN_PRIORITY)));
} }
} }
} }

View File

@ -84,7 +84,7 @@ void Nullkiller::updateAiState()
{ {
auto lockedReason = getHeroLockedReason(hero.h); auto lockedReason = getHeroLockedReason(hero.h);
return lockedReason == HeroLockedReason::DEFENCE || lockedReason == HeroLockedReason::STARTUP; return lockedReason == HeroLockedReason::DEFENCE;
}); });
ai->ah->updatePaths(activeHeroes, true); ai->ah->updatePaths(activeHeroes, true);
@ -134,6 +134,13 @@ void Nullkiller::makeTurn()
return; return;
} }
if(bestTask->priority < MIN_PRIORITY)
{
logAi->trace("Goal %s has too low priority. It is not worth doing it. Ending turn.", bestTask->name());
return;
}
logAi->debug("Trying to realize %s (value %2.3f)", bestTask->name(), bestTask->priority); logAi->debug("Trying to realize %s (value %2.3f)", bestTask->name(), bestTask->priority);
try try

View File

@ -15,6 +15,9 @@
#include "../Goals/AbstractGoal.h" #include "../Goals/AbstractGoal.h"
#include "../Behaviors/Behavior.h" #include "../Behaviors/Behavior.h"
const float MAX_GOLD_PEASURE = 0.3f;
const float MIN_PRIORITY = 0.01f;
enum class HeroLockedReason enum class HeroLockedReason
{ {
NOT_LOCKED = 0, NOT_LOCKED = 0,

View File

@ -49,6 +49,7 @@ void PriorityEvaluator::initVisitTile()
armyLossPersentageVariable = engine->getInputVariable("armyLoss"); armyLossPersentageVariable = engine->getInputVariable("armyLoss");
heroRoleVariable = engine->getInputVariable("heroRole"); heroRoleVariable = engine->getInputVariable("heroRole");
dangerVariable = engine->getInputVariable("danger"); dangerVariable = engine->getInputVariable("danger");
turnVariable = engine->getInputVariable("turn");
mainTurnDistanceVariable = engine->getInputVariable("mainTurnDistance"); mainTurnDistanceVariable = engine->getInputVariable("mainTurnDistance");
scoutTurnDistanceVariable = engine->getInputVariable("scoutTurnDistance"); scoutTurnDistanceVariable = engine->getInputVariable("scoutTurnDistance");
goldRewardVariable = engine->getInputVariable("goldReward"); goldRewardVariable = engine->getInputVariable("goldReward");
@ -57,6 +58,8 @@ void PriorityEvaluator::initVisitTile()
rewardTypeVariable = engine->getInputVariable("rewardType"); rewardTypeVariable = engine->getInputVariable("rewardType");
closestHeroRatioVariable = engine->getInputVariable("closestHeroRatio"); closestHeroRatioVariable = engine->getInputVariable("closestHeroRatio");
strategicalValueVariable = engine->getInputVariable("strategicalValue"); strategicalValueVariable = engine->getInputVariable("strategicalValue");
goldPreasureVariable = engine->getInputVariable("goldPreasure");
goldCostVariable = engine->getInputVariable("goldCost");
value = engine->getOutputVariable("Value"); value = engine->getOutputVariable("Value");
} }
@ -119,6 +122,25 @@ uint64_t getDwellingScore(const CGObjectInstance * target, bool checkGold)
return score; return score;
} }
int getDwellingArmyCost(const CGObjectInstance * target)
{
auto dwelling = dynamic_cast<const CGDwelling *>(target);
int cost = 0;
for(auto & creLevel : dwelling->creatures)
{
if(creLevel.first && creLevel.second.size())
{
auto creature = creLevel.second.back().toCreature();
auto creaturesAreFree = creature->level == 1;
if(!creaturesAreFree)
cost += creature->cost[Res::GOLD] * creLevel.first;
}
}
return cost;
}
uint64_t evaluateArtifactArmyValue(CArtifactInstance * art) uint64_t evaluateArtifactArmyValue(CArtifactInstance * art)
{ {
if(art->artType->id == ArtifactID::SPELL_SCROLL) if(art->artType->id == ArtifactID::SPELL_SCROLL)
@ -192,6 +214,30 @@ uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * h
} }
} }
int getGoldCost(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army)
{
if(!target)
return 0;
switch(target->ID)
{
case Obj::HILL_FORT:
return ai->ah->calculateCreateresUpgrade(army, target, cb->getResourceAmount()).upgradeCost[Res::GOLD];
case Obj::SCHOOL_OF_MAGIC:
case Obj::SCHOOL_OF_WAR:
return 1000;
case Obj::UNIVERSITY:
return 2000;
case Obj::CREATURE_GENERATOR1:
case Obj::CREATURE_GENERATOR2:
case Obj::CREATURE_GENERATOR3:
case Obj::CREATURE_GENERATOR4:
return getDwellingArmyCost(target);
default:
return 0;
}
}
float getStrategicalValue(const CGObjectInstance * target); float getStrategicalValue(const CGObjectInstance * target);
float getEnemyHeroStrategicalValue(const CGHeroInstance * enemy) float getEnemyHeroStrategicalValue(const CGHeroInstance * enemy)
@ -216,9 +262,11 @@ float getResourceRequirementStrength(int resType)
return 0; return 0;
if(dailyIncome[resType] == 0) if(dailyIncome[resType] == 0)
return 1; return 1.0f;
return (float)requiredResources[resType] / dailyIncome[resType] / 3; float ratio = (float)requiredResources[resType] / dailyIncome[resType] / 2;
return std::min(ratio, 1.0f);
} }
float getTotalResourceRequirementStrength(int resType) float getTotalResourceRequirementStrength(int resType)
@ -229,10 +277,11 @@ float getTotalResourceRequirementStrength(int resType)
if(requiredResources[resType] == 0) if(requiredResources[resType] == 0)
return 0; return 0;
if(dailyIncome[resType] == 0) float ratio = dailyIncome[resType] == 0
return requiredResources[resType] / 30; ? requiredResources[resType] / 50
: (float)requiredResources[resType] / dailyIncome[resType] / 50;
return (float)requiredResources[resType] / dailyIncome[resType] / 30; return std::min(ratio, 1.0f);
} }
float getStrategicalValue(const CGObjectInstance * target) float getStrategicalValue(const CGObjectInstance * target)
@ -243,18 +292,21 @@ float getStrategicalValue(const CGObjectInstance * target)
switch(target->ID) switch(target->ID)
{ {
case Obj::MINE: case Obj::MINE:
return target->subID == Res::GOLD ? 0.8f : 0.05f + 0.3f * getTotalResourceRequirementStrength(target->subID) + 0.5f * getResourceRequirementStrength(target->subID); return target->subID == Res::GOLD ? 0.5f : 0.05f * getTotalResourceRequirementStrength(target->subID) + 0.05f * getResourceRequirementStrength(target->subID);
case Obj::RESOURCE: case Obj::RESOURCE:
return target->subID == Res::GOLD ? 0 : 0.5f * getResourceRequirementStrength(target->subID); return target->subID == Res::GOLD ? 0 : 0.3f * getResourceRequirementStrength(target->subID);
case Obj::TOWN: case Obj::TOWN:
return target->tempOwner == PlayerColor::NEUTRAL ? 0.5 : 1; return dynamic_cast<const CGTownInstance *>(target)->hasFort()
? (target->tempOwner == PlayerColor::NEUTRAL ? 0.8f : 1.0f)
: 0.4f;
case Obj::HERO: case Obj::HERO:
return cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES return cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
? getEnemyHeroStrategicalValue(dynamic_cast<const CGHeroInstance *>(target)) ? getEnemyHeroStrategicalValue(dynamic_cast<const CGHeroInstance *>(target))
: 0; : 0;
default: default:
return 0; return 0;
} }
@ -388,13 +440,16 @@ public:
auto day = cb->getDate(Date::DAY); auto day = cb->getDate(Date::DAY);
auto hero = heroPtr.get(); auto hero = heroPtr.get();
bool checkGold = evaluationContext.danger == 0; bool checkGold = evaluationContext.danger == 0;
auto army = chain.getPath().heroArmy;
evaluationContext.armyLossPersentage = task->evaluationContext.armyLoss / (double)task->evaluationContext.heroStrength; evaluationContext.armyLossPersentage = task->evaluationContext.armyLoss / (double)task->evaluationContext.heroStrength;
evaluationContext.heroRole = ai->ah->getHeroRole(heroPtr); evaluationContext.heroRole = ai->ah->getHeroRole(heroPtr);
evaluationContext.goldReward = getGoldReward(target, hero); evaluationContext.goldReward = getGoldReward(target, hero);
evaluationContext.armyReward = getArmyReward(target, hero, chain.getPath().heroArmy, checkGold); evaluationContext.armyReward = getArmyReward(target, hero, army, checkGold);
evaluationContext.skillReward = getSkillReward(target, hero, evaluationContext.heroRole); evaluationContext.skillReward = getSkillReward(target, hero, evaluationContext.heroRole);
evaluationContext.strategicalValue = getStrategicalValue(target); evaluationContext.strategicalValue = getStrategicalValue(target);
evaluationContext.goldCost = getGoldCost(target, hero, army);
evaluationContext.turn = chain.getPath().turn();
return evaluationContext; return evaluationContext;
} }
@ -409,15 +464,16 @@ public:
Goals::BuildThis & buildThis = dynamic_cast<Goals::BuildThis &>(*task); Goals::BuildThis & buildThis = dynamic_cast<Goals::BuildThis &>(*task);
auto & bi = buildThis.buildingInfo; auto & bi = buildThis.buildingInfo;
evaluationContext.goldReward = bi.dailyIncome[Res::GOLD] / 2; evaluationContext.goldReward = 7 * bi.dailyIncome[Res::GOLD] / 2; // 7 day income but half we already have
evaluationContext.heroRole = HeroRole::MAIN; evaluationContext.heroRole = HeroRole::MAIN;
evaluationContext.movementCostByRole[evaluationContext.heroRole] = bi.prerequisitesCount; evaluationContext.movementCostByRole[evaluationContext.heroRole] = bi.prerequisitesCount;
evaluationContext.armyReward = 0; evaluationContext.armyReward = 0;
evaluationContext.strategicalValue = buildThis.townInfo.armyScore / 50000.0; evaluationContext.strategicalValue = buildThis.townInfo.armyScore / 50000.0;
evaluationContext.goldCost = bi.buildCostWithPrerequisits[Res::GOLD];
if(bi.creatureID != CreatureID::NONE) if(bi.creatureID != CreatureID::NONE)
{ {
evaluationContext.strategicalValue += 0.5f + 0.1f * bi.creatureLevel; evaluationContext.strategicalValue += (0.5f + 0.1f * bi.creatureLevel) / (float)bi.prerequisitesCount;
if(bi.baseCreatureID == bi.creatureID) if(bi.baseCreatureID == bi.creatureID)
{ {
@ -429,7 +485,11 @@ public:
evaluationContext.armyReward = upgradedPower - creaturesToUpgrade.power; evaluationContext.armyReward = upgradedPower - creaturesToUpgrade.power;
} }
else
{
evaluationContext.strategicalValue = ai->nullkiller->buildAnalyzer->getGoldPreasure() * evaluationContext.goldReward / 300.0f;
}
return evaluationContext; return evaluationContext;
} }
}; };
@ -485,6 +545,9 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
rewardTypeVariable->setValue(rewardType); rewardTypeVariable->setValue(rewardType);
closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio); closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio);
strategicalValueVariable->setValue(evaluationContext.strategicalValue); strategicalValueVariable->setValue(evaluationContext.strategicalValue);
goldPreasureVariable->setValue(ai->nullkiller->buildAnalyzer->getGoldPreasure());
goldCostVariable->setValue(evaluationContext.goldCost / ((float)cb->getResourceAmount(Res::GOLD) + (float)ai->nullkiller->buildAnalyzer->getDailyIncome()[Res::GOLD] + 1.0f));
turnVariable->setValue(evaluationContext.turn);
engine->process(); engine->process();
//engine.process(VISIT_TILE); //TODO: Process only Visit_Tile //engine.process(VISIT_TILE); //TODO: Process only Visit_Tile
@ -497,16 +560,18 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
assert(result >= 0); assert(result >= 0);
#ifdef VCMI_TRACE_PATHFINDER #ifdef VCMI_TRACE_PATHFINDER
logAi->trace("Evaluated %s, loss: %f, turns main: %f, scout: %f, gold: %d, army gain: %d, danger: %d, role: %s, strategical value: %f, result %f", logAi->trace("Evaluated %s, loss: %f, turns main: %f, scout: %f, gold: %d, cost: %d, army gain: %d, danger: %d, role: %s, strategical value: %f, cwr: %f, result %f",
task->name(), task->name(),
evaluationContext.armyLossPersentage, evaluationContext.armyLossPersentage,
evaluationContext.movementCostByRole[HeroRole::MAIN], evaluationContext.movementCostByRole[HeroRole::MAIN],
evaluationContext.movementCostByRole[HeroRole::SCOUT], evaluationContext.movementCostByRole[HeroRole::SCOUT],
evaluationContext.goldReward, evaluationContext.goldReward,
evaluationContext.goldCost,
evaluationContext.armyReward, evaluationContext.armyReward,
evaluationContext.danger, evaluationContext.danger,
evaluationContext.heroRole ? "scout" : "main", evaluationContext.heroRole ? "scout" : "main",
evaluationContext.strategicalValue, evaluationContext.strategicalValue,
evaluationContext.closestWayRatio,
result); result);
#endif #endif

View File

@ -32,6 +32,7 @@ private:
fl::InputVariable * heroRoleVariable; fl::InputVariable * heroRoleVariable;
fl::InputVariable * mainTurnDistanceVariable; fl::InputVariable * mainTurnDistanceVariable;
fl::InputVariable * scoutTurnDistanceVariable; fl::InputVariable * scoutTurnDistanceVariable;
fl::InputVariable * turnVariable;
fl::InputVariable * goldRewardVariable; fl::InputVariable * goldRewardVariable;
fl::InputVariable * armyRewardVariable; fl::InputVariable * armyRewardVariable;
fl::InputVariable * dangerVariable; fl::InputVariable * dangerVariable;
@ -39,6 +40,8 @@ private:
fl::InputVariable * strategicalValueVariable; fl::InputVariable * strategicalValueVariable;
fl::InputVariable * rewardTypeVariable; fl::InputVariable * rewardTypeVariable;
fl::InputVariable * closestHeroRatioVariable; fl::InputVariable * closestHeroRatioVariable;
fl::InputVariable * goldPreasureVariable;
fl::InputVariable * goldCostVariable;
fl::OutputVariable * value; fl::OutputVariable * value;
std::map<Goals::EGoals, std::shared_ptr<IEvaluationContextBuilder>> evaluationContextBuilders; std::map<Goals::EGoals, std::shared_ptr<IEvaluationContextBuilder>> evaluationContextBuilders;

View File

@ -210,7 +210,7 @@ ui64 FuzzyHelper::evaluateDanger(crint3 tile, const CGHeroInstance * visitor)
return evaluateDanger(tile, visitor, ai.get()); return evaluateDanger(tile, visitor, ai.get());
} }
ui64 FuzzyHelper::evaluateDanger(crint3 tile, const CGHeroInstance * visitor, const VCAI * ai) ui64 FuzzyHelper::evaluateDanger(crint3 tile, const CGHeroInstance * visitor, const VCAI * ai, bool checkGuards)
{ {
auto cb = ai->myCb; auto cb = ai->myCb;
const TerrainTile * t = cb->getTile(tile, false); const TerrainTile * t = cb->getTile(tile, false);
@ -260,12 +260,15 @@ ui64 FuzzyHelper::evaluateDanger(crint3 tile, const CGHeroInstance * visitor, co
} }
} }
auto guards = cb->getGuardingCreatures(tile); if(checkGuards)
for(auto cre : guards)
{ {
float tacticalAdvantage = tacticalAdvantageEngine.getTacticalAdvantage(visitor, dynamic_cast<const CArmedInstance *>(cre)); auto guards = cb->getGuardingCreatures(tile);
for(auto cre : guards)
{
float tacticalAdvantage = tacticalAdvantageEngine.getTacticalAdvantage(visitor, dynamic_cast<const CArmedInstance *>(cre));
vstd::amax(guardDanger, evaluateDanger(cre, ai) * tacticalAdvantage); //we are interested in strongest monster around vstd::amax(guardDanger, evaluateDanger(cre, ai) * tacticalAdvantage); //we are interested in strongest monster around
}
} }
//TODO mozna odwiedzic blockvis nie ruszajac straznika //TODO mozna odwiedzic blockvis nie ruszajac straznika

View File

@ -44,6 +44,6 @@ public:
//std::shared_ptr<AbstractGoal> chooseSolution (std::vector<std::shared_ptr<AbstractGoal>> & vec); //std::shared_ptr<AbstractGoal> chooseSolution (std::vector<std::shared_ptr<AbstractGoal>> & vec);
ui64 evaluateDanger(const CGObjectInstance * obj, const VCAI * ai); ui64 evaluateDanger(const CGObjectInstance * obj, const VCAI * ai);
ui64 evaluateDanger(crint3 tile, const CGHeroInstance * visitor, const VCAI * ai); ui64 evaluateDanger(crint3 tile, const CGHeroInstance * visitor, const VCAI * ai, bool checkGuards = true);
ui64 evaluateDanger(crint3 tile, const CGHeroInstance * visitor); ui64 evaluateDanger(crint3 tile, const CGHeroInstance * visitor);
}; };

View File

@ -104,9 +104,11 @@ namespace Goals
float armyLossPersentage; float armyLossPersentage;
float armyReward; float armyReward;
int32_t goldReward; int32_t goldReward;
int32_t goldCost;
float skillReward; float skillReward;
float strategicalValue; float strategicalValue;
HeroRole heroRole; HeroRole heroRole;
uint8_t turn;
EvaluationContext() EvaluationContext()
: movementCost(0.0), : movementCost(0.0),
@ -118,9 +120,11 @@ namespace Goals
movementCostByRole(), movementCostByRole(),
skillReward(0), skillReward(0),
goldReward(0), goldReward(0),
goldCost(0),
armyReward(0), armyReward(0),
armyLossPersentage(0), armyLossPersentage(0),
heroRole(HeroRole::SCOUT) heroRole(HeroRole::SCOUT),
turn(0)
{ {
} }
}; };

View File

@ -69,7 +69,7 @@ void ExchangeSwapTownHeroes::accept(VCAI * ai)
if(town->visitingHero && town->visitingHero.get() != garrisonHero) if(town->visitingHero && town->visitingHero.get() != garrisonHero)
cb->swapGarrisonHero(town); cb->swapGarrisonHero(town);
makePossibleUpgrades(town); ai->makePossibleUpgrades(town);
ai->moveHeroToTile(town->visitablePos(), garrisonHero); ai->moveHeroToTile(town->visitablePos(), garrisonHero);
auto upperArmy = town->getUpperArmy(); auto upperArmy = town->getUpperArmy();
@ -98,7 +98,7 @@ void ExchangeSwapTownHeroes::accept(VCAI * ai)
if(town->visitingHero && town->visitingHero != garrisonHero) if(town->visitingHero && town->visitingHero != garrisonHero)
{ {
ai->nullkiller->unlockHero(town->visitingHero.get()); ai->nullkiller->unlockHero(town->visitingHero.get());
makePossibleUpgrades(town->visitingHero); ai->makePossibleUpgrades(town->visitingHero);
} }
logAi->debug("Put hero %s to garrison of %s", garrisonHero->name, town->name); logAi->debug("Put hero %s to garrison of %s", garrisonHero->name, town->name);

View File

@ -11,6 +11,8 @@
#include "AINodeStorage.h" #include "AINodeStorage.h"
#include "Actions/TownPortalAction.h" #include "Actions/TownPortalAction.h"
#include "../Goals/Goals.h" #include "../Goals/Goals.h"
#include "../VCAI.h"
#include "../Engine/Nullkiller.h"
#include "../../../CCallback.h" #include "../../../CCallback.h"
#include "../../../lib/mapping/CMap.h" #include "../../../lib/mapping/CMap.h"
#include "../../../lib/mapObjects/MapObjects.h" #include "../../../lib/mapObjects/MapObjects.h"
@ -278,6 +280,7 @@ bool AINodeStorage::calculateHeroChainFinal()
for(AIPathNode & node : chains) for(AIPathNode & node : chains)
{ {
if(node.turns > heroChainTurn if(node.turns > heroChainTurn
&& !node.locked
&& node.action != CGPathNode::ENodeAction::UNKNOWN && node.action != CGPathNode::ENodeAction::UNKNOWN
&& node.actor->actorExchangeCount > 1 && node.actor->actorExchangeCount > 1
&& !hasBetterChain(&node, &node, chains)) && !hasBetterChain(&node, &node, chains))
@ -450,7 +453,7 @@ void AINodeStorage::calculateHeroChain(
if(carrier->armyLoss < carrier->actor->armyValue if(carrier->armyLoss < carrier->actor->armyValue
&& (carrier->action != CGPathNode::BATTLE || (carrier->actor->allowBattle && carrier->specialAction)) && (carrier->action != CGPathNode::BATTLE || (carrier->actor->allowBattle && carrier->specialAction))
&& carrier->action != CGPathNode::BLOCKING_VISIT && carrier->action != CGPathNode::BLOCKING_VISIT
&& other->armyLoss < other->actor->armyValue && (other->armyLoss == 0 || other->armyLoss < other->actor->armyValue)
&& carrier->actor->canExchange(other->actor)) && carrier->actor->canExchange(other->actor))
{ {
#if AI_TRACE_LEVEL >= 2 #if AI_TRACE_LEVEL >= 2
@ -632,18 +635,24 @@ void AINodeStorage::setTownsAndDwellings(
{ {
uint64_t mask = 1 << actors.size(); uint64_t mask = 1 << actors.size();
if(!town->garrisonHero && town->getUpperArmy()->getArmyStrength()) if(!town->garrisonHero || ai->nullkiller->getHeroLockedReason(town->garrisonHero) != HeroLockedReason::DEFENCE)
{ {
actors.push_back(std::make_shared<TownGarrisonActor>(town, mask)); actors.push_back(std::make_shared<TownGarrisonActor>(town, mask));
} }
} }
/*auto dayOfWeek = cb->getDate(Date::DAY_OF_WEEK); /*auto dayOfWeek = cb->getDate(Date::DAY_OF_WEEK);
auto waitForGrowth = dayOfWeek > 4; auto waitForGrowth = dayOfWeek > 4;*/
for(auto obj: visitableObjs) for(auto obj: visitableObjs)
{ {
const CGDwelling * dwelling = dynamic_cast<const CGDwelling *>(obj); if(obj->ID == Obj::HILL_FORT)
{
uint64_t mask = 1 << actors.size();
actors.push_back(std::make_shared<HillFortActor>(obj, mask));
}
/*const CGDwelling * dwelling = dynamic_cast<const CGDwelling *>(obj);
if(dwelling) if(dwelling)
{ {
@ -665,8 +674,8 @@ void AINodeStorage::setTownsAndDwellings(
actors.push_back(dwellingActor); actors.push_back(dwellingActor);
} }
} }
} }*/
}*/ }
} }
std::vector<CGPathNode *> AINodeStorage::calculateTeleportations( std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
@ -979,7 +988,7 @@ std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand)
path.targetHero = node.actor->hero; path.targetHero = node.actor->hero;
path.heroArmy = node.actor->creatureSet; path.heroArmy = node.actor->creatureSet;
path.armyLoss = node.armyLoss; path.armyLoss = node.armyLoss;
path.targetObjectDanger = evaluateDanger(pos, path.targetHero); path.targetObjectDanger = evaluateDanger(pos, path.targetHero, false);
path.targetObjectArmyLoss = evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), path.targetObjectDanger); path.targetObjectArmyLoss = evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), path.targetObjectDanger);
path.chainMask = node.actor->chainMask; path.chainMask = node.actor->chainMask;
path.exchangeCount = node.actor->actorExchangeCount; path.exchangeCount = node.actor->actorExchangeCount;

View File

@ -159,13 +159,14 @@ public:
bool isMovementIneficient(const PathNodeInfo & source, CDestinationNodeInfo & destination) const bool isMovementIneficient(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
{ {
// further chain distribution is calculated as the last stage
if(heroChainPass == EHeroChainPass::CHAIN && destination.node->turns > heroChainTurn)
return true;
return hasBetterChain(source, destination); return hasBetterChain(source, destination);
} }
bool isDistanceLimitReached(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
{
return heroChainPass == EHeroChainPass::CHAIN && destination.node->turns > heroChainTurn;
}
template<class NodeRange> template<class NodeRange>
bool hasBetterChain( bool hasBetterChain(
const CGPathNode * source, const CGPathNode * source,
@ -185,9 +186,9 @@ public:
bool calculateHeroChain(); bool calculateHeroChain();
bool calculateHeroChainFinal(); bool calculateHeroChainFinal();
uint64_t evaluateDanger(const int3 & tile, const CGHeroInstance * hero) const uint64_t evaluateDanger(const int3 & tile, const CGHeroInstance * hero, bool checkGuards) const
{ {
return dangerEvaluator->evaluateDanger(tile, hero, ai); return dangerEvaluator->evaluateDanger(tile, hero, ai, checkGuards);
} }
uint64_t evaluateArmyLoss(const CGHeroInstance * hero, uint64_t armyValue, uint64_t danger) const uint64_t evaluateArmyLoss(const CGHeroInstance * hero, uint64_t armyValue, uint64_t danger) const

View File

@ -16,6 +16,8 @@
#include "../../../lib/mapping/CMap.h" #include "../../../lib/mapping/CMap.h"
#include "../../../lib/mapObjects/MapObjects.h" #include "../../../lib/mapObjects/MapObjects.h"
CCreatureSet emptyArmy;
bool HeroExchangeArmy::needsLastStack() const bool HeroExchangeArmy::needsLastStack() const
{ {
return true; return true;
@ -56,6 +58,21 @@ std::string ChainActor::toString() const
return hero->name; return hero->name;
} }
ObjectActor::ObjectActor(const CGObjectInstance * obj, const CCreatureSet * army, uint64_t chainMask, int initialTurn)
:ChainActor(obj, army, chainMask, initialTurn), object(obj)
{
}
const CGObjectInstance * ObjectActor::getActorObject() const
{
return object;
}
std::string ObjectActor::toString() const
{
return object->getObjectName() + " at " + object->visitablePos().toString();
}
HeroActor::HeroActor(const CGHeroInstance * hero, uint64_t chainMask, const VCAI * ai) HeroActor::HeroActor(const CGHeroInstance * hero, uint64_t chainMask, const VCAI * ai)
:ChainActor(hero, chainMask) :ChainActor(hero, chainMask)
{ {
@ -151,18 +168,38 @@ bool HeroExchangeMap::canExchange(const ChainActor * other)
if(result) if(result)
{ {
if(other->armyCost.nonZero()) TResources resources = ai->myCb->getResourceAmount();
{
TResources resources = ai->myCb->getResourceAmount();
if(!resources.canAfford(actor->armyCost + other->armyCost)) if(!resources.canAfford(actor->armyCost + other->armyCost))
{ {
result = false; result = false;
return; #if AI_TRACE_LEVEL >= 2
} logAi->trace(
"Can not afford exchange because of total cost %s but we have %s",
(actor->armyCost + other->armyCost).toString(),
resources.toString());
#endif
return;
} }
uint64_t reinforcment = ai->ah->howManyReinforcementsCanGet(actor->creatureSet, other->creatureSet); auto upgradeInfo = ai->ah->calculateCreateresUpgrade(
actor->creatureSet,
other->getActorObject(),
resources - actor->armyCost - other->armyCost);
uint64_t reinforcment = upgradeInfo.upgradeValue;
if(other->creatureSet->Slots().size())
reinforcment += ai->ah->howManyReinforcementsCanGet(actor->creatureSet, other->creatureSet);
#if AI_TRACE_LEVEL >= 2
logAi->trace(
"Exchange %s->%s reinforcement: %d, %f%%",
actor->toString(),
other->toString(),
reinforcment,
100.0f * reinforcment / actor->armyValue);
#endif
result = reinforcment > actor->armyValue / 10 || reinforcment > 1000; result = reinforcment > actor->armyValue / 10 || reinforcment > 1000;
} }
@ -204,7 +241,27 @@ HeroActor * HeroExchangeMap::exchange(const ChainActor * other)
result = exchangeMap.at(other); result = exchangeMap.at(other);
else else
{ {
CCreatureSet * newArmy = pickBestCreatures(actor->creatureSet, other->creatureSet); TResources availableResources = ai->myCb->getResourceAmount() - actor->armyCost - other->armyCost;
CCreatureSet * upgradedInitialArmy = tryUpgrade(actor->creatureSet, other->getActorObject(), availableResources);
CCreatureSet * newArmy;
if(other->creatureSet->Slots().size())
{
if(upgradedInitialArmy)
{
newArmy = pickBestCreatures(upgradedInitialArmy, other->creatureSet);
delete upgradedInitialArmy;
}
else
{
newArmy = pickBestCreatures(actor->creatureSet, other->creatureSet);
}
}
else
{
newArmy = upgradedInitialArmy;
}
result = new HeroActor(actor, other, newArmy, ai); result = new HeroActor(actor, other, newArmy, ai);
exchangeMap[other] = result; exchangeMap[other] = result;
} }
@ -212,6 +269,25 @@ HeroActor * HeroExchangeMap::exchange(const ChainActor * other)
return result; return result;
} }
CCreatureSet * HeroExchangeMap::tryUpgrade(const CCreatureSet * army, const CGObjectInstance * upgrader, TResources resources) const
{
auto upgradeInfo = ai->ah->calculateCreateresUpgrade(army, upgrader, resources);
if(!upgradeInfo.upgradeValue)
return nullptr;
CCreatureSet * target = new HeroExchangeArmy();
for(auto & slotInfo : upgradeInfo.resultingArmy)
{
auto targetSlot = target->getFreeSlot();
target->addToSlot(targetSlot, slotInfo.creature->idNumber, TQuantity(slotInfo.count));
}
return target;
}
CCreatureSet * HeroExchangeMap::pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const CCreatureSet * HeroExchangeMap::pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const
{ {
CCreatureSet * target = new HeroExchangeArmy(); CCreatureSet * target = new HeroExchangeArmy();
@ -227,8 +303,13 @@ CCreatureSet * HeroExchangeMap::pickBestCreatures(const CCreatureSet * army1, co
return target; return target;
} }
HillFortActor::HillFortActor(const CGObjectInstance * hillFort, uint64_t chainMask)
:ObjectActor(hillFort, &emptyArmy, chainMask, 0)
{
}
DwellingActor::DwellingActor(const CGDwelling * dwelling, uint64_t chainMask, bool waitForGrowth, int dayOfWeek) DwellingActor::DwellingActor(const CGDwelling * dwelling, uint64_t chainMask, bool waitForGrowth, int dayOfWeek)
:ChainActor( :ObjectActor(
dwelling, dwelling,
getDwellingCreatures(dwelling, waitForGrowth), getDwellingCreatures(dwelling, waitForGrowth),
chainMask, chainMask,
@ -288,7 +369,7 @@ CCreatureSet * DwellingActor::getDwellingCreatures(const CGDwelling * dwelling,
} }
TownGarrisonActor::TownGarrisonActor(const CGTownInstance * town, uint64_t chainMask) TownGarrisonActor::TownGarrisonActor(const CGTownInstance * town, uint64_t chainMask)
:ChainActor(town, town->getUpperArmy(), chainMask, 0), town(town) :ObjectActor(town, town->getUpperArmy(), chainMask, 0), town(town)
{ {
} }

View File

@ -60,6 +60,7 @@ public:
virtual std::string toString() const; virtual std::string toString() const;
ChainActor * exchange(const ChainActor * other) const { return exchange(this, other); } ChainActor * exchange(const ChainActor * other) const { return exchange(this, other); }
void setBaseActor(HeroActor * base); void setBaseActor(HeroActor * base);
virtual const CGObjectInstance * getActorObject() const { return hero; }
protected: protected:
virtual ChainActor * exchange(const ChainActor * specialActor, const ChainActor * other) const; virtual ChainActor * exchange(const ChainActor * specialActor, const ChainActor * other) const;
@ -86,6 +87,7 @@ public:
private: private:
CCreatureSet * pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const; CCreatureSet * pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const;
CCreatureSet * tryUpgrade(const CCreatureSet * army, const CGObjectInstance * upgrader, TResources resources) const;
}; };
class HeroActor : public ChainActor class HeroActor : public ChainActor
@ -112,7 +114,24 @@ protected:
virtual ChainActor * exchange(const ChainActor * specialActor, const ChainActor * other) const override; virtual ChainActor * exchange(const ChainActor * specialActor, const ChainActor * other) const override;
}; };
class DwellingActor : public ChainActor class ObjectActor : public ChainActor
{
private:
const CGObjectInstance * object;
public:
ObjectActor(const CGObjectInstance * obj, const CCreatureSet * army, uint64_t chainMask, int initialTurn);
virtual std::string toString() const override;
const CGObjectInstance * getActorObject() const override;
};
class HillFortActor : public ObjectActor
{
public:
HillFortActor(const CGObjectInstance * hillFort, uint64_t chainMask);
};
class DwellingActor : public ObjectActor
{ {
private: private:
const CGDwelling * dwelling; const CGDwelling * dwelling;
@ -127,7 +146,7 @@ protected:
CCreatureSet * getDwellingCreatures(const CGDwelling * dwelling, bool waitForGrowth); CCreatureSet * getDwellingCreatures(const CGDwelling * dwelling, bool waitForGrowth);
}; };
class TownGarrisonActor : public ChainActor class TownGarrisonActor : public ObjectActor
{ {
private: private:
const CGTownInstance * town; const CGTownInstance * town;

View File

@ -47,151 +47,171 @@ namespace AIPathfinding
{ {
if(nodeStorage->isMovementIneficient(source, destination)) if(nodeStorage->isMovementIneficient(source, destination))
{ {
destination.node->locked = true;
destination.blocked = true; destination.blocked = true;
return; return;
} }
auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper); auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper);
if(blocker == BlockingReason::NONE) if(blocker == BlockingReason::NONE)
return; return;
if(blocker == BlockingReason::DESTINATION_BLOCKVIS && destination.nodeObject) auto destGuardians = cb->getGuardingCreatures(destination.coord);
bool allowBypass = true;
switch(blocker)
{ {
auto enemyHero = destination.nodeHero && destination.heroRelations == PlayerRelations::ENEMIES; case BlockingReason::DESTINATION_GUARDED:
allowBypass = bypassDestinationGuards(destGuardians, source, destination, pathfinderConfig, pathfinderHelper);
if(!enemyHero && !isObjectRemovable(destination.nodeObject)) break;
case BlockingReason::DESTINATION_BLOCKVIS:
allowBypass = destination.nodeObject && bypassRemovableObject(source, destination, pathfinderConfig, pathfinderHelper);
if(allowBypass && destGuardians.size())
allowBypass = bypassDestinationGuards(destGuardians, source, destination, pathfinderConfig, pathfinderHelper);
break;
}
destination.blocked = !allowBypass || nodeStorage->isDistanceLimitReached(source, destination);
destination.node->locked = !allowBypass;
}
bool AIMovementAfterDestinationRule::bypassRemovableObject(
const PathNodeInfo & source,
CDestinationNodeInfo & destination,
const PathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const
{
auto enemyHero = destination.nodeHero && destination.heroRelations == PlayerRelations::ENEMIES;
if(!enemyHero && !isObjectRemovable(destination.nodeObject))
{
if(nodeStorage->getHero(destination.node) == destination.nodeHero)
return true;
return false;
}
if(destination.nodeObject->ID == Obj::QUEST_GUARD || destination.nodeObject->ID == Obj::BORDERGUARD)
{
auto questObj = dynamic_cast<const IQuestObject *>(destination.nodeObject);
auto nodeHero = pathfinderHelper->hero;
if(!destination.nodeObject->wasVisited(nodeHero->tempOwner)
|| !questObj->checkQuest(nodeHero))
{ {
if(nodeStorage->getHero(destination.node) == destination.nodeHero) nodeStorage->updateAINode(destination.node, [&](AIPathNode * node)
return;
destination.blocked = true;
}
if(destination.nodeObject->ID == Obj::QUEST_GUARD || destination.nodeObject->ID == Obj::BORDERGUARD)
{
auto questObj = dynamic_cast<const IQuestObject *>(destination.nodeObject);
auto nodeHero = pathfinderHelper->hero;
if(!destination.nodeObject->wasVisited(nodeHero->tempOwner)
|| !questObj->checkQuest(nodeHero))
{ {
nodeStorage->updateAINode(destination.node, [&](AIPathNode * node) auto questInfo = QuestInfo(questObj->quest, destination.nodeObject, destination.coord);
{
auto questInfo = QuestInfo(questObj->quest, destination.nodeObject, destination.coord);
node->specialAction.reset(new QuestAction(questInfo)); node->specialAction.reset(new QuestAction(questInfo));
}); });
}
} }
return;
} }
if(blocker == BlockingReason::DESTINATION_VISIT) return true;
}
bool AIMovementAfterDestinationRule::bypassDestinationGuards(
std::vector<const CGObjectInstance *> destGuardians,
const PathNodeInfo & source,
CDestinationNodeInfo & destination,
const PathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const
{
auto srcGuardians = cb->getGuardingCreatures(source.coord);
if(destGuardians.empty())
{ {
return; return false;
} }
if(blocker == BlockingReason::DESTINATION_GUARDED) auto srcNode = nodeStorage->getAINode(source.node);
vstd::erase_if(destGuardians, [&](const CGObjectInstance * destGuard) -> bool
{ {
auto srcGuardians = cb->getGuardingCreatures(source.coord); return vstd::contains(srcGuardians, destGuard);
auto destGuardians = cb->getGuardingCreatures(destination.coord); });
if(destGuardians.empty()) auto guardsAlreadyBypassed = destGuardians.empty() && srcGuardians.size();
{
destination.blocked = true;
return; if(guardsAlreadyBypassed && srcNode->actor->allowBattle)
} {
vstd::erase_if(destGuardians, [&](const CGObjectInstance * destGuard) -> bool
{
return vstd::contains(srcGuardians, destGuard);
});
auto guardsAlreadyBypassed = destGuardians.empty() && srcGuardians.size();
auto srcNode = nodeStorage->getAINode(source.node);
if(guardsAlreadyBypassed && srcNode->actor->allowBattle)
{
#ifdef VCMI_TRACE_PATHFINDER #ifdef VCMI_TRACE_PATHFINDER
logAi->trace( logAi->trace(
"Bypass guard at destination while moving %s -> %s", "Bypass guard at destination while moving %s -> %s",
source.coord.toString(), source.coord.toString(),
destination.coord.toString()); destination.coord.toString());
#endif #endif
return; return true;
}
const AIPathNode * destNode = nodeStorage->getAINode(destination.node);
auto battleNodeOptional = nodeStorage->getOrCreateNode(
destination.coord,
destination.node->layer,
destNode->actor->battleActor);
if(!battleNodeOptional)
{
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Can not allocate battle node while moving %s -> %s",
source.coord.toString(),
destination.coord.toString());
#endif
destination.blocked = true;
return;
}
AIPathNode * battleNode = battleNodeOptional.get();
if(battleNode->locked)
{
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Block bypass guard at destination while moving %s -> %s",
source.coord.toString(),
destination.coord.toString());
#endif
destination.blocked = true;
return;
}
auto hero = nodeStorage->getHero(source.node);
auto danger = nodeStorage->evaluateDanger(destination.coord, hero);
double actualArmyValue = srcNode->actor->armyValue - srcNode->armyLoss;
double loss = nodeStorage->evaluateArmyLoss(hero, actualArmyValue, danger);
if(loss < actualArmyValue)
{
destination.node = battleNode;
nodeStorage->commit(destination, source);
battleNode->armyLoss += loss;
vstd::amax(battleNode->danger, danger);
battleNode->specialAction = std::make_shared<BattleAction>(destination.coord);
if(source.nodeObject && isObjectRemovable(source.nodeObject))
{
battleNode->theNodeBefore = source.node;
}
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Begin bypass guard at destination with danger %s while moving %s -> %s",
std::to_string(danger),
source.coord.toString(),
destination.coord.toString());
#endif
return;
}
} }
destination.blocked = true; const AIPathNode * destNode = nodeStorage->getAINode(destination.node);
auto battleNodeOptional = nodeStorage->getOrCreateNode(
destination.coord,
destination.node->layer,
destNode->actor->battleActor);
if(!battleNodeOptional)
{
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Can not allocate battle node while moving %s -> %s",
source.coord.toString(),
destination.coord.toString());
#endif
return false;
}
AIPathNode * battleNode = battleNodeOptional.get();
if(battleNode->locked)
{
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Block bypass guard at destination while moving %s -> %s",
source.coord.toString(),
destination.coord.toString());
#endif
return false;
}
auto hero = nodeStorage->getHero(source.node);
auto danger = nodeStorage->evaluateDanger(destination.coord, hero, true);
double actualArmyValue = srcNode->actor->armyValue - srcNode->armyLoss;
double loss = nodeStorage->evaluateArmyLoss(hero, actualArmyValue, danger);
if(loss < actualArmyValue)
{
destination.node = battleNode;
nodeStorage->commit(destination, source);
battleNode->armyLoss += loss;
vstd::amax(battleNode->danger, danger);
battleNode->specialAction = std::make_shared<BattleAction>(destination.coord);
if(source.nodeObject && isObjectRemovable(source.nodeObject))
{
battleNode->theNodeBefore = source.node;
}
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Begin bypass guard at destination with danger %s while moving %s -> %s",
std::to_string(danger),
source.coord.toString(),
destination.coord.toString());
#endif
return true;
}
return false;
} }
} }

View File

@ -32,5 +32,19 @@ namespace AIPathfinding
CDestinationNodeInfo & destination, CDestinationNodeInfo & destination,
const PathfinderConfig * pathfinderConfig, const PathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const override; CPathfinderHelper * pathfinderHelper) const override;
private:
bool bypassDestinationGuards(
std::vector<const CGObjectInstance *> destGuardians,
const PathNodeInfo & source,
CDestinationNodeInfo & destination,
const PathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const;
bool bypassRemovableObject(
const PathNodeInfo & source,
CDestinationNodeInfo & destination,
const PathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const;
}; };
} }

View File

@ -762,24 +762,29 @@ void VCAI::loadGame(BinaryDeserializer & h, const int version)
serializeInternal(h, version); serializeInternal(h, version);
} }
void makePossibleUpgrades(const CArmedInstance * obj) bool VCAI::makePossibleUpgrades(const CArmedInstance * obj)
{ {
if(!obj) if(!obj)
return; return false;
bool upgraded = false;
for(int i = 0; i < GameConstants::ARMY_SIZE; i++) for(int i = 0; i < GameConstants::ARMY_SIZE; i++)
{ {
if(const CStackInstance * s = obj->getStackPtr(SlotID(i))) if(const CStackInstance * s = obj->getStackPtr(SlotID(i)))
{ {
UpgradeInfo ui; UpgradeInfo ui;
cb->getUpgradeInfo(obj, SlotID(i), ui); myCb->getUpgradeInfo(obj, SlotID(i), ui);
if(ui.oldID >= 0 && cb->getResourceAmount().canAfford(ui.cost[0] * s->count)) if(ui.oldID >= 0 && myCb->getResourceAmount().canAfford(ui.cost[0] * s->count))
{ {
cb->upgradeCreature(obj, SlotID(i), ui.newID[0]); myCb->upgradeCreature(obj, SlotID(i), ui.newID[0]);
upgraded = true;
logAi->debug("Upgraded %d %s to %s", s->count, ui.oldID.toCreature()->namePl, ui.newID[0].toCreature()->namePl); logAi->debug("Upgraded %d %s to %s", s->count, ui.oldID.toCreature()->namePl, ui.newID[0].toCreature()->namePl);
} }
} }
} }
return upgraded;
} }
void VCAI::makeTurn() void VCAI::makeTurn()
@ -2203,12 +2208,15 @@ void VCAI::tryRealize(Goals::BuyArmy & g)
ui64 valueBought = 0; ui64 valueBought = 0;
//buy the stacks with largest AI value //buy the stacks with largest AI value
makePossibleUpgrades(t); auto upgradeSuccessfull = makePossibleUpgrades(t);
auto armyToBuy = ah->getArmyAvailableToBuy(t->getUpperArmy(), t); auto armyToBuy = ah->getArmyAvailableToBuy(t->getUpperArmy(), t);
if(armyToBuy.empty()) if(armyToBuy.empty())
{ {
if(upgradeSuccessfull)
throw goalFulfilledException(sptr(g));
throw cannotFulfillGoalException("No creatures to buy."); throw cannotFulfillGoalException("No creatures to buy.");
} }

View File

@ -220,6 +220,7 @@ public:
void pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance * other = nullptr); void pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance * other = nullptr);
void moveCreaturesToHero(const CGTownInstance * t); void moveCreaturesToHero(const CGTownInstance * t);
void performObjectInteraction(const CGObjectInstance * obj, HeroPtr h); void performObjectInteraction(const CGObjectInstance * obj, HeroPtr h);
bool makePossibleUpgrades(const CArmedInstance * obj);
bool moveHeroToTile(int3 dst, HeroPtr h); bool moveHeroToTile(int3 dst, HeroPtr h);
void buildStructure(const CGTownInstance * t, BuildingID building); //TODO: move to BuildingManager void buildStructure(const CGTownInstance * t, BuildingID building); //TODO: move to BuildingManager
@ -401,5 +402,3 @@ public:
return msg.c_str(); return msg.c_str();
} }
}; };
void makePossibleUpgrades(const CArmedInstance * obj);