1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-04-23 12:08:45 +02:00

Nullkiller AI: basic hill fort support and hero chain reworked to start from stronger army

This commit is contained in:
Andrii Danylchenko 2021-05-16 14:15:12 +03:00 committed by Andrii Danylchenko
parent 400967904b
commit a39fa51e14
11 changed files with 275 additions and 44 deletions

View File

@ -213,6 +213,14 @@ std::vector<creInfo> AIhelper::getArmyAvailableToBuy(const CCreatureSet * hero,
return armyManager->getArmyAvailableToBuy(hero, dwelling); return armyManager->getArmyAvailableToBuy(hero, dwelling);
} }
ArmyUpgradeInfo AIhelper::calculateCreateresUpgrade(
const CCreatureSet * army,
const CGObjectInstance * upgrader,
const TResources & availableResources) const
{
return armyManager->calculateCreateresUpgrade(army, upgrader, availableResources);
}
int AIhelper::selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const int AIhelper::selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const
{ {
return heroManager->selectBestSkill(hero, skills); return heroManager->selectBestSkill(hero, skills);

View File

@ -82,6 +82,10 @@ public:
std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const override; std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const override;
uint64_t evaluateStackPower(const CCreature * creature, int count) const override; uint64_t evaluateStackPower(const CCreature * creature, int count) const override;
SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const override; SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const override;
ArmyUpgradeInfo calculateCreateresUpgrade(
const CCreatureSet * army,
const CGObjectInstance * upgrader,
const TResources & availableResources) const override;
const std::map<HeroPtr, HeroRole> & getHeroRoles() const override; const std::map<HeroPtr, HeroRole> & getHeroRoles() const override;
HeroRole getHeroRole(const HeroPtr & hero) const override; HeroRole getHeroRole(const HeroPtr & hero) const override;

View File

@ -208,3 +208,143 @@ void ArmyManager::update()
army.second.power = evaluateStackPower(army.second.creature, army.second.count); army.second.power = evaluateStackPower(army.second.creature, army.second.count);
} }
} }
struct UpgradeInfo
{
const CCreature * initialCreature;
const CCreature * upgradedCreature;
TResources cost;
int count;
uint64_t upgradeValue;
UpgradeInfo(CreatureID initial, CreatureID upgraded, int count)
:initialCreature(initial.toCreature()), upgradedCreature(upgraded.toCreature()), count(count)
{
cost = (upgradedCreature->cost - initialCreature->cost) * count;
upgradeValue = (upgradedCreature->AIValue - initialCreature->AIValue) * count;
}
};
std::vector<SlotInfo> ArmyManager::convertToSlots(const CCreatureSet * army) const
{
std::vector<SlotInfo> result;
for(auto slot : army->Slots())
{
SlotInfo slotInfo;
slotInfo.creature = slot.second->getCreatureID().toCreature();
slotInfo.count = slot.second->count;
slotInfo.power = evaluateStackPower(slotInfo.creature, slotInfo.count);
result.push_back(slotInfo);
}
return result;
}
std::vector<UpgradeInfo> ArmyManager::getHillFortUpgrades(const CCreatureSet * army) const
{
std::vector<UpgradeInfo> upgrades;
for(auto creature : army->Slots())
{
CreatureID initial = creature.second->getCreatureID();
auto possibleUpgrades = initial.toCreature()->upgrades;
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);
if(initial.toCreature()->level == 1)
upgrade.cost = TResources();
upgrades.push_back(upgrade);
}
return upgrades;
}
std::vector<UpgradeInfo> ArmyManager::getDwellingUpgrades(const CCreatureSet * army, const CGDwelling * dwelling) const
{
std::vector<UpgradeInfo> upgrades;
return upgrades;
}
std::vector<UpgradeInfo> ArmyManager::getPossibleUpgrades(const CCreatureSet * army, const CGObjectInstance * upgrader) const
{
std::vector<UpgradeInfo> upgrades;
if(upgrader->ID == Obj::HILL_FORT)
{
upgrades = getHillFortUpgrades(army);
}
else
{
auto dwelling = dynamic_cast<const CGDwelling *>(upgrader);
if(dwelling)
{
upgrades = getDwellingUpgrades(army, dwelling);
}
}
return upgrades;
}
ArmyUpgradeInfo ArmyManager::calculateCreateresUpgrade(
const CCreatureSet * army,
const CGObjectInstance * upgrader,
const TResources & availableResources) const
{
std::vector<UpgradeInfo> upgrades = getPossibleUpgrades(army, upgrader);
vstd::erase_if(upgrades, [&](const UpgradeInfo & u) -> bool
{
return !availableResources.canAfford(u.cost);
});
if(upgrades.empty())
return ArmyUpgradeInfo();
std::sort(upgrades.begin(), upgrades.end(), [](const UpgradeInfo & u1, const UpgradeInfo & u2) -> bool
{
return u1.upgradeValue > u2.upgradeValue;
});
TResources resourcesLeft = availableResources;
ArmyUpgradeInfo result;
result.resultingArmy = convertToSlots(army);
for(auto upgrade : upgrades)
{
if(resourcesLeft.canAfford(upgrade.cost))
{
SlotInfo upgradedArmy;
upgradedArmy.creature = upgrade.upgradedCreature;
upgradedArmy.count = upgrade.count;
upgradedArmy.power = evaluateStackPower(upgradedArmy.creature, upgradedArmy.count);
auto slotToReplace = std::find_if(result.resultingArmy.begin(), result.resultingArmy.end(), [&](const SlotInfo & slot) -> bool {
return slot.count == upgradedArmy.count && slot.creature == upgrade.initialCreature;
});
resourcesLeft -= upgrade.cost;
result.upgradeCost += upgrade.cost;
result.upgradeValue += upgrade.upgradeValue;
*slotToReplace = upgradedArmy;
}
}
return result;
}

View File

@ -25,6 +25,18 @@ struct SlotInfo
uint64_t power; uint64_t power;
}; };
struct ArmyUpgradeInfo
{
std::vector<SlotInfo> resultingArmy;
uint64_t upgradeValue;
TResources upgradeCost;
ArmyUpgradeInfo()
: resultingArmy(), upgradeValue(0), upgradeCost()
{
}
};
class DLL_EXPORT IArmyManager //: public: IAbstractManager class DLL_EXPORT IArmyManager //: public: IAbstractManager
{ {
public: public:
@ -40,8 +52,14 @@ public:
virtual std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const = 0; virtual std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const = 0;
virtual uint64_t evaluateStackPower(const CCreature * creature, int count) const = 0; virtual uint64_t evaluateStackPower(const CCreature * creature, int count) const = 0;
virtual SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const = 0; virtual SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const = 0;
virtual ArmyUpgradeInfo calculateCreateresUpgrade(
const CCreatureSet * army,
const CGObjectInstance * upgrader,
const TResources & availableResources) const = 0;
}; };
struct UpgradeInfo;
class DLL_EXPORT ArmyManager : public IArmyManager class DLL_EXPORT ArmyManager : public IArmyManager
{ {
private: private:
@ -63,4 +81,14 @@ public:
std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const override; std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const override;
uint64_t evaluateStackPower(const CCreature * creature, int count) const override; uint64_t evaluateStackPower(const CCreature * creature, int count) const override;
SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const override; SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const override;
ArmyUpgradeInfo calculateCreateresUpgrade(
const CCreatureSet * army,
const CGObjectInstance * upgrader,
const TResources & availableResources) const override;
private:
std::vector<SlotInfo> convertToSlots(const CCreatureSet * army) const;
std::vector<UpgradeInfo> getPossibleUpgrades(const CCreatureSet * army, const CGObjectInstance * upgrader) const;
std::vector<UpgradeInfo> getHillFortUpgrades(const CCreatureSet * army) const;
std::vector<UpgradeInfo> getDwellingUpgrades(const CCreatureSet * army, const CGDwelling * dwelling) const;
}; };

View File

@ -172,10 +172,15 @@ Goals::TGoalVec StartupBehavior::getTasks()
{ {
for(const CGTownInstance * town : towns) for(const CGTownInstance * town : towns)
{ {
if(town->garrisonHero && town->garrisonHero->movement && ai->nullkiller->getHeroLockedReason(town->garrisonHero) != HeroLockedReason::DEFENCE) if(town->garrisonHero
&& town->garrisonHero->movement
&& !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(0.0001f)));
} }
} }
}
return tasks; return tasks;
} }

View File

@ -22,6 +22,7 @@
#include "../VCAI.h" #include "../VCAI.h"
#include "../AIhelper.h" #include "../AIhelper.h"
#include "../Engine/Nullkiller.h" #include "../Engine/Nullkiller.h"
#include "../Goals/ExecuteHeroChain.h"
#define MIN_AI_STRENGHT (0.5f) //lower when combat AI gets smarter #define MIN_AI_STRENGHT (0.5f) //lower when combat AI gets smarter
#define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us #define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
@ -153,7 +154,7 @@ uint64_t evaluateArtifactArmyValue(CArtifactInstance * art)
return statsValue > classValue ? statsValue : classValue; return statsValue > classValue ? statsValue : classValue;
} }
uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero, bool checkGold) uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero, const CCreatureSet * army, bool checkGold)
{ {
const float enemyArmyEliminationRewardRatio = 0.5f; const float enemyArmyEliminationRewardRatio = 0.5f;
@ -164,6 +165,8 @@ uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * h
{ {
case Obj::TOWN: case Obj::TOWN:
return target->tempOwner == PlayerColor::NEUTRAL ? 1000 : 10000; return target->tempOwner == PlayerColor::NEUTRAL ? 1000 : 10000;
case Obj::HILL_FORT:
return ai->ah->calculateCreateresUpgrade(army, target, cb->getResourceAmount()).upgradeValue;
case Obj::CREATURE_BANK: case Obj::CREATURE_BANK:
return getCreatureBankArmyReward(target, hero); return getCreatureBankArmyReward(target, hero);
case Obj::CREATURE_GENERATOR1: case Obj::CREATURE_GENERATOR1:
@ -377,15 +380,11 @@ class ExecuteHeroChainEvaluationContextBuilder : public IEvaluationContextBuilde
public: public:
virtual Goals::EvaluationContext buildEvaluationContext(Goals::TSubgoal task) const override virtual Goals::EvaluationContext buildEvaluationContext(Goals::TSubgoal task) const override
{ {
Goals::ExecuteHeroChain & chain = dynamic_cast<Goals::ExecuteHeroChain &>(*task);
auto evaluationContext = task->evaluationContext; auto evaluationContext = task->evaluationContext;
int objId = task->objid;
if(task->parent)
objId = task->parent->objid;
auto heroPtr = task->hero; auto heroPtr = task->hero;
const CGObjectInstance * target = cb->getObj((ObjectInstanceID)objId, false); const CGObjectInstance * target = cb->getObj((ObjectInstanceID)task->objid, false);
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;
@ -393,7 +392,7 @@ public:
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, checkGold); evaluationContext.armyReward = getArmyReward(target, hero, chain.getPath().heroArmy, checkGold);
evaluationContext.skillReward = getSkillReward(target, hero, evaluationContext.heroRole); evaluationContext.skillReward = getSkillReward(target, hero, evaluationContext.heroRole);
evaluationContext.strategicalValue = getStrategicalValue(target); evaluationContext.strategicalValue = getStrategicalValue(target);

View File

@ -281,32 +281,17 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj, const VCAI * ai)
switch(obj->ID) switch(obj->ID)
{ {
case Obj::HERO:
{
InfoAboutHero iah;
cb->getHeroInfo(obj, iah);
return iah.army.getStrength();
}
case Obj::TOWN: case Obj::TOWN:
case Obj::GARRISON:
case Obj::GARRISON2:
{ {
InfoAboutTown iat; const CGTownInstance * cre = dynamic_cast<const CGTownInstance *>(obj);
cb->getTownInfo(obj, iat); return cre->getUpperArmy()->getArmyStrength();
return iat.army.getStrength();
} }
case Obj::MONSTER: case Obj::MONSTER:
{ case Obj::HERO:
//TODO!!!!!!!! case Obj::GARRISON:
const CGCreature * cre = dynamic_cast<const CGCreature *>(obj); case Obj::GARRISON2:
return cre->getArmyStrength();
}
case Obj::CREATURE_GENERATOR1: case Obj::CREATURE_GENERATOR1:
case Obj::CREATURE_GENERATOR4: case Obj::CREATURE_GENERATOR4:
{
const CGDwelling * d = dynamic_cast<const CGDwelling *>(obj);
return d->getArmyStrength();
}
case Obj::MINE: case Obj::MINE:
case Obj::ABANDONED_MINE: case Obj::ABANDONED_MINE:
{ {

View File

@ -279,7 +279,8 @@ bool AINodeStorage::calculateHeroChainFinal()
{ {
if(node.turns > heroChainTurn if(node.turns > heroChainTurn
&& node.action != CGPathNode::ENodeAction::UNKNOWN && node.action != CGPathNode::ENodeAction::UNKNOWN
&& node.actor->actorExchangeCount > 1) && node.actor->actorExchangeCount > 1
&& !hasBetterChain(&node, &node, chains))
{ {
heroChain.push_back(&node); heroChain.push_back(&node);
} }
@ -332,6 +333,55 @@ bool AINodeStorage::calculateHeroChain()
return heroChain.size(); return heroChain.size();
} }
bool AINodeStorage::selectFirstActor()
{
if(!actors.size())
return false;
auto strongest = *vstd::maxElementByFun(actors, [](std::shared_ptr<ChainActor> actor) -> uint64_t
{
return actor->armyValue;
});
chainMask = strongest->chainMask;
return true;
}
bool AINodeStorage::selectNextActor()
{
auto currentActor = std::find_if(actors.begin(), actors.end(), [&](std::shared_ptr<ChainActor> actor)-> bool
{
return actor->chainMask == chainMask;
});
auto nextActor = actors.end();
for(auto actor = actors.begin(); actor != actors.end(); actor++)
{
if(actor->get()->armyValue > currentActor->get()->armyValue
|| actor->get()->armyValue == currentActor->get()->armyValue && actor <= currentActor)
{
continue;
}
if(nextActor == actors.end()
|| actor->get()->armyValue > nextActor->get()->armyValue)
{
nextActor = actor;
}
}
if(nextActor != actors.end())
{
chainMask = nextActor->get()->chainMask;
return true;
}
return false;
}
void AINodeStorage::cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const void AINodeStorage::cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const
{ {
vstd::erase_if(result, [&](const ExchangeCandidate & chainInfo) -> bool vstd::erase_if(result, [&](const ExchangeCandidate & chainInfo) -> bool
@ -354,6 +404,9 @@ void AINodeStorage::calculateHeroChain(
if(node == srcNode || !node->actor) if(node == srcNode || !node->actor)
continue; continue;
if((node->actor->chainMask & chainMask) == 0 && (srcNode->actor->chainMask & chainMask) == 0)
continue;
if(node->turns > heroChainTurn if(node->turns > heroChainTurn
|| (node->action == CGPathNode::ENodeAction::UNKNOWN && node->actor->hero) || (node->action == CGPathNode::ENodeAction::UNKNOWN && node->actor->hero)
|| (node->actor->chainMask & srcNode->actor->chainMask) != 0) || (node->actor->chainMask & srcNode->actor->chainMask) != 0)

View File

@ -112,6 +112,7 @@ private:
std::vector<std::shared_ptr<ChainActor>> actors; std::vector<std::shared_ptr<ChainActor>> actors;
std::vector<CGPathNode *> heroChain; std::vector<CGPathNode *> heroChain;
EHeroChainPass heroChainPass; // true if we need to calculate hero chain EHeroChainPass heroChainPass; // true if we need to calculate hero chain
uint64_t chainMask;
int heroChainTurn; int heroChainTurn;
int heroChainMaxTurns; int heroChainMaxTurns;
PlayerColor playerID; PlayerColor playerID;
@ -126,6 +127,8 @@ public:
void initialize(const PathfinderOptions & options, const CGameState * gs) override; void initialize(const PathfinderOptions & options, const CGameState * gs) override;
bool increaseHeroChainTurnLimit(); bool increaseHeroChainTurnLimit();
bool selectFirstActor();
bool selectNextActor();
virtual std::vector<CGPathNode *> getInitialNodes() override; virtual std::vector<CGPathNode *> getInitialNodes() override;

View File

@ -62,25 +62,32 @@ void AIPathfinder::updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain)
} }
auto config = std::make_shared<AIPathfinding::AIPathfinderConfig>(cb, ai, storage); auto config = std::make_shared<AIPathfinding::AIPathfinderConfig>(cb, ai, storage);
bool continueCalculation = false;
logAi->trace("Recalculate paths pass %d", pass++);
cb->calculatePaths(config);
if(!useHeroChain)
return;
do do
{ {
storage->selectFirstActor();
do do
{
while(storage->calculateHeroChain())
{ {
logAi->trace("Recalculate paths pass %d", pass++); logAi->trace("Recalculate paths pass %d", pass++);
cb->calculatePaths(config); cb->calculatePaths(config);
} while(useHeroChain && storage->calculateHeroChain()); }
if(!useHeroChain) logAi->trace("Select next actor");
break; } while(storage->selectNextActor());
if(storage->calculateHeroChainFinal()) if(storage->calculateHeroChainFinal())
{ {
logAi->trace("Recalculate paths pass final"); logAi->trace("Recalculate paths pass final");
cb->calculatePaths(config); cb->calculatePaths(config);
} }
} while(storage->increaseHeroChainTurnLimit());
continueCalculation = storage->increaseHeroChainTurnLimit() && storage->calculateHeroChain();
} while(continueCalculation);
} }

View File

@ -456,11 +456,6 @@ void VCAI::showHillFortWindow(const CGObjectInstance * object, const CGHeroInsta
{ {
LOG_TRACE(logAi); LOG_TRACE(logAi);
NET_EVENT_HANDLER; NET_EVENT_HANDLER;
requestActionASAP([=]()
{
makePossibleUpgrades(visitor);
});
} }
void VCAI::playerBonusChanged(const Bonus & bonus, bool gain) void VCAI::playerBonusChanged(const Bonus & bonus, bool gain)
@ -781,6 +776,7 @@ void makePossibleUpgrades(const CArmedInstance * obj)
if(ui.oldID >= 0 && cb->getResourceAmount().canAfford(ui.cost[0] * s->count)) if(ui.oldID >= 0 && cb->getResourceAmount().canAfford(ui.cost[0] * s->count))
{ {
cb->upgradeCreature(obj, SlotID(i), ui.newID[0]); cb->upgradeCreature(obj, SlotID(i), ui.newID[0]);
logAi->debug("Upgraded %d %s to %s", s->count, ui.oldID.toCreature()->namePl, ui.newID[0].toCreature()->namePl);
} }
} }
} }
@ -1092,6 +1088,9 @@ void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
} }
} }
break; break;
case Obj::HILL_FORT:
makePossibleUpgrades(h.get());
break;
} }
completeGoal(sptr(Goals::VisitObj(obj->id.getNum()).sethero(h))); completeGoal(sptr(Goals::VisitObj(obj->id.getNum()).sethero(h)));
} }