1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-02-03 13:01:33 +02:00

Merge pull request #1657 from vcmi/nkai-fix-build

NKAI: improve build behavior
This commit is contained in:
Andrii Danylchenko 2023-03-13 19:56:49 +02:00 committed by GitHub
commit 6c693f2920
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 82 additions and 49 deletions

View File

@ -1301,7 +1301,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
if(path.nodes[i - 1].turns)
{
//blockedHeroes.insert(h); //to avoid attempts of moving heroes with very little MPs
break;
return false;
}
int3 endpos = path.nodes[i - 1].coord;

View File

@ -58,7 +58,7 @@ std::vector<SlotInfo> ArmyManager::getSortedSlots(const CCreatureSet * target, c
}
}
for(auto pair : creToPower)
for(auto & pair : creToPower)
resultingArmy.push_back(pair.second);
boost::sort(resultingArmy, [](const SlotInfo & left, const SlotInfo & right) -> bool
@ -112,7 +112,7 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
for(auto bonus : *bonusModifiers)
{
// army bonuses will change and object bonuses are temporary
if(bonus->source != Bonus::ARMY || bonus->source != Bonus::OBJECT)
if(bonus->source != Bonus::ARMY && bonus->source != Bonus::OBJECT)
{
newArmyInstance.addNewBonus(std::make_shared<Bonus>(*bonus));
}
@ -182,8 +182,10 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
{
auto weakest = getWeakestCreature(resultingArmy);
if(weakest != resultingArmy.end() && weakest->count == 1) //we check iterator validity for playing with settings that allow 0 stacks armies
if(weakest->count == 1)
{
assert(resultingArmy.size() > 1);
resultingArmy.erase(weakest);
}
else

View File

@ -111,6 +111,12 @@ void HeroManager::update()
int globalMainCount = std::min(((int)myHeroes.size() + 2) / 3, cb->getMapSize().x / 50 + 1);
//vstd::amin(globalMainCount, 1 + (cb->getTownsInfo().size() / 3));
if(cb->getTownsInfo().size() < 4 && globalMainCount > 2)
{
globalMainCount = 2;
}
std::sort(myHeroes.begin(), myHeroes.end(), scoreSort);
for(auto hero : myHeroes)

View File

@ -250,10 +250,13 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
auto upgrade = ai->nullkiller->armyManager->calculateCreaturesUpgrade(path.heroArmy, upgrader, availableResources);
if(ai->nullkiller->heroManager->getHeroRole(path.targetHero) == HeroRole::MAIN)
if(!upgrader->garrisonHero && ai->nullkiller->heroManager->getHeroRole(path.targetHero) == HeroRole::MAIN)
{
upgrade.upgradeValue +=
ai->nullkiller->armyManager->howManyReinforcementsCanGet(path.targetHero, path.heroArmy, upgrader);
ai->nullkiller->armyManager->howManyReinforcementsCanGet(
path.targetHero,
path.heroArmy,
upgrader->getUpperArmy());
}
auto armyValue = (float)upgrade.upgradeValue / path.getHeroStrength();

View File

@ -70,10 +70,7 @@ bool needToRecruitHero(const CGTownInstance * startupTown)
return false;
if(!startupTown->garrisonHero && !startupTown->visitingHero)
return false;
auto heroToCheck = startupTown->garrisonHero ? startupTown->garrisonHero.get() : startupTown->visitingHero.get();
auto paths = cb->getPathsInfo(heroToCheck);
return true;
int treasureSourcesCount = 0;
@ -84,18 +81,16 @@ bool needToRecruitHero(const CGTownInstance * startupTown)
|| obj->ID == Obj::CAMPFIRE
|| obj->ID == Obj::WATER_WHEEL)
{
auto path = paths->getPathInfo(obj->visitablePos());
if((path->accessible == CGPathNode::BLOCKVIS || path->accessible == CGPathNode::VISITABLE)
&& path->reachable())
{
treasureSourcesCount++;
}
treasureSourcesCount++;
}
}
auto basicCount = cb->getTownsInfo().size() + 2;
auto boost = (int)std::floor(std::pow(treasureSourcesCount / 2.0, 2));
auto boost = std::min(
(int)std::floor(std::pow(1 + (cb->getMapSize().x / 50), 2)),
treasureSourcesCount / 2);
logAi->trace("Treasure sources found %d", treasureSourcesCount);
logAi->trace("Startup allows %d+%d heroes", basicCount, boost);
return cb->getHeroCount(ai->playerID, true) < basicCount + boost;

View File

@ -130,6 +130,9 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
return danger;
}
case Obj::PANDORAS_BOX:
return 10000; //Who knows what awaits us there
case Obj::ARTIFACT:
case Obj::RESOURCE:
{

View File

@ -236,7 +236,6 @@ void Nullkiller::makeTurn()
{
Goals::TTaskVec fastTasks = {
choseBestTask(sptr(BuyArmyBehavior()), 1),
choseBestTask(sptr(RecruitHeroBehavior()), 1),
choseBestTask(sptr(BuildingBehavior()), 1)
};
@ -251,6 +250,7 @@ void Nullkiller::makeTurn()
Goals::TTaskVec bestTasks = {
bestTask,
choseBestTask(sptr(RecruitHeroBehavior()), 1),
choseBestTask(sptr(CaptureObjectsBehavior()), 1),
choseBestTask(sptr(ClusterBehavior()), MAX_DEPTH),
choseBestTask(sptr(DefenceBehavior()), MAX_DEPTH),

View File

@ -361,8 +361,8 @@ float RewardEvaluator::getTotalResourceRequirementStrength(int resType) const
return 0;
float ratio = dailyIncome[resType] == 0
? requiredResources[resType] / 50
: (float)requiredResources[resType] / dailyIncome[resType] / 50;
? (float)requiredResources[resType] / 50.0f
: (float)requiredResources[resType] / dailyIncome[resType] / 50.0f;
return std::min(ratio, 1.0f);
}
@ -377,10 +377,12 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
case Obj::MINE:
return target->subID == Res::GOLD
? 0.5f
: 0.02f * getTotalResourceRequirementStrength(target->subID) + 0.02f * getResourceRequirementStrength(target->subID);
: 0.4f * getTotalResourceRequirementStrength(target->subID) + 0.1f * getResourceRequirementStrength(target->subID);
case Obj::RESOURCE:
return target->subID == Res::GOLD ? 0 : 0.1f * getResourceRequirementStrength(target->subID);
return target->subID == Res::GOLD
? 0
: 0.2f * getTotalResourceRequirementStrength(target->subID) + 0.4f * getResourceRequirementStrength(target->subID);
case Obj::CREATURE_BANK:
{
@ -547,7 +549,7 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
case Obj::SEA_CHEST:
return 1500;
case Obj::PANDORAS_BOX:
return 5000;
return 2500;
case Obj::PRISON:
//Objectively saves us 2500 to hire hero
return GameConstants::HERO_GOLD_COST;
@ -800,21 +802,23 @@ public:
evaluationContext.goldReward += 7 * bi.dailyIncome[Res::GOLD] / 2; // 7 day income but half we already have
evaluationContext.heroRole = HeroRole::MAIN;
evaluationContext.movementCostByRole[evaluationContext.heroRole] += bi.prerequisitesCount;
evaluationContext.strategicalValue += buildThis.townInfo.armyStrength / 50000.0;
evaluationContext.goldCost += bi.buildCostWithPrerequisits[Res::GOLD];
if(bi.creatureID != CreatureID::NONE)
{
evaluationContext.strategicalValue += buildThis.townInfo.armyStrength / 50000.0;
if(bi.baseCreatureID == bi.creatureID)
{
evaluationContext.strategicalValue += 0.5f + 0.1f * bi.creatureLevel / (float)bi.prerequisitesCount;
evaluationContext.strategicalValue += (0.5f + 0.1f * bi.creatureLevel) / (float)bi.prerequisitesCount;
evaluationContext.armyReward += bi.armyStrength;
}
else
{
auto potentialUpgradeValue = evaluationContext.evaluator.getUpgradeArmyReward(buildThis.town, bi);
//evaluationContext.strategicalValue += 0.05f * bi.creatureLevel / (float)bi.prerequisitesCount;
evaluationContext.armyReward += 0.3f * potentialUpgradeValue / (float)bi.prerequisitesCount;
evaluationContext.strategicalValue += potentialUpgradeValue / 10000.0f / (float)bi.prerequisitesCount;
evaluationContext.armyReward += potentialUpgradeValue / (float)bi.prerequisitesCount;
}
}
else if(bi.id == BuildingID::CITADEL || bi.id == BuildingID::CASTLE)
@ -824,7 +828,14 @@ public:
}
else
{
evaluationContext.strategicalValue += evaluationContext.evaluator.ai->buildAnalyzer->getGoldPreasure() * evaluationContext.goldReward / 2200.0f;
auto goldPreasure = evaluationContext.evaluator.ai->buildAnalyzer->getGoldPreasure();
evaluationContext.strategicalValue += evaluationContext.goldReward * goldPreasure / 3500.0f / bi.prerequisitesCount;
}
if(bi.notEnoughRes && bi.prerequisitesCount == 1)
{
evaluationContext.strategicalValue /= 2;
}
}
};

View File

@ -855,6 +855,10 @@ void AINodeStorage::setHeroes(std::map<const CGHeroInstance *, HeroRole> heroes)
for(auto & hero : heroes)
{
// do not allow our own heroes in garrison to act on map
if(hero.first->getOwner() == ai->playerID && hero.first->inTownGarrison)
continue;
uint64_t mask = FirstActorMask << actors.size();
auto actor = std::make_shared<HeroActor>(hero.first, hero.second, mask, ai);

View File

@ -274,12 +274,12 @@ ExchangeResult HeroExchangeMap::tryExchangeNoLock(const ChainActor * other)
return result;
}
if(other->isMovable && other->armyValue <= actor->armyValue / 10 && other->armyValue < MIN_ARMY_STRENGTH_FOR_CHAIN)
return result;
if(other->isMovable && other->armyValue <= actor->armyValue / 10 && other->armyValue < MIN_ARMY_STRENGTH_FOR_CHAIN)
return result;
TResources availableResources = resources - actor->armyCost - other->armyCost;
HeroExchangeArmy * upgradedInitialArmy = tryUpgrade(actor->creatureSet, other->getActorObject(), availableResources);
HeroExchangeArmy * newArmy;
TResources availableResources = resources - actor->armyCost - other->armyCost;
HeroExchangeArmy * upgradedInitialArmy = tryUpgrade(actor->creatureSet, other->getActorObject(), availableResources);
HeroExchangeArmy * newArmy;
if(other->creatureSet->Slots().size())
{
@ -303,20 +303,25 @@ ExchangeResult HeroExchangeMap::tryExchangeNoLock(const ChainActor * other)
if(!newArmy) return result;
auto reinforcement = newArmy->getArmyStrength() - actor->creatureSet->getArmyStrength();
auto newArmyStrength = newArmy->getArmyStrength();
auto oldArmyStrength = actor->creatureSet->getArmyStrength();
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
if(newArmyStrength <= oldArmyStrength) return result;
auto reinforcement = newArmyStrength - oldArmyStrength;
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
logAi->trace(
"Exchange %s->%s reinforcement: %d, %f%%",
actor->toString(),
other->toString(),
reinforcement,
100.0f * reinforcement / actor->armyValue);
#endif
#endif
if(reinforcement <= actor->armyValue / 10 && reinforcement < MIN_ARMY_STRENGTH_FOR_CHAIN)
{
delete newArmy;
if(reinforcement <= actor->armyValue / 10 && reinforcement < MIN_ARMY_STRENGTH_FOR_CHAIN)
{
delete newArmy;
return result;
}
@ -365,7 +370,7 @@ HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
{
auto buyArmy = ai->armyManager->getArmyAvailableToBuy(target, ai->cb->getTown(upgrader->id), resources);
for(auto creatureToBuy : buyArmy)
for(auto & creatureToBuy : buyArmy)
{
auto targetSlot = target->getSlotFor(creatureToBuy.cre);

View File

@ -86,8 +86,8 @@ InputVariable: strategicalValue
lock-range: false
term: NONE Ramp 0.200 0.000
term: LOWEST Triangle 0.000 0.010 0.250
term: LOW Triangle 0.000 0.400 0.700
term: MEDIUM Triangle 0.400 0.700 1.000
term: LOW Triangle 0.000 0.250 0.700
term: MEDIUM Triangle 0.250 0.700 1.000
term: HIGH Ramp 0.700 1.000
InputVariable: goldPreasure
description: Ratio between weekly army cost and gold income
@ -189,10 +189,10 @@ RuleBlock: gold reward
rule: if armyReward is LOW and heroRole is MAIN and danger is not NONE and armyLoss is LOW then Value is HIGH
rule: if armyReward is LOW and heroRole is MAIN and danger is NONE then Value is BITLOW with 0.5
rule: if armyReward is LOW and heroRole is MAIN and danger is NONE and mainTurnDistance is LOWEST then Value is HIGH
rule: if skillReward is LOW and heroRole is MAIN and armyLoss is LOW then Value is BITHIGH with 0.5
rule: if skillReward is MEDIUM and heroRole is MAIN and armyLoss is LOW and fear is not HIGH then Value is HIGH with 0.5
rule: if skillReward is LOW and heroRole is MAIN and armyLoss is LOW then Value is BITHIGH
rule: if skillReward is MEDIUM and heroRole is MAIN and armyLoss is LOW and fear is not HIGH then Value is BITHIGH
rule: if skillReward is HIGH and heroRole is MAIN and armyLoss is LOW and fear is not HIGH then Value is HIGH
rule: if strategicalValue is LOW and heroRole is MAIN and armyLoss is LOW then Value is BITLOW
rule: if strategicalValue is LOW and heroRole is MAIN and armyLoss is LOW then Value is BITHIGH
rule: if strategicalValue is LOWEST and heroRole is MAIN and armyLoss is LOW then Value is LOW
rule: if strategicalValue is LOW and heroRole is SCOUT and armyLoss is LOW and fear is not HIGH then Value is HIGH with 0.5
rule: if strategicalValue is MEDIUM and heroRole is SCOUT and danger is NONE and fear is not HIGH then Value is HIGH
@ -211,9 +211,13 @@ RuleBlock: gold reward
rule: if heroRole is MAIN and danger is not NONE and armyLoss is LOW then Value is BITHIGH with 0.2
rule: if heroRole is SCOUT then Value is BITLOW
rule: if goldCost is not NONE and goldReward is NONE and goldPreasure is HIGH then Value is LOWEST
rule: if turn is NOW then Value is BITHIGH with 0.2
rule: if goldPreasure is HIGH and goldReward is HIGH and armyLoss is LOW and fear is not HIGH then Value is HIGHEST
rule: if goldPreasure is HIGH and goldReward is MEDIUM and armyLoss is LOW and fear is not HIGH then Value is HIGH
rule: if goldPreasure is HIGH and goldReward is LOW and armyLoss is LOW then Value is BITHIGH
rule: if turn is NOW then Value is LOW with 0.3
rule: if turn is not NOW then Value is LOW with 0.4
rule: if goldPreasure is HIGH and goldReward is HIGH and heroRole is MAIN and danger is not NONE and armyLoss is LOW and fear is not HIGH then Value is HIGHEST
rule: if goldPreasure is HIGH and goldReward is MEDIUM and heroRole is MAIN and danger is not NONE and armyLoss is LOW and fear is not HIGH then Value is HIGH
rule: if goldPreasure is HIGH and goldReward is HIGH and heroRole is SCOUT and danger is NONE and armyLoss is LOW and fear is not HIGH then Value is HIGHEST
rule: if goldPreasure is HIGH and goldReward is MEDIUM and heroRole is SCOUT and danger is NONE and armyLoss is LOW and fear is not HIGH then Value is HIGH
rule: if goldPreasure is HIGH and goldReward is LOW and heroRole is SCOUT and armyLoss is LOW then Value is BITHIGH
rule: if goldPreasure is HIGH and goldReward is LOW and heroRole is SCOUT and scoutTurnDistance is LOW and armyLoss is LOW then Value is HIGH with 0.5
rule: if fear is MEDIUM then Value is LOW
rule: if fear is HIGH then Value is LOWEST