1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +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);
}
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
{
return heroManager->selectBestSkill(hero, skills);

View File

@ -82,6 +82,10 @@ public:
std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const override;
uint64_t evaluateStackPower(const CCreature * creature, int count) 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;
HeroRole getHeroRole(const HeroPtr & hero) const override;

View File

@ -207,4 +207,144 @@ void ArmyManager::update()
army.second.creature = army.first.toCreature();
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;
};
struct ArmyUpgradeInfo
{
std::vector<SlotInfo> resultingArmy;
uint64_t upgradeValue;
TResources upgradeCost;
ArmyUpgradeInfo()
: resultingArmy(), upgradeValue(0), upgradeCost()
{
}
};
class DLL_EXPORT IArmyManager //: public: IAbstractManager
{
public:
@ -40,8 +52,14 @@ public:
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 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
{
private:
@ -63,4 +81,14 @@ public:
std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const override;
uint64_t evaluateStackPower(const CCreature * creature, int count) 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,8 +172,13 @@ Goals::TGoalVec StartupBehavior::getTasks()
{
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)));
}
}
}

View File

@ -22,6 +22,7 @@
#include "../VCAI.h"
#include "../AIhelper.h"
#include "../Engine/Nullkiller.h"
#include "../Goals/ExecuteHeroChain.h"
#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
@ -153,7 +154,7 @@ uint64_t evaluateArtifactArmyValue(CArtifactInstance * art)
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;
@ -164,6 +165,8 @@ uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * h
{
case Obj::TOWN:
return target->tempOwner == PlayerColor::NEUTRAL ? 1000 : 10000;
case Obj::HILL_FORT:
return ai->ah->calculateCreateresUpgrade(army, target, cb->getResourceAmount()).upgradeValue;
case Obj::CREATURE_BANK:
return getCreatureBankArmyReward(target, hero);
case Obj::CREATURE_GENERATOR1:
@ -377,15 +380,11 @@ class ExecuteHeroChainEvaluationContextBuilder : public IEvaluationContextBuilde
public:
virtual Goals::EvaluationContext buildEvaluationContext(Goals::TSubgoal task) const override
{
Goals::ExecuteHeroChain & chain = dynamic_cast<Goals::ExecuteHeroChain &>(*task);
auto evaluationContext = task->evaluationContext;
int objId = task->objid;
if(task->parent)
objId = task->parent->objid;
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 hero = heroPtr.get();
bool checkGold = evaluationContext.danger == 0;
@ -393,7 +392,7 @@ public:
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, checkGold);
evaluationContext.armyReward = getArmyReward(target, hero, chain.getPath().heroArmy, checkGold);
evaluationContext.skillReward = getSkillReward(target, hero, evaluationContext.heroRole);
evaluationContext.strategicalValue = getStrategicalValue(target);

View File

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

View File

@ -279,7 +279,8 @@ bool AINodeStorage::calculateHeroChainFinal()
{
if(node.turns > heroChainTurn
&& node.action != CGPathNode::ENodeAction::UNKNOWN
&& node.actor->actorExchangeCount > 1)
&& node.actor->actorExchangeCount > 1
&& !hasBetterChain(&node, &node, chains))
{
heroChain.push_back(&node);
}
@ -332,6 +333,55 @@ bool AINodeStorage::calculateHeroChain()
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
{
vstd::erase_if(result, [&](const ExchangeCandidate & chainInfo) -> bool
@ -354,6 +404,9 @@ void AINodeStorage::calculateHeroChain(
if(node == srcNode || !node->actor)
continue;
if((node->actor->chainMask & chainMask) == 0 && (srcNode->actor->chainMask & chainMask) == 0)
continue;
if(node->turns > heroChainTurn
|| (node->action == CGPathNode::ENodeAction::UNKNOWN && node->actor->hero)
|| (node->actor->chainMask & srcNode->actor->chainMask) != 0)

View File

@ -112,6 +112,7 @@ private:
std::vector<std::shared_ptr<ChainActor>> actors;
std::vector<CGPathNode *> heroChain;
EHeroChainPass heroChainPass; // true if we need to calculate hero chain
uint64_t chainMask;
int heroChainTurn;
int heroChainMaxTurns;
PlayerColor playerID;
@ -126,6 +127,8 @@ public:
void initialize(const PathfinderOptions & options, const CGameState * gs) override;
bool increaseHeroChainTurnLimit();
bool selectFirstActor();
bool selectNextActor();
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);
bool continueCalculation = false;
logAi->trace("Recalculate paths pass %d", pass++);
cb->calculatePaths(config);
if(!useHeroChain)
return;
do
{
storage->selectFirstActor();
do
{
logAi->trace("Recalculate paths pass %d", pass++);
cb->calculatePaths(config);
} while(useHeroChain && storage->calculateHeroChain());
while(storage->calculateHeroChain())
{
logAi->trace("Recalculate paths pass %d", pass++);
cb->calculatePaths(config);
}
if(!useHeroChain)
break;
logAi->trace("Select next actor");
} while(storage->selectNextActor());
if(storage->calculateHeroChainFinal())
{
logAi->trace("Recalculate paths pass final");
cb->calculatePaths(config);
}
continueCalculation = storage->increaseHeroChainTurnLimit() && storage->calculateHeroChain();
} while(continueCalculation);
} while(storage->increaseHeroChainTurnLimit());
}

View File

@ -456,11 +456,6 @@ void VCAI::showHillFortWindow(const CGObjectInstance * object, const CGHeroInsta
{
LOG_TRACE(logAi);
NET_EVENT_HANDLER;
requestActionASAP([=]()
{
makePossibleUpgrades(visitor);
});
}
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))
{
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;
case Obj::HILL_FORT:
makePossibleUpgrades(h.get());
break;
}
completeGoal(sptr(Goals::VisitObj(obj->id.getNum()).sethero(h)));
}