1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

NKAI: improve army gathering

This commit is contained in:
Andrii Danylchenko 2023-03-05 15:42:15 +02:00
parent 8b0c7b6601
commit 148c3436df
7 changed files with 106 additions and 44 deletions

View File

@ -590,13 +590,18 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
if(hero.validAndSet() && target.valid() && objects.size())
{
auto objType = objects.back()->ID;
auto topObj = objects.front()->id == hero->id ? objects.back() : objects.front();
auto objType = topObj->ID; // top object should be our hero
auto goalObjectID = nullkiller->getTargetObject();
auto ratio = (float)nullkiller->dangerEvaluator->evaluateDanger(target, hero.get()) / (float)hero->getTotalStrength();
bool dangerUnknown = ratio == 0;
bool dangerTooHigh = ratio > (1 / SAFE_ATTACK_CONSTANT);
answer = objects.back()->id == nullkiller->getTargetObject(); // no if we do not aim to visit this object
answer = topObj->id == goalObjectID; // no if we do not aim to visit this object
logAi->trace("Query hook: %s(%s) by %s danger ratio %f", target.toString(), topObj->getObjectName(), hero.name, ratio);
if(cb->getObj(goalObjectID, false))
{
logAi->trace("AI expected %s", cb->getObj(goalObjectID, false)->getObjectName());
}
if(objType == Obj::BORDERGUARD || objType == Obj::QUEST_GUARD)
{
@ -604,9 +609,10 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
}
else if(objType == Obj::ARTIFACT || objType == Obj::RESOURCE)
{
logAi->trace("Guarded object query hook: %s by %s danger ratio %f", target.toString(), hero.name, ratio);
bool dangerUnknown = ratio == 0;
bool dangerTooHigh = ratio > (1 / SAFE_ATTACK_CONSTANT);
answer = dangerUnknown || dangerTooHigh;
answer = !dangerUnknown && !dangerTooHigh;
}
}

View File

@ -109,7 +109,7 @@ void HeroManager::update()
return scores.at(h1) > scores.at(h2);
};
int globalMainCount = std::min(((int)myHeroes.size() + 2) / 3, cb->getMapSize().x / 100 + 1);
int globalMainCount = std::min(((int)myHeroes.size() + 2) / 3, cb->getMapSize().x / 50 + 1);
std::sort(myHeroes.begin(), myHeroes.end(), scoreSort);

View File

@ -81,7 +81,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
auto hero = path.targetHero;
auto danger = path.getTotalDanger();
if(ai->nullkiller->heroManager->getHeroRole(hero) == HeroRole::SCOUT && danger == 0 && path.exchangeCount > 1)
if(ai->nullkiller->heroManager->getHeroRole(hero) == HeroRole::SCOUT && path.exchangeCount > 1)
continue;
auto firstBlockedAction = path.getFirstBlockedAction();
@ -126,8 +126,13 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
sharedPtr.reset(newWay);
if(!closestWay || closestWay->movementCost() > path.movementCost())
auto heroRole = ai->nullkiller->heroManager->getHeroRole(path.targetHero);
if(heroRole == HeroRole::SCOUT
&& (!closestWay || closestWay->movementCost() > path.movementCost()))
{
closestWay = &path;
}
if(!ai->nullkiller->arePathHeroesLocked(path))
{
@ -137,11 +142,13 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
}
}
assert(closestWay || waysToVisitObj.empty());
for(auto way : waysToVisitObj)
if(closestWay)
{
way->closestWayRatio
= closestWay->movementCost() / way->getPath().movementCost();
for(auto way : waysToVisitObj)
{
way->closestWayRatio
= closestWay->movementCost() / way->getPath().movementCost();
}
}
return tasks;

View File

@ -45,8 +45,7 @@ Goals::TGoalVec GatherArmyBehavior::decompose() const
for(const CGHeroInstance * hero : heroes)
{
if(ai->nullkiller->heroManager->getHeroRole(hero) == HeroRole::MAIN
&& hero->getArmyStrength() >= 300)
if(ai->nullkiller->heroManager->getHeroRole(hero) == HeroRole::MAIN)
{
vstd::concatenate(tasks, deliverArmyToHero(hero));
}
@ -70,13 +69,6 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Checking ways to gaher army for hero %s, %s", hero->getObjectName(), pos.toString());
#endif
if(ai->nullkiller->isHeroLocked(hero))
{
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Skipping locked hero %s, %s", hero->getObjectName(), pos.toString());
#endif
return tasks;
}
auto paths = ai->nullkiller->pathfinder->getPathInfo(pos);
@ -92,6 +84,14 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
if(path.containsHero(hero)) continue;
if(path.turn() == 0 && hero->inTownGarrison)
{
#if NKAI_TRACE_LEVEL >= 1
logAi->trace("Skipping garnisoned hero %s, %s", hero->getObjectName(), pos.toString());
#endif
continue;
}
if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
{
#if NKAI_TRACE_LEVEL >= 2
@ -124,14 +124,32 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
// avoid trying to move bigger army to the weaker one.
if(armyValue > 1)
{
bool hasOtherMainInPath = false;
for(auto node : path.nodes)
{
if(!node.targetHero) continue;
auto heroRole = ai->nullkiller->heroManager->getHeroRole(node.targetHero);
if(heroRole == HeroRole::MAIN)
{
hasOtherMainInPath = true;
break;
}
}
if(hasOtherMainInPath)
{
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Army value is too large.");
logAi->trace("Army value is too large.");
#endif
continue;
continue;
}
}
auto danger = path.getTotalDanger();
auto isSafe = isSafeToVisit(hero, path.heroArmy, danger);
#if NKAI_TRACE_LEVEL >= 2
@ -194,7 +212,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Path found %s", path.toString());
#endif
if(upgrader->visitingHero != path.targetHero)
if(upgrader->visitingHero && upgrader->visitingHero.get() != path.targetHero)
{
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Ignore path. Town has visiting hero.");
@ -219,7 +237,10 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
continue;
}
if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
auto heroRole = ai->nullkiller->heroManager->getHeroRole(path.targetHero);
if(heroRole == HeroRole::SCOUT
&& ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
{
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength());
@ -228,16 +249,22 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
}
auto upgrade = ai->nullkiller->armyManager->calculateCreaturesUpgrade(path.heroArmy, upgrader, availableResources);
auto armyValue = (float)upgrade.upgradeValue / path.getHeroStrength();
if(ai->nullkiller->heroManager->getHeroRole(path.targetHero) == HeroRole::MAIN)
{
upgrade.upgradeValue +=
ai->nullkiller->armyManager->howManyReinforcementsCanGet(path.targetHero, path.heroArmy, upgrader);
upgrade.upgradeValue +=
ai->nullkiller->armyManager->howManyReinforcementsCanGet(path.targetHero, path.heroArmy, upgrader);
}
if(armyValue < 0.1f || upgrade.upgradeValue < 300) // avoid small upgrades
auto armyValue = (float)upgrade.upgradeValue / path.getHeroStrength();
if(armyValue < 0.25f || upgrade.upgradeValue < 300) // avoid small upgrades
{
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Ignore path. Army value is too small (%f)", armyValue);
#endif
continue;
}
auto danger = path.getTotalDanger();

View File

@ -115,8 +115,20 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
{
case Obj::TOWN:
{
const CGTownInstance * cre = dynamic_cast<const CGTownInstance *>(obj);
return cre->getUpperArmy()->getArmyStrength();
const CGTownInstance * town = dynamic_cast<const CGTownInstance *>(obj);
auto danger = town->getUpperArmy()->getArmyStrength();
if(danger || town->visitingHero)
{
auto fortLevel = town->fortLevel();
if(fortLevel == CGTownInstance::EFortLevel::CASTLE)
danger += 10000;
else if(fortLevel == CGTownInstance::EFortLevel::CITADEL)
danger += 4000;
}
return danger;
}
case Obj::ARTIFACT:
case Obj::RESOURCE:

View File

@ -222,7 +222,7 @@ void Nullkiller::makeTurn()
boost::lock_guard<boost::mutex> sharedStorageLock(AISharedStorage::locker);
const int MAX_DEPTH = 10;
const int FAST_TASK_MINIMAL_PRIORITY = 0.7;
const float FAST_TASK_MINIMAL_PRIORITY = 0.7;
resetAiState();

View File

@ -81,6 +81,12 @@ void PriorityEvaluator::initVisitTile()
value = engine->getOutputVariable("Value");
}
bool isAnotherAi(const CGObjectInstance * obj, const CPlayerSpecificInfoCallback & cb)
{
return obj->getOwner().isValidPlayer()
&& cb.getStartInfo()->getIthPlayersSettings(obj->getOwner()).isControlledByAI();
}
int32_t estimateTownIncome(CCallback * cb, const CGObjectInstance * target, const CGHeroInstance * hero)
{
auto relations = cb->getPlayerRelations(hero->tempOwner, target->tempOwner);
@ -88,15 +94,17 @@ int32_t estimateTownIncome(CCallback * cb, const CGObjectInstance * target, cons
if(relations != PlayerRelations::ENEMIES)
return 0; // if we already own it, no additional reward will be received by just visiting it
auto booster = isAnotherAi(target, *cb) ? 1 : 2;
auto town = cb->getTown(target->id);
auto fortLevel = town->fortLevel();
if(town->hasCapitol()) return 4000;
if(town->hasCapitol()) return booster * 2000;
// probably well developed town will have city hall
if(fortLevel == CGTownInstance::CASTLE) return 1500;
if(fortLevel == CGTownInstance::CASTLE) return booster * 750;
return town->hasFort() && town->tempOwner != PlayerColor::NEUTRAL ? 1000 : 500;
return booster * (town->hasFort() && town->tempOwner != PlayerColor::NEUTRAL ? booster * 500 : 250);
}
TResources getCreatureBankResources(const CGObjectInstance * target, const CGHeroInstance * hero)
@ -247,11 +255,12 @@ uint64_t RewardEvaluator::getArmyReward(
{
auto town = dynamic_cast<const CGTownInstance *>(target);
auto fortLevel = town->fortLevel();
auto booster = isAnotherAi(town, *ai->cb) ? 1 : 2;
if(fortLevel < CGTownInstance::CITADEL)
return town->hasFort() ? 1000 : 0;
return town->hasFort() ? booster * 500 : 0;
else
return fortLevel == CGTownInstance::CASTLE ? 10000 : 4000;
return booster * (fortLevel == CGTownInstance::CASTLE ? 5000 : 2000);
}
case Obj::HILL_FORT:
@ -395,13 +404,14 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
auto town = dynamic_cast<const CGTownInstance *>(target);
auto fortLevel = town->fortLevel();
auto booster = isAnotherAi(town, *ai->cb) ? 0.3 : 1;
if(town->hasCapitol()) return 1;
if(fortLevel < CGTownInstance::CITADEL)
return town->hasFort() ? 0.6 : 0.4;
return booster * (town->hasFort() ? 0.6 : 0.4);
else
return fortLevel == CGTownInstance::CASTLE ? 0.9 : 0.8;
return booster * (fortLevel == CGTownInstance::CASTLE ? 0.9 : 0.8);
}
case Obj::HERO:
@ -579,6 +589,7 @@ public:
uint64_t upgradeValue = armyUpgrade.getUpgradeValue();
evaluationContext.armyReward += upgradeValue;
evaluationContext.strategicalValue += upgradeValue / armyUpgrade.hero->getTotalStrength();
}
};
@ -590,8 +601,7 @@ void addTileDanger(EvaluationContext & evaluationContext, const int3 & tile, uin
{
auto dangerRatio = enemyDanger.danger / (double)ourStrength;
auto enemyHero = evaluationContext.evaluator.ai->cb->getObj(enemyDanger.hero.hid, false);
bool isAI =enemyHero
&& evaluationContext.evaluator.ai->cb->getStartInfo()->getIthPlayersSettings(enemyHero->getOwner()).isControlledByAI();
bool isAI = enemyHero && isAnotherAi(enemyHero, *evaluationContext.evaluator.ai->cb);
if(isAI)
{