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:
parent
400967904b
commit
a39fa51e14
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
};
|
||||
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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:
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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)));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user