mirror of
https://github.com/vcmi/vcmi.git
synced 2025-09-16 09:26:28 +02:00
NKAI - Prefer giving fast units to scouts
Nullkiller AI will now prefer giving its scout heroes faster units to optimize their movement points on next turns. Unit selection logic: - AI prefers to give 'weak' units that won't affect army strength of main hero. Unit is considered 'weak' if its level below 4 or if its AI value is below 1% of total army strength. So AI can give high-tier unit to scout, but only if main hero already has massive army. - Within weak units, if hero is moving on terrain with penalty, AI will always prefer units that are native to this terrain. So on snow AI will always prefer unit from Tower even if its speed is lower than unit from another faction. - Within remaining candidates, AI will prefer unit that will give higher movement points limit. This also means that in case of H3 rules, all units with 11+ speed will be viewed as equally good - If there are multiple units with same speed, AI will prefer unit with lowest AI value
This commit is contained in:
@@ -411,6 +411,7 @@ void AIGateway::heroCreated(const CGHeroInstance * h)
|
||||
{
|
||||
LOG_TRACE(logAi);
|
||||
NET_EVENT_HANDLER;
|
||||
nullkiller->invalidatePathfinderData(); // new hero needs to look around
|
||||
}
|
||||
|
||||
void AIGateway::advmapSpellCast(const CGHeroInstance * caster, SpellID spellID)
|
||||
@@ -929,7 +930,7 @@ void AIGateway::pickBestCreatures(const CArmedInstance * destinationArmy, const
|
||||
|
||||
const CArmedInstance * armies[] = {destinationArmy, source};
|
||||
|
||||
auto bestArmy = nullkiller->armyManager->getBestArmy(destinationArmy, destinationArmy, source);
|
||||
auto bestArmy = nullkiller->armyManager->getBestArmy(destinationArmy, destinationArmy, source, myCb->getTile(source->visitablePos())->getTerrainID());
|
||||
|
||||
for(auto army : armies)
|
||||
{
|
||||
@@ -983,7 +984,7 @@ void AIGateway::pickBestCreatures(const CArmedInstance * destinationArmy, const
|
||||
&& source->stacksCount() == 1
|
||||
&& (!destinationArmy->hasStackAtSlot(i) || destinationArmy->getCreature(i) == targetCreature))
|
||||
{
|
||||
auto weakest = nullkiller->armyManager->getWeakestCreature(bestArmy);
|
||||
auto weakest = nullkiller->armyManager->getBestUnitForScout(bestArmy, myCb->getTile(source->visitablePos())->getTerrainID());
|
||||
|
||||
if(weakest->creature == targetCreature)
|
||||
{
|
||||
|
@@ -13,8 +13,10 @@
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../../../CCallback.h"
|
||||
#include "../../../lib/mapObjects/MapObjects.h"
|
||||
#include "../../../lib/mapping/CMapDefines.h"
|
||||
#include "../../../lib/IGameSettings.h"
|
||||
#include "../../../lib/GameConstants.h"
|
||||
#include "../../../lib/TerrainHandler.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
@@ -76,7 +78,7 @@ std::vector<SlotInfo> ArmyManager::toSlotInfo(std::vector<creInfo> army) const
|
||||
|
||||
uint64_t ArmyManager::howManyReinforcementsCanGet(const CGHeroInstance * hero, const CCreatureSet * source) const
|
||||
{
|
||||
return howManyReinforcementsCanGet(hero, hero, source);
|
||||
return howManyReinforcementsCanGet(hero, hero, source, ai->cb->getTile(hero->visitablePos())->getTerrainID());
|
||||
}
|
||||
|
||||
std::vector<SlotInfo> ArmyManager::getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const
|
||||
@@ -111,17 +113,59 @@ std::vector<SlotInfo> ArmyManager::getSortedSlots(const CCreatureSet * target, c
|
||||
return resultingArmy;
|
||||
}
|
||||
|
||||
std::vector<SlotInfo>::iterator ArmyManager::getWeakestCreature(std::vector<SlotInfo> & army) const
|
||||
std::vector<SlotInfo>::iterator ArmyManager::getBestUnitForScout(std::vector<SlotInfo> & army, const TerrainId & armyTerrain) const
|
||||
{
|
||||
auto weakest = boost::min_element(army, [](const SlotInfo & left, const SlotInfo & right) -> bool
|
||||
uint64_t totalPower = 0;
|
||||
|
||||
for (const auto & unit : army)
|
||||
totalPower += unit.power;
|
||||
|
||||
// TODO: replace with EGameSettings::HEROES_MOVEMENT_COST_BASE in 1.7
|
||||
bool terrainHasPenalty = armyTerrain.hasValue() && armyTerrain.toEntity(VLC)->moveCost != GameConstants::BASE_MOVEMENT_COST;
|
||||
|
||||
// arbitrary threshold - don't give scout more than specified part of total AI value of our army
|
||||
uint64_t maxUnitValue = totalPower / 100;
|
||||
|
||||
const auto & movementPointsLimits = cb->getSettings().getVector(EGameSettings::HEROES_MOVEMENT_POINTS_LAND);
|
||||
|
||||
auto fastest = boost::min_element(army, [&](const SlotInfo & left, const SlotInfo & right) -> bool
|
||||
{
|
||||
if(left.creature->getLevel() != right.creature->getLevel())
|
||||
return left.creature->getLevel() < right.creature->getLevel();
|
||||
|
||||
return left.creature->getMovementRange() > right.creature->getMovementRange();
|
||||
uint64_t leftUnitPower = left.power / left.count;
|
||||
uint64_t rightUnitPower = left.power / left.count;
|
||||
bool leftUnitIsWeak = leftUnitPower < maxUnitValue || left.creature->getLevel() < 4;
|
||||
bool rightUnitIsWeak = rightUnitPower < maxUnitValue || right.creature->getLevel() < 4;
|
||||
|
||||
if (leftUnitIsWeak != rightUnitIsWeak)
|
||||
return leftUnitIsWeak;
|
||||
|
||||
if (terrainHasPenalty)
|
||||
{
|
||||
auto leftNativeTerrain = left.creature->getFactionID().toFaction()->nativeTerrain;
|
||||
auto rightNativeTerrain = right.creature->getFactionID().toFaction()->nativeTerrain;
|
||||
|
||||
if (leftNativeTerrain != rightNativeTerrain)
|
||||
{
|
||||
if (leftNativeTerrain == armyTerrain)
|
||||
return true;
|
||||
|
||||
if (rightNativeTerrain == armyTerrain)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int leftEffectiveMovement = std::min<int>(movementPointsLimits.size() - 1, left.creature->getMovementRange());
|
||||
int rightEffectiveMovement = std::min<int>(movementPointsLimits.size() - 1, right.creature->getMovementRange());
|
||||
|
||||
int leftMovementPointsLimit = movementPointsLimits[leftEffectiveMovement];
|
||||
int rightMovementPointsLimit = movementPointsLimits[rightEffectiveMovement];
|
||||
|
||||
if (leftMovementPointsLimit != rightMovementPointsLimit)
|
||||
return leftMovementPointsLimit > rightMovementPointsLimit;
|
||||
|
||||
return leftUnitPower < rightUnitPower;
|
||||
});
|
||||
|
||||
return weakest;
|
||||
return fastest;
|
||||
}
|
||||
|
||||
class TemporaryArmy : public CArmedInstance
|
||||
@@ -134,7 +178,7 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const
|
||||
std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source, const TerrainId & armyTerrain) const
|
||||
{
|
||||
auto sortedSlots = getSortedSlots(target, source);
|
||||
|
||||
@@ -218,7 +262,7 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
|
||||
&& allowedFactions.size() == alignmentMap.size()
|
||||
&& source->needsLastStack())
|
||||
{
|
||||
auto weakest = getWeakestCreature(resultingArmy);
|
||||
auto weakest = getBestUnitForScout(resultingArmy, armyTerrain);
|
||||
|
||||
if(weakest->count == 1)
|
||||
{
|
||||
@@ -398,14 +442,14 @@ std::vector<creInfo> ArmyManager::getArmyAvailableToBuy(
|
||||
return creaturesInDwellings;
|
||||
}
|
||||
|
||||
ui64 ArmyManager::howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const
|
||||
ui64 ArmyManager::howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source, const TerrainId & armyTerrain) const
|
||||
{
|
||||
if(source->stacksCount() == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto bestArmy = getBestArmy(armyCarrier, target, source);
|
||||
auto bestArmy = getBestArmy(armyCarrier, target, source, armyTerrain);
|
||||
uint64_t newArmy = 0;
|
||||
uint64_t oldArmy = target->getArmyStrength();
|
||||
|
||||
|
@@ -53,10 +53,11 @@ public:
|
||||
virtual ui64 howManyReinforcementsCanGet(
|
||||
const IBonusBearer * armyCarrier,
|
||||
const CCreatureSet * target,
|
||||
const CCreatureSet * source) const = 0;
|
||||
const CCreatureSet * source,
|
||||
const TerrainId & armyTerrain) const = 0;
|
||||
|
||||
virtual std::vector<SlotInfo> getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const = 0;
|
||||
virtual std::vector<SlotInfo>::iterator getWeakestCreature(std::vector<SlotInfo> & army) const = 0;
|
||||
virtual std::vector<SlotInfo> getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source, const TerrainId & armyTerrain) const = 0;
|
||||
virtual std::vector<SlotInfo>::iterator getBestUnitForScout(std::vector<SlotInfo> & army, const TerrainId & armyTerrain) const = 0;
|
||||
virtual std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const = 0;
|
||||
virtual std::vector<SlotInfo> toSlotInfo(std::vector<creInfo> creatures) const = 0;
|
||||
|
||||
@@ -97,9 +98,9 @@ public:
|
||||
uint8_t turn = 0) const override;
|
||||
|
||||
ui64 howManyReinforcementsCanGet(const CGHeroInstance * hero, const CCreatureSet * source) const override;
|
||||
ui64 howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const override;
|
||||
std::vector<SlotInfo> getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source) const override;
|
||||
std::vector<SlotInfo>::iterator getWeakestCreature(std::vector<SlotInfo> & army) const override;
|
||||
ui64 howManyReinforcementsCanGet(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source, const TerrainId & armyTerrain) const override;
|
||||
std::vector<SlotInfo> getBestArmy(const IBonusBearer * armyCarrier, const CCreatureSet * target, const CCreatureSet * source, const TerrainId & armyTerrain) const override;
|
||||
std::vector<SlotInfo>::iterator getBestUnitForScout(std::vector<SlotInfo> & army, const TerrainId & armyTerrain) const override;
|
||||
std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const override;
|
||||
std::vector<SlotInfo> toSlotInfo(std::vector<creInfo> creatures) const override;
|
||||
|
||||
|
@@ -57,7 +57,8 @@ Goals::TGoalVec BuyArmyBehavior::decompose(const Nullkiller * ai) const
|
||||
auto reinforcement = ai->armyManager->howManyReinforcementsCanGet(
|
||||
targetHero,
|
||||
targetHero,
|
||||
&*townArmyAvailableToBuy);
|
||||
&*townArmyAvailableToBuy,
|
||||
TerrainId::NONE);
|
||||
|
||||
if(reinforcement)
|
||||
vstd::amin(reinforcement, ai->armyManager->howManyReinforcementsCanBuy(town->getUpperArmy(), town));
|
||||
|
@@ -300,7 +300,8 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * ai, const CGT
|
||||
ai->armyManager->getBestArmy(
|
||||
path.targetHero,
|
||||
path.heroArmy,
|
||||
upgrader->getUpperArmy()));
|
||||
upgrader->getUpperArmy(),
|
||||
TerrainId::NONE));
|
||||
|
||||
armyToGetOrBuy.upgradeValue -= path.heroArmy->getArmyStrength();
|
||||
|
||||
|
@@ -149,7 +149,7 @@ Goals::TGoalVec StartupBehavior::decompose(const Nullkiller * ai) const
|
||||
{
|
||||
if(!startupTown->visitingHero)
|
||||
{
|
||||
if(ai->armyManager->howManyReinforcementsCanGet(startupTown->getUpperArmy(), startupTown->getUpperArmy(), closestHero) > 200)
|
||||
if(ai->armyManager->howManyReinforcementsCanGet(startupTown->getUpperArmy(), startupTown->getUpperArmy(), closestHero, TerrainId::NONE) > 200)
|
||||
{
|
||||
auto paths = ai->pathfinder->getPathInfo(startupTown->visitablePos());
|
||||
|
||||
|
@@ -13,6 +13,7 @@
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../../../CCallback.h"
|
||||
#include "../../../lib/mapObjects/MapObjects.h"
|
||||
#include "../../../lib/mapping/CMapDefines.h"
|
||||
#include "../../../lib/pathfinder/TurnInfo.h"
|
||||
#include "Actions/BuyArmyAction.h"
|
||||
|
||||
@@ -394,7 +395,7 @@ HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
|
||||
HeroExchangeArmy * HeroExchangeMap::pickBestCreatures(const CCreatureSet * army1, const CCreatureSet * army2) const
|
||||
{
|
||||
auto * target = new HeroExchangeArmy();
|
||||
auto bestArmy = ai->armyManager->getBestArmy(actor->hero, army1, army2);
|
||||
auto bestArmy = ai->armyManager->getBestArmy(actor->hero, army1, army2, ai->cb->getTile(actor->hero->visitablePos())->getTerrainID());
|
||||
|
||||
for(auto & slotInfo : bestArmy)
|
||||
{
|
||||
|
Reference in New Issue
Block a user