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;
});
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()
@ -254,11 +260,12 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
return info;
}
TResources BuildAnalyzer::getDailyIncome() const
void BuildAnalyzer::updateDailyIncome()
{
auto objects = cb->getMyObjects();
auto towns = cb->getTownsInfo();
TResources dailyIncome = TResources();
dailyIncome = TResources();
for(const CGObjectInstance* obj : objects)
{
@ -274,8 +281,6 @@ TResources BuildAnalyzer::getDailyIncome() const
{
dailyIncome += town->dailyIncome();
}
return dailyIncome;
}
void TownDevelopmentInfo::addExistingDwelling(const BuildingInfo & existingDwelling)

View File

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

View File

@ -275,6 +275,35 @@ std::vector<UpgradeInfo> ArmyManager::getDwellingUpgrades(const CCreatureSet * a
{
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;
}
@ -304,6 +333,9 @@ ArmyUpgradeInfo ArmyManager::calculateCreateresUpgrade(
const CGObjectInstance * upgrader,
const TResources & availableResources) const
{
if(!upgrader)
return ArmyUpgradeInfo();
std::vector<UpgradeInfo> upgrades = getPossibleUpgrades(army, upgrader);
vstd::erase_if(upgrades, [&](const UpgradeInfo & u) -> bool

View File

@ -49,6 +49,7 @@ Goals::TGoalVec BuildingBehavior::getTasks()
totalDevelopmentCost.toString());
auto & developmentInfos = ai->nullkiller->buildAnalyzer->getDevelopmentInfo();
auto goldPreasure = ai->nullkiller->buildAnalyzer->getGoldPreasure();
for(auto & developmentInfo : developmentInfos)
{
@ -56,7 +57,8 @@ Goals::TGoalVec BuildingBehavior::getTasks()
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 "../Goals/BuyArmy.h"
#include "../Goals/VisitTile.h"
#include "../Engine/Nullkiller.h"
#include "lib/mapping/CMap.h" //for victory conditions
#include "lib/CPathfinder.h"
@ -34,6 +35,9 @@ Goals::TGoalVec BuyArmyBehavior::getTasks()
if(cb->getDate(Date::DAY) == 1)
return tasks;
if(ai->nullkiller->buildAnalyzer->getGoldPreasure() > MAX_GOLD_PEASURE)
return tasks;
auto heroes = cb->getHeroesInfo();

View File

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

View File

@ -177,7 +177,7 @@ Goals::TGoalVec StartupBehavior::getTasks()
&& !town->visitingHero
&& 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);
return lockedReason == HeroLockedReason::DEFENCE || lockedReason == HeroLockedReason::STARTUP;
return lockedReason == HeroLockedReason::DEFENCE;
});
ai->ah->updatePaths(activeHeroes, true);
@ -134,6 +134,13 @@ void Nullkiller::makeTurn()
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);
try

View File

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

View File

@ -49,6 +49,7 @@ void PriorityEvaluator::initVisitTile()
armyLossPersentageVariable = engine->getInputVariable("armyLoss");
heroRoleVariable = engine->getInputVariable("heroRole");
dangerVariable = engine->getInputVariable("danger");
turnVariable = engine->getInputVariable("turn");
mainTurnDistanceVariable = engine->getInputVariable("mainTurnDistance");
scoutTurnDistanceVariable = engine->getInputVariable("scoutTurnDistance");
goldRewardVariable = engine->getInputVariable("goldReward");
@ -57,6 +58,8 @@ void PriorityEvaluator::initVisitTile()
rewardTypeVariable = engine->getInputVariable("rewardType");
closestHeroRatioVariable = engine->getInputVariable("closestHeroRatio");
strategicalValueVariable = engine->getInputVariable("strategicalValue");
goldPreasureVariable = engine->getInputVariable("goldPreasure");
goldCostVariable = engine->getInputVariable("goldCost");
value = engine->getOutputVariable("Value");
}
@ -119,6 +122,25 @@ uint64_t getDwellingScore(const CGObjectInstance * target, bool checkGold)
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)
{
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 getEnemyHeroStrategicalValue(const CGHeroInstance * enemy)
@ -216,9 +262,11 @@ float getResourceRequirementStrength(int resType)
return 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)
@ -229,10 +277,11 @@ float getTotalResourceRequirementStrength(int resType)
if(requiredResources[resType] == 0)
return 0;
if(dailyIncome[resType] == 0)
return requiredResources[resType] / 30;
float ratio = dailyIncome[resType] == 0
? 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)
@ -243,18 +292,21 @@ float getStrategicalValue(const CGObjectInstance * target)
switch(target->ID)
{
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:
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:
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:
return cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
? getEnemyHeroStrategicalValue(dynamic_cast<const CGHeroInstance *>(target))
: 0;
default:
return 0;
}
@ -388,13 +440,16 @@ public:
auto day = cb->getDate(Date::DAY);
auto hero = heroPtr.get();
bool checkGold = evaluationContext.danger == 0;
auto army = chain.getPath().heroArmy;
evaluationContext.armyLossPersentage = task->evaluationContext.armyLoss / (double)task->evaluationContext.heroStrength;
evaluationContext.heroRole = ai->ah->getHeroRole(heroPtr);
evaluationContext.goldReward = getGoldReward(target, hero);
evaluationContext.armyReward = getArmyReward(target, hero, chain.getPath().heroArmy, checkGold);
evaluationContext.armyReward = getArmyReward(target, hero, army, checkGold);
evaluationContext.skillReward = getSkillReward(target, hero, evaluationContext.heroRole);
evaluationContext.strategicalValue = getStrategicalValue(target);
evaluationContext.goldCost = getGoldCost(target, hero, army);
evaluationContext.turn = chain.getPath().turn();
return evaluationContext;
}
@ -409,15 +464,16 @@ public:
Goals::BuildThis & buildThis = dynamic_cast<Goals::BuildThis &>(*task);
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.movementCostByRole[evaluationContext.heroRole] = bi.prerequisitesCount;
evaluationContext.armyReward = 0;
evaluationContext.strategicalValue = buildThis.townInfo.armyScore / 50000.0;
evaluationContext.goldCost = bi.buildCostWithPrerequisits[Res::GOLD];
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)
{
@ -429,7 +485,11 @@ public:
evaluationContext.armyReward = upgradedPower - creaturesToUpgrade.power;
}
else
{
evaluationContext.strategicalValue = ai->nullkiller->buildAnalyzer->getGoldPreasure() * evaluationContext.goldReward / 300.0f;
}
return evaluationContext;
}
};
@ -485,6 +545,9 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
rewardTypeVariable->setValue(rewardType);
closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio);
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(VISIT_TILE); //TODO: Process only Visit_Tile
@ -497,16 +560,18 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
assert(result >= 0);
#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(),
evaluationContext.armyLossPersentage,
evaluationContext.movementCostByRole[HeroRole::MAIN],
evaluationContext.movementCostByRole[HeroRole::SCOUT],
evaluationContext.goldReward,
evaluationContext.goldCost,
evaluationContext.armyReward,
evaluationContext.danger,
evaluationContext.heroRole ? "scout" : "main",
evaluationContext.strategicalValue,
evaluationContext.closestWayRatio,
result);
#endif

View File

@ -32,6 +32,7 @@ private:
fl::InputVariable * heroRoleVariable;
fl::InputVariable * mainTurnDistanceVariable;
fl::InputVariable * scoutTurnDistanceVariable;
fl::InputVariable * turnVariable;
fl::InputVariable * goldRewardVariable;
fl::InputVariable * armyRewardVariable;
fl::InputVariable * dangerVariable;
@ -39,6 +40,8 @@ private:
fl::InputVariable * strategicalValueVariable;
fl::InputVariable * rewardTypeVariable;
fl::InputVariable * closestHeroRatioVariable;
fl::InputVariable * goldPreasureVariable;
fl::InputVariable * goldCostVariable;
fl::OutputVariable * value;
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());
}
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;
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);
for(auto cre : guards)
if(checkGuards)
{
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

View File

@ -44,6 +44,6 @@ public:
//std::shared_ptr<AbstractGoal> chooseSolution (std::vector<std::shared_ptr<AbstractGoal>> & vec);
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);
};

View File

@ -104,9 +104,11 @@ namespace Goals
float armyLossPersentage;
float armyReward;
int32_t goldReward;
int32_t goldCost;
float skillReward;
float strategicalValue;
HeroRole heroRole;
uint8_t turn;
EvaluationContext()
: movementCost(0.0),
@ -118,9 +120,11 @@ namespace Goals
movementCostByRole(),
skillReward(0),
goldReward(0),
goldCost(0),
armyReward(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)
cb->swapGarrisonHero(town);
makePossibleUpgrades(town);
ai->makePossibleUpgrades(town);
ai->moveHeroToTile(town->visitablePos(), garrisonHero);
auto upperArmy = town->getUpperArmy();
@ -98,7 +98,7 @@ void ExchangeSwapTownHeroes::accept(VCAI * ai)
if(town->visitingHero && town->visitingHero != garrisonHero)
{
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);

View File

@ -11,6 +11,8 @@
#include "AINodeStorage.h"
#include "Actions/TownPortalAction.h"
#include "../Goals/Goals.h"
#include "../VCAI.h"
#include "../Engine/Nullkiller.h"
#include "../../../CCallback.h"
#include "../../../lib/mapping/CMap.h"
#include "../../../lib/mapObjects/MapObjects.h"
@ -278,6 +280,7 @@ bool AINodeStorage::calculateHeroChainFinal()
for(AIPathNode & node : chains)
{
if(node.turns > heroChainTurn
&& !node.locked
&& node.action != CGPathNode::ENodeAction::UNKNOWN
&& node.actor->actorExchangeCount > 1
&& !hasBetterChain(&node, &node, chains))
@ -450,7 +453,7 @@ void AINodeStorage::calculateHeroChain(
if(carrier->armyLoss < carrier->actor->armyValue
&& (carrier->action != CGPathNode::BATTLE || (carrier->actor->allowBattle && carrier->specialAction))
&& carrier->action != CGPathNode::BLOCKING_VISIT
&& other->armyLoss < other->actor->armyValue
&& (other->armyLoss == 0 || other->armyLoss < other->actor->armyValue)
&& carrier->actor->canExchange(other->actor))
{
#if AI_TRACE_LEVEL >= 2
@ -632,18 +635,24 @@ void AINodeStorage::setTownsAndDwellings(
{
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));
}
}
/*auto dayOfWeek = cb->getDate(Date::DAY_OF_WEEK);
auto waitForGrowth = dayOfWeek > 4;
auto waitForGrowth = dayOfWeek > 4;*/
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)
{
@ -665,8 +674,8 @@ void AINodeStorage::setTownsAndDwellings(
actors.push_back(dwellingActor);
}
}
}
}*/
}*/
}
}
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.heroArmy = node.actor->creatureSet;
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.chainMask = node.actor->chainMask;
path.exchangeCount = node.actor->actorExchangeCount;

View File

@ -159,13 +159,14 @@ public:
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);
}
bool isDistanceLimitReached(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
{
return heroChainPass == EHeroChainPass::CHAIN && destination.node->turns > heroChainTurn;
}
template<class NodeRange>
bool hasBetterChain(
const CGPathNode * source,
@ -185,9 +186,9 @@ public:
bool calculateHeroChain();
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

View File

@ -16,6 +16,8 @@
#include "../../../lib/mapping/CMap.h"
#include "../../../lib/mapObjects/MapObjects.h"
CCreatureSet emptyArmy;
bool HeroExchangeArmy::needsLastStack() const
{
return true;
@ -56,6 +58,21 @@ std::string ChainActor::toString() const
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)
:ChainActor(hero, chainMask)
{
@ -151,18 +168,38 @@ bool HeroExchangeMap::canExchange(const ChainActor * other)
if(result)
{
if(other->armyCost.nonZero())
{
TResources resources = ai->myCb->getResourceAmount();
TResources resources = ai->myCb->getResourceAmount();
if(!resources.canAfford(actor->armyCost + other->armyCost))
{
result = false;
return;
}
if(!resources.canAfford(actor->armyCost + other->armyCost))
{
result = false;
#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;
}
@ -204,7 +241,27 @@ HeroActor * HeroExchangeMap::exchange(const ChainActor * other)
result = exchangeMap.at(other);
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);
exchangeMap[other] = result;
}
@ -212,6 +269,25 @@ HeroActor * HeroExchangeMap::exchange(const ChainActor * other)
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 * target = new HeroExchangeArmy();
@ -227,8 +303,13 @@ CCreatureSet * HeroExchangeMap::pickBestCreatures(const CCreatureSet * army1, co
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)
:ChainActor(
:ObjectActor(
dwelling,
getDwellingCreatures(dwelling, waitForGrowth),
chainMask,
@ -288,7 +369,7 @@ CCreatureSet * DwellingActor::getDwellingCreatures(const CGDwelling * dwelling,
}
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;
ChainActor * exchange(const ChainActor * other) const { return exchange(this, other); }
void setBaseActor(HeroActor * base);
virtual const CGObjectInstance * getActorObject() const { return hero; }
protected:
virtual ChainActor * exchange(const ChainActor * specialActor, const ChainActor * other) const;
@ -86,6 +87,7 @@ public:
private:
CCreatureSet * pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const;
CCreatureSet * tryUpgrade(const CCreatureSet * army, const CGObjectInstance * upgrader, TResources resources) const;
};
class HeroActor : public ChainActor
@ -112,7 +114,24 @@ protected:
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:
const CGDwelling * dwelling;
@ -127,7 +146,7 @@ protected:
CCreatureSet * getDwellingCreatures(const CGDwelling * dwelling, bool waitForGrowth);
};
class TownGarrisonActor : public ChainActor
class TownGarrisonActor : public ObjectActor
{
private:
const CGTownInstance * town;

View File

@ -47,151 +47,171 @@ namespace AIPathfinding
{
if(nodeStorage->isMovementIneficient(source, destination))
{
destination.node->locked = true;
destination.blocked = true;
return;
}
auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper);
if(blocker == BlockingReason::NONE)
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)
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)
{
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);
auto destGuardians = cb->getGuardingCreatures(destination.coord);
return vstd::contains(srcGuardians, destGuard);
});
if(destGuardians.empty())
{
destination.blocked = true;
auto guardsAlreadyBypassed = destGuardians.empty() && srcGuardians.size();
return;
}
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)
{
if(guardsAlreadyBypassed && srcNode->actor->allowBattle)
{
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Bypass guard at destination while moving %s -> %s",
source.coord.toString(),
destination.coord.toString());
logAi->trace(
"Bypass guard at destination while moving %s -> %s",
source.coord.toString(),
destination.coord.toString());
#endif
return;
}
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;
}
return true;
}
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,
const PathfinderConfig * pathfinderConfig,
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);
}
void makePossibleUpgrades(const CArmedInstance * obj)
bool VCAI::makePossibleUpgrades(const CArmedInstance * obj)
{
if(!obj)
return;
return false;
bool upgraded = false;
for(int i = 0; i < GameConstants::ARMY_SIZE; i++)
{
if(const CStackInstance * s = obj->getStackPtr(SlotID(i)))
{
UpgradeInfo ui;
cb->getUpgradeInfo(obj, SlotID(i), ui);
if(ui.oldID >= 0 && cb->getResourceAmount().canAfford(ui.cost[0] * s->count))
myCb->getUpgradeInfo(obj, SlotID(i), ui);
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);
}
}
}
return upgraded;
}
void VCAI::makeTurn()
@ -2203,12 +2208,15 @@ void VCAI::tryRealize(Goals::BuyArmy & g)
ui64 valueBought = 0;
//buy the stacks with largest AI value
makePossibleUpgrades(t);
auto upgradeSuccessfull = makePossibleUpgrades(t);
auto armyToBuy = ah->getArmyAvailableToBuy(t->getUpperArmy(), t);
if(armyToBuy.empty())
{
if(upgradeSuccessfull)
throw goalFulfilledException(sptr(g));
throw cannotFulfillGoalException("No creatures to buy.");
}

View File

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