1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-03-25 21:38:59 +02:00

Merge pull request #5412 from IvanSavenko/ai_scouts

NKAI - Prefer giving fast units to scouts
This commit is contained in:
Ivan Savenko 2025-02-14 12:49:41 +02:00 committed by GitHub
commit 32a2413b5e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 73 additions and 24 deletions

View File

@ -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)
{

View File

@ -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();

View File

@ -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;

View File

@ -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));

View File

@ -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();

View File

@ -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());

View File

@ -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)
{