1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-12 02:28:11 +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()) 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(); 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) 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) 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); 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); 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 hero = path.targetHero;
auto danger = path.getTotalDanger(); 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; continue;
auto firstBlockedAction = path.getFirstBlockedAction(); auto firstBlockedAction = path.getFirstBlockedAction();
@ -126,8 +126,13 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
sharedPtr.reset(newWay); 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; closestWay = &path;
}
if(!ai->nullkiller->arePathHeroesLocked(path)) if(!ai->nullkiller->arePathHeroesLocked(path))
{ {
@ -137,11 +142,13 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
} }
} }
assert(closestWay || waysToVisitObj.empty()); if(closestWay)
for(auto way : waysToVisitObj)
{ {
way->closestWayRatio for(auto way : waysToVisitObj)
= closestWay->movementCost() / way->getPath().movementCost(); {
way->closestWayRatio
= closestWay->movementCost() / way->getPath().movementCost();
}
} }
return tasks; return tasks;

View File

@ -45,8 +45,7 @@ Goals::TGoalVec GatherArmyBehavior::decompose() const
for(const CGHeroInstance * hero : heroes) for(const CGHeroInstance * hero : heroes)
{ {
if(ai->nullkiller->heroManager->getHeroRole(hero) == HeroRole::MAIN if(ai->nullkiller->heroManager->getHeroRole(hero) == HeroRole::MAIN)
&& hero->getArmyStrength() >= 300)
{ {
vstd::concatenate(tasks, deliverArmyToHero(hero)); vstd::concatenate(tasks, deliverArmyToHero(hero));
} }
@ -70,13 +69,6 @@ Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * her
#if NKAI_TRACE_LEVEL >= 1 #if NKAI_TRACE_LEVEL >= 1
logAi->trace("Checking ways to gaher army for hero %s, %s", hero->getObjectName(), pos.toString()); logAi->trace("Checking ways to gaher army for hero %s, %s", hero->getObjectName(), pos.toString());
#endif #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); 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.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(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
{ {
#if NKAI_TRACE_LEVEL >= 2 #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. // avoid trying to move bigger army to the weaker one.
if(armyValue > 1) 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 #if NKAI_TRACE_LEVEL >= 2
logAi->trace("Army value is too large."); logAi->trace("Army value is too large.");
#endif #endif
continue; continue;
}
} }
auto danger = path.getTotalDanger(); auto danger = path.getTotalDanger();
auto isSafe = isSafeToVisit(hero, path.heroArmy, danger); auto isSafe = isSafeToVisit(hero, path.heroArmy, danger);
#if NKAI_TRACE_LEVEL >= 2 #if NKAI_TRACE_LEVEL >= 2
@ -194,7 +212,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
#if NKAI_TRACE_LEVEL >= 2 #if NKAI_TRACE_LEVEL >= 2
logAi->trace("Path found %s", path.toString()); logAi->trace("Path found %s", path.toString());
#endif #endif
if(upgrader->visitingHero != path.targetHero) if(upgrader->visitingHero && upgrader->visitingHero.get() != path.targetHero)
{ {
#if NKAI_TRACE_LEVEL >= 2 #if NKAI_TRACE_LEVEL >= 2
logAi->trace("Ignore path. Town has visiting hero."); logAi->trace("Ignore path. Town has visiting hero.");
@ -219,7 +237,10 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
continue; 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 #if NKAI_TRACE_LEVEL >= 2
logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %lld", path.heroArmy->getArmyStrength()); 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 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) if(ai->nullkiller->heroManager->getHeroRole(path.targetHero) == HeroRole::MAIN)
{ {
upgrade.upgradeValue += upgrade.upgradeValue +=
ai->nullkiller->armyManager->howManyReinforcementsCanGet(path.targetHero, path.heroArmy, upgrader); 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; continue;
}
auto danger = path.getTotalDanger(); auto danger = path.getTotalDanger();

View File

@ -115,8 +115,20 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
{ {
case Obj::TOWN: case Obj::TOWN:
{ {
const CGTownInstance * cre = dynamic_cast<const CGTownInstance *>(obj); const CGTownInstance * town = dynamic_cast<const CGTownInstance *>(obj);
return cre->getUpperArmy()->getArmyStrength(); 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::ARTIFACT:
case Obj::RESOURCE: case Obj::RESOURCE:

View File

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

View File

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