2021-05-15 19:23:38 +03:00
/*
2021-05-15 22:04:26 +03:00
* PriorityEvaluator . cpp , part of VCMI engine
*
* Authors : listed in file AUTHORS in main folder
*
* License : GNU General Public License v2 .0 or later
* Full text of license available in license . txt file , in main folder
*
2021-05-15 19:23:38 +03:00
*/
# include "StdInc.h"
# include <limits>
2021-05-16 14:55:47 +03:00
# include "Nullkiller.h"
2023-06-02 21:47:37 +03:00
# include "../../../lib/mapObjectConstructors/AObjectTypeHandler.h"
# include "../../../lib/mapObjectConstructors/CObjectClassesHandler.h"
2023-06-06 18:32:53 +03:00
# include "../../../lib/mapObjectConstructors/CBankInstanceConstructor.h"
2021-05-15 19:23:38 +03:00
# include "../../../lib/mapObjects/MapObjects.h"
2024-10-20 23:32:39 +02:00
# include "../../../lib/mapping/CMapDefines.h"
# include "../../../lib/RoadHandler.h"
2021-05-15 19:23:38 +03:00
# include "../../../lib/CCreatureHandler.h"
# include "../../../lib/VCMI_Lib.h"
2023-02-28 09:07:59 +02:00
# include "../../../lib/StartInfo.h"
2021-05-15 19:23:38 +03:00
# include "../../../CCallback.h"
2021-05-15 22:02:27 +03:00
# include "../../../lib/filesystem/Filesystem.h"
2021-05-16 14:15:12 +03:00
# include "../Goals/ExecuteHeroChain.h"
2020-05-04 18:58:43 +03:00
# include "../Goals/BuildThis.h"
2023-09-24 13:07:42 +03:00
# include "../Goals/StayAtTown.h"
2023-02-28 09:07:59 +02:00
# include "../Goals/ExchangeSwapTownHeroes.h"
2023-06-04 16:02:02 +03:00
# include "../Goals/DismissHero.h"
2021-05-16 15:01:37 +03:00
# include "../Markers/UnlockCluster.h"
2021-05-16 14:56:35 +03:00
# include "../Markers/HeroExchange.h"
# include "../Markers/ArmyUpgrade.h"
2021-05-16 15:00:24 +03:00
# include "../Markers/DefendTown.h"
2021-05-15 19:23:38 +03:00
2022-09-26 21:01:07 +03:00
namespace NKAI
{
2024-11-28 15:08:15 +00:00
constexpr float MIN_CRITICAL_VALUE = 2.0f ;
2021-05-15 19:23:38 +03:00
2024-07-12 23:36:41 +02:00
EvaluationContext : : EvaluationContext ( const Nullkiller * ai )
2021-05-16 14:38:53 +03:00
: movementCost ( 0.0 ) ,
manaCost ( 0 ) ,
danger ( 0 ) ,
closestWayRatio ( 1 ) ,
movementCostByRole ( ) ,
skillReward ( 0 ) ,
goldReward ( 0 ) ,
goldCost ( 0 ) ,
armyReward ( 0 ) ,
armyLossPersentage ( 0 ) ,
heroRole ( HeroRole : : SCOUT ) ,
turn ( 0 ) ,
2020-05-04 18:58:43 +03:00
strategicalValue ( 0 ) ,
2024-07-12 17:41:46 +02:00
conquestValue ( 0 ) ,
2021-05-16 14:55:33 +03:00
evaluator ( ai ) ,
2023-05-14 09:17:15 +03:00
enemyHeroDangerRatio ( 0 ) ,
2024-07-15 18:12:52 +02:00
threat ( 0 ) ,
2024-07-12 17:41:46 +02:00
armyGrowth ( 0 ) ,
2024-07-12 23:36:41 +02:00
armyInvolvement ( 0 ) ,
2024-08-11 18:21:24 +02:00
defenseValue ( 0 ) ,
2024-07-15 18:12:52 +02:00
isDefend ( false ) ,
2024-08-14 22:53:48 +02:00
threatTurns ( INT_MAX ) ,
2024-07-19 15:25:24 +02:00
involvesSailing ( false ) ,
2024-08-14 22:53:48 +02:00
isTradeBuilding ( false ) ,
2024-09-10 00:23:17 +02:00
isExchange ( false ) ,
2024-09-14 02:58:23 +02:00
isArmyUpgrade ( false ) ,
2024-09-22 13:06:07 +02:00
isHero ( false ) ,
2024-09-23 01:25:58 +02:00
isEnemy ( false ) ,
2024-09-14 02:58:23 +02:00
explorePriority ( 0 )
2021-05-16 14:38:53 +03:00
{
}
2023-06-04 16:02:02 +03:00
void EvaluationContext : : addNonCriticalStrategicalValue ( float value )
{
vstd : : amax ( strategicalValue , std : : min ( value , MIN_CRITICAL_VALUE ) ) ;
}
2021-05-15 19:23:38 +03:00
PriorityEvaluator : : ~ PriorityEvaluator ( )
{
2021-05-15 22:02:27 +03:00
delete engine ;
2021-05-15 19:23:38 +03:00
}
void PriorityEvaluator : : initVisitTile ( )
{
2024-02-25 12:39:19 +02:00
auto file = CResourceHandler : : get ( ) - > load ( ResourcePath ( " config/ai/nkai/object-priorities.txt " ) ) - > readAll ( ) ;
2021-05-15 22:02:27 +03:00
std : : string str = std : : string ( ( char * ) file . first . get ( ) , file . second ) ;
engine = fl : : FllImporter ( ) . fromString ( str ) ;
armyLossPersentageVariable = engine - > getInputVariable ( " armyLoss " ) ;
2023-05-14 09:17:15 +03:00
armyGrowthVariable = engine - > getInputVariable ( " armyGrowth " ) ;
2021-05-15 22:02:27 +03:00
heroRoleVariable = engine - > getInputVariable ( " heroRole " ) ;
dangerVariable = engine - > getInputVariable ( " danger " ) ;
2021-05-16 14:19:00 +03:00
turnVariable = engine - > getInputVariable ( " turn " ) ;
2021-05-16 14:14:07 +03:00
mainTurnDistanceVariable = engine - > getInputVariable ( " mainTurnDistance " ) ;
scoutTurnDistanceVariable = engine - > getInputVariable ( " scoutTurnDistance " ) ;
2021-05-15 22:02:27 +03:00
goldRewardVariable = engine - > getInputVariable ( " goldReward " ) ;
armyRewardVariable = engine - > getInputVariable ( " armyReward " ) ;
skillRewardVariable = engine - > getInputVariable ( " skillReward " ) ;
rewardTypeVariable = engine - > getInputVariable ( " rewardType " ) ;
closestHeroRatioVariable = engine - > getInputVariable ( " closestHeroRatio " ) ;
2021-05-16 14:13:35 +03:00
strategicalValueVariable = engine - > getInputVariable ( " strategicalValue " ) ;
2024-04-16 21:10:15 +00:00
goldPressureVariable = engine - > getInputVariable ( " goldPressure " ) ;
2021-05-16 14:19:00 +03:00
goldCostVariable = engine - > getInputVariable ( " goldCost " ) ;
2021-05-16 14:55:33 +03:00
fearVariable = engine - > getInputVariable ( " fear " ) ;
2021-05-15 22:02:27 +03:00
value = engine - > getOutputVariable ( " Value " ) ;
2021-05-15 19:23:38 +03:00
}
2023-03-05 15:42:15 +02:00
bool isAnotherAi ( const CGObjectInstance * obj , const CPlayerSpecificInfoCallback & cb )
{
return obj - > getOwner ( ) . isValidPlayer ( )
& & cb . getStartInfo ( ) - > getIthPlayersSettings ( obj - > getOwner ( ) ) . isControlledByAI ( ) ;
}
2021-05-16 15:08:56 +03:00
int32_t estimateTownIncome ( CCallback * cb , const CGObjectInstance * target , const CGHeroInstance * hero )
2021-05-15 19:23:38 +03:00
{
auto relations = cb - > getPlayerRelations ( hero - > tempOwner , target - > tempOwner ) ;
if ( relations ! = PlayerRelations : : ENEMIES )
return 0 ; // if we already own it, no additional reward will be received by just visiting it
2023-03-05 15:42:15 +02:00
auto booster = isAnotherAi ( target , * cb ) ? 1 : 2 ;
2021-05-15 19:23:38 +03:00
auto town = cb - > getTown ( target - > id ) ;
2023-02-28 09:07:59 +02:00
auto fortLevel = town - > fortLevel ( ) ;
2021-05-15 19:23:38 +03:00
2023-07-30 18:02:56 +03:00
if ( town - > hasCapitol ( ) )
return booster * 2000 ;
2023-02-28 09:07:59 +02:00
// probably well developed town will have city hall
2023-03-05 15:42:15 +02:00
if ( fortLevel = = CGTownInstance : : CASTLE ) return booster * 750 ;
2023-02-28 09:07:59 +02:00
2023-03-05 15:42:15 +02:00
return booster * ( town - > hasFort ( ) & & town - > tempOwner ! = PlayerColor : : NEUTRAL ? booster * 500 : 250 ) ;
2021-05-15 19:23:38 +03:00
}
2024-04-20 15:28:42 +03:00
int32_t getResourcesGoldReward ( const TResources & res )
2023-12-23 15:53:30 +02:00
{
2024-04-20 15:28:42 +03:00
int32_t result = 0 ;
2024-08-12 18:26:30 +00:00
for ( auto r : GameResID : : ALL_RESOURCES ( ) )
2024-04-20 15:28:42 +03:00
{
if ( res [ r ] > 0 )
result + = r = = EGameResID : : GOLD ? res [ r ] : res [ r ] * 100 ;
}
return result ;
2023-12-23 15:53:30 +02:00
}
2021-05-15 19:23:38 +03:00
uint64_t getCreatureBankArmyReward ( const CGObjectInstance * target , const CGHeroInstance * hero )
{
2023-10-28 12:27:10 +03:00
auto objectInfo = target - > getObjectHandler ( ) - > getObjectInfo ( target - > appearance ) ;
2021-05-15 19:23:38 +03:00
CBankInfo * bankInfo = dynamic_cast < CBankInfo * > ( objectInfo . get ( ) ) ;
2024-01-09 19:08:22 +02:00
auto creatures = bankInfo - > getPossibleCreaturesReward ( target - > cb ) ;
2021-05-15 19:23:38 +03:00
uint64_t result = 0 ;
2022-09-09 19:19:01 +02:00
const auto & slots = hero - > Slots ( ) ;
ui64 weakestStackPower = 0 ;
2024-04-20 10:33:37 +03:00
int duplicatingSlots = getDuplicatingSlots ( hero ) ;
2022-09-09 19:19:01 +02:00
if ( slots . size ( ) > = GameConstants : : ARMY_SIZE )
2021-05-15 19:23:38 +03:00
{
2022-09-09 19:19:01 +02:00
//No free slot, we might discard our weakest stack
weakestStackPower = std : : numeric_limits < ui64 > ( ) . max ( ) ;
2022-09-22 11:02:36 +03:00
for ( const auto & stack : slots )
2022-09-09 19:19:01 +02:00
{
vstd : : amin ( weakestStackPower , stack . second - > getPower ( ) ) ;
}
2021-05-15 19:23:38 +03:00
}
2022-09-09 19:19:01 +02:00
for ( auto c : creatures )
{
//Only if hero has slot for this creature in the army
2024-10-12 16:02:35 +00:00
auto ccre = dynamic_cast < const CCreature * > ( c . data . getType ( ) ) ;
2024-04-20 10:33:37 +03:00
if ( hero - > getSlotFor ( ccre ) . validSlot ( ) | | duplicatingSlots > 0 )
2022-09-09 19:19:01 +02:00
{
2024-10-12 16:02:35 +00:00
result + = ( c . data . getType ( ) - > getAIValue ( ) * c . data . count ) * c . chance ;
2022-09-09 19:19:01 +02:00
}
2023-07-30 11:33:52 +03:00
/*else
2022-09-09 19:19:01 +02:00
{
//we will need to discard the weakest stack
2023-04-05 03:26:29 +03:00
result + = ( c . data . type - > getAIValue ( ) * c . data . count - weakestStackPower ) * c . chance ;
2023-07-30 11:33:52 +03:00
} */
2022-09-09 19:19:01 +02:00
}
result / = 100 ; //divide by total chance
2021-05-15 19:23:38 +03:00
return result ;
}
2023-05-14 09:17:15 +03:00
uint64_t getDwellingArmyValue ( CCallback * cb , const CGObjectInstance * target , bool checkGold )
2021-05-15 19:23:38 +03:00
{
auto dwelling = dynamic_cast < const CGDwelling * > ( target ) ;
uint64_t score = 0 ;
for ( auto & creLevel : dwelling - > creatures )
{
if ( creLevel . first & & creLevel . second . size ( ) )
{
auto creature = creLevel . second . back ( ) . toCreature ( ) ;
2023-04-05 03:26:29 +03:00
auto creaturesAreFree = creature - > getLevel ( ) = = 1 ;
if ( ! creaturesAreFree & & checkGold & & ! cb - > getResourceAmount ( ) . canAfford ( creature - > getFullRecruitCost ( ) * creLevel . first ) )
2021-05-15 22:02:57 +03:00
continue ;
2023-04-05 03:26:29 +03:00
score + = creature - > getAIValue ( ) * creLevel . first ;
2021-05-15 19:23:38 +03:00
}
}
return score ;
}
2023-05-14 09:17:15 +03:00
uint64_t getDwellingArmyGrowth ( CCallback * cb , const CGObjectInstance * target , PlayerColor myColor )
{
auto dwelling = dynamic_cast < const CGDwelling * > ( target ) ;
uint64_t score = 0 ;
if ( dwelling - > getOwner ( ) = = myColor )
return 0 ;
for ( auto & creLevel : dwelling - > creatures )
{
if ( creLevel . second . size ( ) )
{
auto creature = creLevel . second . back ( ) . toCreature ( ) ;
score + = creature - > getAIValue ( ) * creature - > getGrowth ( ) ;
}
}
return score ;
}
2021-05-16 14:19:00 +03:00
int getDwellingArmyCost ( const CGObjectInstance * target )
{
auto dwelling = dynamic_cast < const CGDwelling * > ( target ) ;
int cost = 0 ;
for ( auto & creLevel : dwelling - > creatures )
{
if ( creLevel . first & & creLevel . second . size ( ) )
{
auto creature = creLevel . second . back ( ) . toCreature ( ) ;
2023-04-05 03:26:29 +03:00
auto creaturesAreFree = creature - > getLevel ( ) = = 1 ;
2021-05-16 14:19:00 +03:00
if ( ! creaturesAreFree )
2024-07-15 18:12:52 +02:00
cost + = creature - > getFullRecruitCost ( ) . marketValue ( ) * creLevel . first ;
2021-05-16 14:19:00 +03:00
}
}
return cost ;
}
2024-04-20 15:28:42 +03:00
static uint64_t evaluateArtifactArmyValue ( const CArtifact * art )
2021-05-16 13:52:34 +03:00
{
2024-04-20 15:28:42 +03:00
if ( art - > getId ( ) = = ArtifactID : : SPELL_SCROLL )
2021-05-16 13:52:34 +03:00
return 1500 ;
Per-hero artifact scoring for NKAI
AI now has better (but not perfect) system for scoring artifacts.
All artifacts now have their base score, based on bonuses provided by
the artifact. For composite artifacts, AI will also consider bonuses
from its components.
When exploring, AI will now weight artifacts as min(base score, price /
5), meaning even artifacts that AI can't score will still have some
priority, if only to sell them or deny them to enemy
AI will now also consider what to equip when trading with heroes or on
the end of AI pass. When considering what to equip, hero will use base
score of an artifact, adjusted by how relevant particular bonus /
artifact for a hero. Some examples:
- Morale-bonusing artifacts have their score reduced based on percentage
of undeads in the army, all the way to 0 for undead-only troops
- Artifacts that give spells (books, hat) will have score reduced based
on how many of these spells are already known by hero
- Land movement artifacts have zero score while on water and vice versa
- Necromancy artifacts will have zero score if hero does not have
necromancy
- Archery artifacts are scaled by amount of ranged troops, and only used
if hero has archery skill
AI may still equip 'bad' artifact, however this should only happen if
hero has no other artifacts to equip into said slot.
TODO's for future PR's (although not sure if / when I will work on
these):
- avoid equipping duplicates of the same artifact. Most notably with
scrolls, but may happen with other misc artifacts or rings.
- consideration for various spell-immunity neclaces
- consideration for birectional artifacts, like Shackles of War, or some
Spheres - since these artifacts need consideration on what our expected
enemy is to correctly estimate whether
- equipping artifacts based on immediate need - for example, equipping
recovery artifacts before end of day, equipping legion pieces only in
town on day 7, preparing for strong / weak enemies (or even preparing
for specific enemy in advance)
- transferring resource-generating artifacts and such to scout heroes,
allowing main hero to focus on combat
- ensure that AI can equip combined artifacts even if non-primary slots
are in use - by considering whether score of combined artifact is higher
than score of all used slots
2025-01-18 13:14:24 +00:00
return getPotentialArtifactScore ( art ) ;
2021-05-16 13:52:34 +03:00
}
2020-05-04 18:58:43 +03:00
uint64_t RewardEvaluator : : getArmyReward (
const CGObjectInstance * target ,
const CGHeroInstance * hero ,
const CCreatureSet * army ,
bool checkGold ) const
2021-05-15 19:23:38 +03:00
{
2021-05-16 13:53:05 +03:00
const float enemyArmyEliminationRewardRatio = 0.5f ;
2023-07-30 11:33:52 +03:00
auto relations = ai - > cb - > getPlayerRelations ( target - > tempOwner , ai - > playerID ) ;
2021-05-15 19:23:38 +03:00
if ( ! target )
return 0 ;
switch ( target - > ID )
{
2021-05-16 14:15:12 +03:00
case Obj : : HILL_FORT :
2021-05-16 20:53:11 +03:00
return ai - > armyManager - > calculateCreaturesUpgrade ( army , target , ai - > cb - > getResourceAmount ( ) ) . upgradeValue ;
2021-05-15 19:23:38 +03:00
case Obj : : CREATURE_GENERATOR1 :
2021-05-15 22:02:57 +03:00
case Obj : : CREATURE_GENERATOR2 :
case Obj : : CREATURE_GENERATOR3 :
case Obj : : CREATURE_GENERATOR4 :
2023-05-14 09:17:15 +03:00
return getDwellingArmyValue ( ai - > cb . get ( ) , target , checkGold ) ;
2024-09-13 23:06:49 +02:00
case Obj : : SPELL_SCROLL :
//FALL_THROUGH
2021-05-15 19:23:38 +03:00
case Obj : : ARTIFACT :
2024-10-12 08:41:59 +00:00
return evaluateArtifactArmyValue ( dynamic_cast < const CGArtifact * > ( target ) - > storedArtifact - > getType ( ) ) ;
2021-05-16 13:53:05 +03:00
case Obj : : HERO :
2023-07-30 11:33:52 +03:00
return relations = = PlayerRelations : : ENEMIES
2021-05-16 13:53:05 +03:00
? enemyArmyEliminationRewardRatio * dynamic_cast < const CGHeroInstance * > ( target ) - > getArmyStrength ( )
: 0 ;
2022-09-09 19:20:41 +02:00
case Obj : : PANDORAS_BOX :
return 5000 ;
2023-09-24 13:07:42 +03:00
case Obj : : MAGIC_WELL :
case Obj : : MAGIC_SPRING :
return getManaRecoveryArmyReward ( hero ) ;
2021-05-15 19:23:38 +03:00
default :
2024-04-20 15:28:42 +03:00
break ;
2021-05-15 19:23:38 +03:00
}
2024-04-20 15:28:42 +03:00
auto rewardable = dynamic_cast < const Rewardable : : Interface * > ( target ) ;
if ( rewardable )
{
auto totalValue = 0 ;
for ( int index : rewardable - > getAvailableRewards ( hero , Rewardable : : EEventType : : EVENT_FIRST_VISIT ) )
{
auto & info = rewardable - > configuration . info [ index ] ;
auto rewardValue = 0 ;
if ( ! info . reward . artifacts . empty ( ) )
{
for ( auto artID : info . reward . artifacts )
{
2024-08-12 18:26:30 +00:00
const auto * art = dynamic_cast < const CArtifact * > ( VLC - > artifacts ( ) - > getById ( artID ) ) ;
2024-04-20 15:28:42 +03:00
rewardValue + = evaluateArtifactArmyValue ( art ) ;
}
}
if ( ! info . reward . creatures . empty ( ) )
{
2024-08-12 18:26:30 +00:00
for ( const auto & stackInfo : info . reward . creatures )
2024-04-20 15:28:42 +03:00
{
rewardValue + = stackInfo . getType ( ) - > getAIValue ( ) * stackInfo . getCount ( ) ;
}
}
totalValue + = rewardValue > 0 ? rewardValue / ( info . reward . artifacts . size ( ) + info . reward . creatures . size ( ) ) : 0 ;
}
return totalValue ;
}
return 0 ;
2021-05-15 19:23:38 +03:00
}
2023-05-14 09:17:15 +03:00
uint64_t RewardEvaluator : : getArmyGrowth (
const CGObjectInstance * target ,
const CGHeroInstance * hero ,
const CCreatureSet * army ) const
{
if ( ! target )
return 0 ;
2023-07-30 11:33:52 +03:00
auto relations = ai - > cb - > getPlayerRelations ( target - > tempOwner , hero - > tempOwner ) ;
if ( relations ! = PlayerRelations : : ENEMIES )
return 0 ;
2023-05-14 09:17:15 +03:00
switch ( target - > ID )
{
case Obj : : TOWN :
{
auto town = dynamic_cast < const CGTownInstance * > ( target ) ;
auto fortLevel = town - > fortLevel ( ) ;
auto neutral = ! town - > getOwner ( ) . isValidPlayer ( ) ;
auto booster = isAnotherAi ( town , * ai - > cb ) | | neutral ? 1 : 2 ;
if ( fortLevel < CGTownInstance : : CITADEL )
return town - > hasFort ( ) ? booster * 500 : 0 ;
else
return booster * ( fortLevel = = CGTownInstance : : CASTLE ? 5000 : 2000 ) ;
}
case Obj : : CREATURE_GENERATOR1 :
case Obj : : CREATURE_GENERATOR2 :
case Obj : : CREATURE_GENERATOR3 :
case Obj : : CREATURE_GENERATOR4 :
return getDwellingArmyGrowth ( ai - > cb . get ( ) , target , hero - > getOwner ( ) ) ;
case Obj : : ARTIFACT :
// it is not supported now because hero will not sit in town on 7th day but later parts of legion may be counted as army growth as well.
return 0 ;
default :
return 0 ;
}
}
2020-05-04 18:58:43 +03:00
int RewardEvaluator : : getGoldCost ( const CGObjectInstance * target , const CGHeroInstance * hero , const CCreatureSet * army ) const
2021-05-16 14:19:00 +03:00
{
if ( ! target )
return 0 ;
2023-05-01 15:29:56 +04:00
if ( auto * m = dynamic_cast < const IMarket * > ( target ) )
{
if ( m - > allowsTrade ( EMarketMode : : RESOURCE_SKILL ) )
return 2000 ;
}
2021-05-16 14:19:00 +03:00
switch ( target - > ID )
{
case Obj : : HILL_FORT :
2023-04-05 03:26:29 +03:00
return ai - > armyManager - > calculateCreaturesUpgrade ( army , target , ai - > cb - > getResourceAmount ( ) ) . upgradeCost [ EGameResID : : GOLD ] ;
2021-05-16 14:19:00 +03:00
case Obj : : SCHOOL_OF_MAGIC :
case Obj : : SCHOOL_OF_WAR :
return 1000 ;
case Obj : : CREATURE_GENERATOR1 :
case Obj : : CREATURE_GENERATOR2 :
case Obj : : CREATURE_GENERATOR3 :
case Obj : : CREATURE_GENERATOR4 :
return getDwellingArmyCost ( target ) ;
default :
return 0 ;
}
}
2020-05-04 18:58:43 +03:00
float RewardEvaluator : : getEnemyHeroStrategicalValue ( const CGHeroInstance * enemy ) const
2021-05-16 14:13:35 +03:00
{
2020-05-04 18:58:43 +03:00
auto objectsUnderTreat = ai - > dangerHitMap - > getOneTurnAccessibleObjects ( enemy ) ;
2021-05-16 14:13:35 +03:00
float objectValue = 0 ;
for ( auto obj : objectsUnderTreat )
{
2021-05-16 14:13:56 +03:00
vstd : : amax ( objectValue , getStrategicalValue ( obj ) ) ;
2021-05-16 14:13:35 +03:00
}
2022-09-10 06:38:46 +02:00
/*
1. If an enemy hero can attack nearby object , it ' s not useful to capture the object on our own .
Killing the hero is almost as important ( 0.9 ) as capturing the object itself .
2. The formula quickly approaches 1.0 as hero level increases ,
but higher level always means higher value and the minimal value for level 1 hero is 0.5
*/
2023-06-04 16:02:02 +03:00
return std : : min ( 1.5f , objectValue * 0.9f + ( 1.5f - ( 1.5f / ( 1 + enemy - > level ) ) ) ) ;
2021-05-16 14:13:35 +03:00
}
2020-05-04 18:58:43 +03:00
float RewardEvaluator : : getResourceRequirementStrength ( int resType ) const
2021-05-16 14:15:03 +03:00
{
2020-05-04 18:58:43 +03:00
TResources requiredResources = ai - > buildAnalyzer - > getResourcesRequiredNow ( ) ;
TResources dailyIncome = ai - > buildAnalyzer - > getDailyIncome ( ) ;
2021-05-16 14:15:03 +03:00
if ( requiredResources [ resType ] = = 0 )
return 0 ;
if ( dailyIncome [ resType ] = = 0 )
2021-05-16 14:19:00 +03:00
return 1.0f ;
2021-05-16 14:15:03 +03:00
2021-05-16 14:19:00 +03:00
float ratio = ( float ) requiredResources [ resType ] / dailyIncome [ resType ] / 2 ;
return std : : min ( ratio , 1.0f ) ;
2021-05-16 14:15:03 +03:00
}
2020-05-04 18:58:43 +03:00
float RewardEvaluator : : getTotalResourceRequirementStrength ( int resType ) const
2021-05-16 14:15:03 +03:00
{
2020-05-04 18:58:43 +03:00
TResources requiredResources = ai - > buildAnalyzer - > getTotalResourcesRequired ( ) ;
TResources dailyIncome = ai - > buildAnalyzer - > getDailyIncome ( ) ;
2021-05-16 14:15:03 +03:00
if ( requiredResources [ resType ] = = 0 )
return 0 ;
2021-05-16 14:19:00 +03:00
float ratio = dailyIncome [ resType ] = = 0
2023-07-29 18:54:20 +03:00
? ( float ) requiredResources [ resType ] / 10.0f
: ( float ) requiredResources [ resType ] / dailyIncome [ resType ] / 20.0f ;
2021-05-16 14:15:03 +03:00
2023-07-29 18:54:20 +03:00
return std : : min ( ratio , 2.0f ) ;
2021-05-16 14:15:03 +03:00
}
2023-06-11 19:21:50 +03:00
uint64_t RewardEvaluator : : townArmyGrowth ( const CGTownInstance * town ) const
{
uint64_t result = 0 ;
for ( auto creatureInfo : town - > creatures )
{
if ( creatureInfo . second . empty ( ) )
continue ;
auto creature = creatureInfo . second . back ( ) . toCreature ( ) ;
result + = creature - > getAIValue ( ) * town - > getGrowthInfo ( creature - > getLevel ( ) - 1 ) . totalGrowth ( ) ;
}
return result ;
}
2024-09-14 02:58:23 +02:00
float RewardEvaluator : : getManaRecoveryArmyReward ( const CGHeroInstance * hero ) const
2023-09-24 13:07:42 +03:00
{
return ai - > heroManager - > getMagicStrength ( hero ) * 10000 * ( 1.0f - std : : sqrt ( static_cast < float > ( hero - > mana ) / hero - > manaLimit ( ) ) ) ;
}
2024-04-20 15:28:42 +03:00
float RewardEvaluator : : getResourceRequirementStrength ( const TResources & res ) const
{
float sum = 0.0f ;
for ( TResources : : nziterator it ( res ) ; it . valid ( ) ; it + + )
{
//Evaluate resources used for construction. Gold is evaluated separately.
if ( it - > resType ! = EGameResID : : GOLD )
{
sum + = 0.1f * it - > resVal * getResourceRequirementStrength ( it - > resType )
+ 0.05f * it - > resVal * getTotalResourceRequirementStrength ( it - > resType ) ;
}
}
return sum ;
}
float RewardEvaluator : : getStrategicalValue ( const CGObjectInstance * target , const CGHeroInstance * hero ) const
2021-05-16 14:13:35 +03:00
{
if ( ! target )
return 0 ;
switch ( target - > ID )
{
2021-05-16 14:15:03 +03:00
case Obj : : MINE :
2023-10-28 12:27:10 +03:00
{
auto mine = dynamic_cast < const CGMine * > ( target ) ;
return mine - > producedResource = = EGameResID : : GOLD
2020-05-04 18:58:43 +03:00
? 0.5f
2023-10-28 12:27:10 +03:00
: 0.4f * getTotalResourceRequirementStrength ( mine - > producedResource ) + 0.1f * getResourceRequirementStrength ( mine - > producedResource ) ;
}
2021-05-16 14:15:03 +03:00
case Obj : : RESOURCE :
2023-10-28 12:27:10 +03:00
{
auto resource = dynamic_cast < const CGResource * > ( target ) ;
2024-04-20 15:28:42 +03:00
TResources res ;
res [ resource - > resourceID ( ) ] = resource - > amount ;
return getResourceRequirementStrength ( res ) ;
2023-10-28 12:27:10 +03:00
}
2021-05-16 14:15:03 +03:00
2021-05-16 14:13:35 +03:00
case Obj : : TOWN :
2023-02-28 09:07:59 +02:00
{
2021-05-16 14:57:36 +03:00
if ( ai - > buildAnalyzer - > getDevelopmentInfo ( ) . empty ( ) )
2023-06-11 19:21:50 +03:00
return 10.0f ;
2021-05-16 14:57:36 +03:00
2023-02-28 09:07:59 +02:00
auto town = dynamic_cast < const CGTownInstance * > ( target ) ;
2023-06-11 19:21:50 +03:00
if ( town - > getOwner ( ) = = ai - > playerID )
{
auto armyIncome = townArmyGrowth ( town ) ;
auto dailyIncome = town - > dailyIncome ( ) [ EGameResID : : GOLD ] ;
return std : : min ( 1.0f , std : : sqrt ( armyIncome / 40000.0f ) ) + std : : min ( 0.3f , dailyIncome / 10000.0f ) ;
}
2023-02-28 09:07:59 +02:00
auto fortLevel = town - > fortLevel ( ) ;
2023-07-30 18:02:56 +03:00
auto booster = isAnotherAi ( town , * ai - > cb ) ? 0.4f : 1.0f ;
2023-02-28 09:07:59 +02:00
2023-07-30 18:02:56 +03:00
if ( town - > hasCapitol ( ) )
return booster * 1.5 ;
2023-02-28 09:07:59 +02:00
if ( fortLevel < CGTownInstance : : CITADEL )
2023-07-30 18:02:56 +03:00
return booster * ( town - > hasFort ( ) ? 1.0 : 0.8 ) ;
2023-02-28 09:07:59 +02:00
else
2023-07-30 18:02:56 +03:00
return booster * ( fortLevel = = CGTownInstance : : CASTLE ? 1.4 : 1.2 ) ;
2023-02-28 09:07:59 +02:00
}
2021-05-16 14:13:35 +03:00
case Obj : : HERO :
2021-05-16 15:08:56 +03:00
return ai - > cb - > getPlayerRelations ( target - > tempOwner , ai - > playerID ) = = PlayerRelations : : ENEMIES
2021-05-16 14:13:35 +03:00
? getEnemyHeroStrategicalValue ( dynamic_cast < const CGHeroInstance * > ( target ) )
: 0 ;
2021-05-16 14:19:00 +03:00
2023-12-23 15:53:51 +02:00
case Obj : : KEYMASTER :
return 0.6f ;
2021-05-16 14:13:35 +03:00
default :
2024-04-20 15:28:42 +03:00
break ;
2021-05-16 14:13:35 +03:00
}
2024-04-20 15:28:42 +03:00
auto rewardable = dynamic_cast < const Rewardable : : Interface * > ( target ) ;
if ( rewardable & & hero )
{
auto resourceReward = 0.0f ;
for ( int index : rewardable - > getAvailableRewards ( hero , Rewardable : : EEventType : : EVENT_FIRST_VISIT ) )
{
resourceReward + = getResourceRequirementStrength ( rewardable - > configuration . info [ index ] . reward . resources ) ;
}
return resourceReward ;
}
return 0 ;
2021-05-16 14:13:35 +03:00
}
2024-07-12 17:41:46 +02:00
float RewardEvaluator : : getConquestValue ( const CGObjectInstance * target ) const
{
if ( ! target )
return 0 ;
if ( target - > getOwner ( ) = = ai - > playerID )
return 0 ;
switch ( target - > ID )
{
case Obj : : TOWN :
{
if ( ai - > buildAnalyzer - > getDevelopmentInfo ( ) . empty ( ) )
return 10.0f ;
auto town = dynamic_cast < const CGTownInstance * > ( target ) ;
if ( town - > getOwner ( ) = = ai - > playerID )
{
auto armyIncome = townArmyGrowth ( town ) ;
auto dailyIncome = town - > dailyIncome ( ) [ EGameResID : : GOLD ] ;
return std : : min ( 1.0f , std : : sqrt ( armyIncome / 40000.0f ) ) + std : : min ( 0.3f , dailyIncome / 10000.0f ) ;
}
auto fortLevel = town - > fortLevel ( ) ;
auto booster = 1.0f ;
if ( town - > hasCapitol ( ) )
return booster * 1.5 ;
if ( fortLevel < CGTownInstance : : CITADEL )
return booster * ( town - > hasFort ( ) ? 1.0 : 0.8 ) ;
else
return booster * ( fortLevel = = CGTownInstance : : CASTLE ? 1.4 : 1.2 ) ;
}
case Obj : : HERO :
return ai - > cb - > getPlayerRelations ( target - > tempOwner , ai - > playerID ) = = PlayerRelations : : ENEMIES
? getEnemyHeroStrategicalValue ( dynamic_cast < const CGHeroInstance * > ( target ) )
: 0 ;
case Obj : : KEYMASTER :
return 0.6f ;
default :
return 0 ;
}
}
2023-09-30 18:47:47 +03:00
float RewardEvaluator : : evaluateWitchHutSkillScore ( const CGObjectInstance * hut , const CGHeroInstance * hero , HeroRole role ) const
2021-05-15 22:02:27 +03:00
{
2023-09-30 18:47:47 +03:00
auto rewardable = dynamic_cast < const CRewardableObject * > ( hut ) ;
assert ( rewardable ) ;
auto skill = SecondarySkill ( * rewardable - > configuration . getVariable ( " secondarySkill " , " gainedSkill " ) ) ;
2021-05-15 22:02:27 +03:00
if ( ! hut - > wasVisited ( hero - > tempOwner ) )
return role = = HeroRole : : SCOUT ? 2 : 0 ;
2023-10-05 16:13:52 +03:00
if ( hero - > getSecSkillLevel ( skill ) ! = MasteryLevel : : NONE
2021-05-15 22:02:27 +03:00
| | hero - > secSkills . size ( ) > = GameConstants : : SKILL_PER_HERO )
return 0 ;
2020-05-04 18:58:43 +03:00
auto score = ai - > heroManager - > evaluateSecSkill ( skill , hero ) ;
2021-05-15 22:02:27 +03:00
return score > = 2 ? ( role = = HeroRole : : MAIN ? 10 : 4 ) : score ;
}
2020-05-04 18:58:43 +03:00
float RewardEvaluator : : getSkillReward ( const CGObjectInstance * target , const CGHeroInstance * hero , HeroRole role ) const
2021-05-15 22:02:27 +03:00
{
2021-05-16 13:53:05 +03:00
const float enemyHeroEliminationSkillRewardRatio = 0.5f ;
2021-05-15 22:02:27 +03:00
if ( ! target )
return 0 ;
switch ( target - > ID )
{
case Obj : : STAR_AXIS :
case Obj : : SCHOLAR :
case Obj : : SCHOOL_OF_MAGIC :
case Obj : : SCHOOL_OF_WAR :
case Obj : : GARDEN_OF_REVELATION :
case Obj : : MARLETTO_TOWER :
case Obj : : MERCENARY_CAMP :
2021-05-16 14:15:03 +03:00
case Obj : : TREE_OF_KNOWLEDGE :
2021-05-15 22:02:27 +03:00
return 1 ;
2021-05-16 14:15:03 +03:00
case Obj : : LEARNING_STONE :
2021-05-16 14:20:09 +03:00
return 1.0f / std : : sqrt ( hero - > level ) ;
2021-05-15 22:02:27 +03:00
case Obj : : ARENA :
return 2 ;
2023-07-30 11:33:52 +03:00
case Obj : : SHRINE_OF_MAGIC_INCANTATION :
2024-04-20 15:28:42 +03:00
return 0.25f ;
2023-07-30 11:33:52 +03:00
case Obj : : SHRINE_OF_MAGIC_GESTURE :
2024-04-20 15:28:42 +03:00
return 1.0f ;
2023-07-30 11:33:52 +03:00
case Obj : : SHRINE_OF_MAGIC_THOUGHT :
2024-04-20 15:28:42 +03:00
return 2.0f ;
2021-05-15 22:02:27 +03:00
case Obj : : LIBRARY_OF_ENLIGHTENMENT :
return 8 ;
case Obj : : WITCH_HUT :
2023-09-30 18:47:47 +03:00
return evaluateWitchHutSkillScore ( target , hero , role ) ;
2022-09-09 19:20:41 +02:00
case Obj : : PANDORAS_BOX :
//Can contains experience, spells, or skills (only on custom maps)
return 2.5f ;
2021-05-16 13:53:05 +03:00
case Obj : : HERO :
2021-05-16 15:08:56 +03:00
return ai - > cb - > getPlayerRelations ( target - > tempOwner , ai - > playerID ) = = PlayerRelations : : ENEMIES
2021-05-16 13:53:05 +03:00
? enemyHeroEliminationSkillRewardRatio * dynamic_cast < const CGHeroInstance * > ( target ) - > level
: 0 ;
2024-04-20 15:28:42 +03:00
2021-05-15 22:02:27 +03:00
default :
2024-04-20 15:28:42 +03:00
break ;
2021-05-15 22:02:27 +03:00
}
2024-04-20 15:28:42 +03:00
auto rewardable = dynamic_cast < const Rewardable : : Interface * > ( target ) ;
if ( rewardable )
{
auto totalValue = 0.0f ;
for ( int index : rewardable - > getAvailableRewards ( hero , Rewardable : : EEventType : : EVENT_FIRST_VISIT ) )
{
auto & info = rewardable - > configuration . info [ index ] ;
auto rewardValue = 0.0f ;
if ( ! info . reward . spells . empty ( ) )
{
for ( auto spellID : info . reward . spells )
{
const spells : : Spell * spell = VLC - > spells ( ) - > getById ( spellID ) ;
if ( hero - > canLearnSpell ( spell ) & & ! hero - > spellbookContainsSpell ( spellID ) )
{
rewardValue + = std : : sqrt ( spell - > getLevel ( ) ) / 4.0f ;
}
}
totalValue + = rewardValue / info . reward . spells . size ( ) ;
}
if ( ! info . reward . primary . empty ( ) )
{
for ( auto value : info . reward . primary )
{
totalValue + = value ;
}
}
}
return totalValue ;
}
return 0 ;
2021-05-15 22:02:27 +03:00
}
2023-02-28 09:07:59 +02:00
const HitMapInfo & RewardEvaluator : : getEnemyHeroDanger ( const int3 & tile , uint8_t turn ) const
2021-05-16 14:55:33 +03:00
{
2023-10-27 19:32:52 +00:00
auto & treatNode = ai - > dangerHitMap - > getTileThreat ( tile ) ;
2021-05-16 14:55:33 +03:00
if ( treatNode . maximumDanger . danger = = 0 )
2023-10-27 19:32:52 +00:00
return HitMapInfo : : NoThreat ;
2021-05-16 15:01:37 +03:00
if ( treatNode . maximumDanger . turn < = turn )
2023-02-28 09:07:59 +02:00
return treatNode . maximumDanger ;
2021-05-16 15:01:37 +03:00
2023-10-27 19:32:52 +00:00
return treatNode . fastestDanger . turn < = turn ? treatNode . fastestDanger : HitMapInfo : : NoThreat ;
2021-05-16 14:55:33 +03:00
}
2021-05-16 13:53:05 +03:00
int32_t getArmyCost ( const CArmedInstance * army )
{
int32_t value = 0 ;
for ( auto stack : army - > Slots ( ) )
{
2024-07-15 18:12:52 +02:00
value + = stack . second - > getCreatureID ( ) . toCreature ( ) - > getFullRecruitCost ( ) . marketValue ( ) * stack . second - > count ;
2021-05-16 13:53:05 +03:00
}
return value ;
}
2020-05-04 18:58:43 +03:00
int32_t RewardEvaluator : : getGoldReward ( const CGObjectInstance * target , const CGHeroInstance * hero ) const
2021-05-15 19:23:38 +03:00
{
if ( ! target )
return 0 ;
2023-07-30 11:33:52 +03:00
auto relations = ai - > cb - > getPlayerRelations ( target - > tempOwner , hero - > tempOwner ) ;
2021-05-15 19:23:38 +03:00
const int dailyIncomeMultiplier = 5 ;
2021-05-16 13:53:05 +03:00
const float enemyArmyEliminationGoldRewardRatio = 0.2f ;
const int32_t heroEliminationBonus = GameConstants : : HERO_GOLD_COST / 2 ;
2021-05-15 19:23:38 +03:00
switch ( target - > ID )
{
case Obj : : RESOURCE :
2023-10-28 12:27:10 +03:00
{
auto * res = dynamic_cast < const CGResource * > ( target ) ;
2024-04-14 15:23:44 +03:00
return res & & res - > resourceID ( ) = = GameResID : : GOLD ? 600 : 100 ;
2023-10-28 12:27:10 +03:00
}
2021-05-15 19:23:38 +03:00
case Obj : : TREASURE_CHEST :
return 1500 ;
case Obj : : WATER_WHEEL :
return 1000 ;
case Obj : : TOWN :
2021-05-16 15:08:56 +03:00
return dailyIncomeMultiplier * estimateTownIncome ( ai - > cb . get ( ) , target , hero ) ;
2021-05-15 19:23:38 +03:00
case Obj : : MINE :
case Obj : : ABANDONED_MINE :
2023-10-28 12:27:10 +03:00
{
auto * mine = dynamic_cast < const CGMine * > ( target ) ;
return dailyIncomeMultiplier * ( mine - > producedResource = = GameResID : : GOLD ? 1000 : 75 ) ;
}
2022-09-09 19:20:41 +02:00
case Obj : : PANDORAS_BOX :
2023-03-11 11:42:44 +02:00
return 2500 ;
2022-09-09 19:21:36 +02:00
case Obj : : PRISON :
//Objectively saves us 2500 to hire hero
return GameConstants : : HERO_GOLD_COST ;
2021-05-16 13:53:05 +03:00
case Obj : : HERO :
2023-07-30 11:33:52 +03:00
return relations = = PlayerRelations : : ENEMIES
2021-05-16 13:53:05 +03:00
? heroEliminationBonus + enemyArmyEliminationGoldRewardRatio * getArmyCost ( dynamic_cast < const CGHeroInstance * > ( target ) )
: 0 ;
2021-05-15 19:23:38 +03:00
default :
2024-04-20 15:28:42 +03:00
break ;
2021-05-15 19:23:38 +03:00
}
2024-04-20 15:28:42 +03:00
auto rewardable = dynamic_cast < const Rewardable : : Interface * > ( target ) ;
if ( rewardable )
{
auto goldReward = 0 ;
for ( int index : rewardable - > getAvailableRewards ( hero , Rewardable : : EEventType : : EVENT_FIRST_VISIT ) )
{
auto & info = rewardable - > configuration . info [ index ] ;
goldReward + = getResourcesGoldReward ( info . reward . resources ) ;
}
return goldReward ;
}
return 0 ;
2021-05-15 19:23:38 +03:00
}
2021-05-16 14:56:35 +03:00
class HeroExchangeEvaluator : public IEvaluationContextBuilder
{
public :
2024-02-13 15:21:30 +01:00
void buildEvaluationContext ( EvaluationContext & evaluationContext , Goals : : TSubgoal task ) const override
2021-05-16 14:56:35 +03:00
{
if ( task - > goalType ! = Goals : : HERO_EXCHANGE )
return ;
Goals : : HeroExchange & heroExchange = dynamic_cast < Goals : : HeroExchange & > ( * task ) ;
2024-03-31 18:39:00 +03:00
uint64_t armyStrength = heroExchange . getReinforcementArmyStrength ( evaluationContext . evaluator . ai ) ;
2021-05-16 14:56:35 +03:00
2024-04-14 15:23:44 +03:00
evaluationContext . addNonCriticalStrategicalValue ( 2.0f * armyStrength / ( float ) heroExchange . hero - > getArmyStrength ( ) ) ;
2024-07-24 21:22:14 +02:00
evaluationContext . conquestValue + = 2.0f * armyStrength / ( float ) heroExchange . hero - > getArmyStrength ( ) ;
2024-04-14 15:23:44 +03:00
evaluationContext . heroRole = evaluationContext . evaluator . ai - > heroManager - > getHeroRole ( heroExchange . hero ) ;
2024-08-30 16:46:36 +02:00
evaluationContext . isExchange = true ;
2021-05-16 14:56:35 +03:00
}
} ;
class ArmyUpgradeEvaluator : public IEvaluationContextBuilder
{
public :
2024-02-13 15:21:30 +01:00
void buildEvaluationContext ( EvaluationContext & evaluationContext , Goals : : TSubgoal task ) const override
2021-05-16 14:56:35 +03:00
{
if ( task - > goalType ! = Goals : : ARMY_UPGRADE )
return ;
Goals : : ArmyUpgrade & armyUpgrade = dynamic_cast < Goals : : ArmyUpgrade & > ( * task ) ;
uint64_t upgradeValue = armyUpgrade . getUpgradeValue ( ) ;
evaluationContext . armyReward + = upgradeValue ;
2023-06-04 16:02:02 +03:00
evaluationContext . addNonCriticalStrategicalValue ( upgradeValue / ( float ) armyUpgrade . hero - > getArmyStrength ( ) ) ;
2024-09-14 02:58:23 +02:00
evaluationContext . isArmyUpgrade = true ;
2021-05-16 14:56:35 +03:00
}
} ;
2024-05-19 10:04:45 +03:00
class ExplorePointEvaluator : public IEvaluationContextBuilder
{
public :
void buildEvaluationContext ( EvaluationContext & evaluationContext , Goals : : TSubgoal task ) const override
{
if ( task - > goalType ! = Goals : : EXPLORATION_POINT )
return ;
int tilesDiscovered = task - > value ;
evaluationContext . addNonCriticalStrategicalValue ( 0.03f * tilesDiscovered ) ;
2024-09-14 02:58:23 +02:00
for ( auto obj : evaluationContext . evaluator . ai - > cb - > getVisitableObjs ( task - > tile ) )
{
switch ( obj - > ID . num )
{
case Obj : : MONOLITH_ONE_WAY_ENTRANCE :
case Obj : : MONOLITH_TWO_WAY :
case Obj : : SUBTERRANEAN_GATE :
evaluationContext . explorePriority = 1 ;
break ;
case Obj : : REDWOOD_OBSERVATORY :
case Obj : : PILLAR_OF_FIRE :
evaluationContext . explorePriority = 2 ;
break ;
}
}
2024-11-07 17:37:18 +01:00
if ( evaluationContext . evaluator . ai - > cb - > getTile ( task - > tile ) - > roadType ! = RoadId : : NO_ROAD )
2024-10-20 23:32:39 +02:00
evaluationContext . explorePriority = 1 ;
2024-09-14 02:58:23 +02:00
if ( evaluationContext . explorePriority = = 0 )
evaluationContext . explorePriority = 3 ;
2024-05-19 10:04:45 +03:00
}
} ;
2023-09-24 13:07:42 +03:00
class StayAtTownManaRecoveryEvaluator : public IEvaluationContextBuilder
{
public :
2024-09-29 01:23:13 +02:00
void buildEvaluationContext ( EvaluationContext & evaluationContext , Goals : : TSubgoal task ) const override
2023-09-24 13:07:42 +03:00
{
2024-09-29 01:23:13 +02:00
if ( task - > goalType ! = Goals : : STAY_AT_TOWN )
2023-09-24 13:07:42 +03:00
return ;
2024-09-29 01:23:13 +02:00
Goals : : StayAtTown & stayAtTown = dynamic_cast < Goals : : StayAtTown & > ( * task ) ;
2023-09-24 13:07:42 +03:00
2024-04-14 15:23:44 +03:00
evaluationContext . armyReward + = evaluationContext . evaluator . getManaRecoveryArmyReward ( stayAtTown . getHero ( ) ) ;
2024-09-06 22:14:59 +02:00
if ( evaluationContext . armyReward = = 0 )
evaluationContext . isDefend = true ;
2024-09-29 01:23:13 +02:00
else
{
evaluationContext . movementCost + = stayAtTown . getMovementWasted ( ) ;
evaluationContext . movementCostByRole [ evaluationContext . heroRole ] + = stayAtTown . getMovementWasted ( ) ;
}
2023-09-24 13:07:42 +03:00
}
} ;
2023-02-28 09:07:59 +02:00
void addTileDanger ( EvaluationContext & evaluationContext , const int3 & tile , uint8_t turn , uint64_t ourStrength )
{
HitMapInfo enemyDanger = evaluationContext . evaluator . getEnemyHeroDanger ( tile , turn ) ;
if ( enemyDanger . danger )
{
auto dangerRatio = enemyDanger . danger / ( double ) ourStrength ;
vstd : : amax ( evaluationContext . enemyHeroDangerRatio , dangerRatio ) ;
2024-07-15 18:12:52 +02:00
vstd : : amax ( evaluationContext . threat , enemyDanger . threat ) ;
2023-02-28 09:07:59 +02:00
}
}
2021-05-16 15:00:24 +03:00
class DefendTownEvaluator : public IEvaluationContextBuilder
{
public :
2024-02-13 15:21:30 +01:00
void buildEvaluationContext ( EvaluationContext & evaluationContext , Goals : : TSubgoal task ) const override
2021-05-16 15:00:24 +03:00
{
if ( task - > goalType ! = Goals : : DEFEND_TOWN )
return ;
Goals : : DefendTown & defendTown = dynamic_cast < Goals : : DefendTown & > ( * task ) ;
const CGTownInstance * town = defendTown . town ;
auto & treat = defendTown . getTreat ( ) ;
2023-06-11 19:21:50 +03:00
auto strategicalValue = evaluationContext . evaluator . getStrategicalValue ( town ) ;
2021-05-16 15:00:24 +03:00
float multiplier = 1 ;
if ( treat . turn < defendTown . getTurn ( ) )
multiplier / = 1 + ( defendTown . getTurn ( ) - treat . turn ) ;
2023-06-04 16:02:02 +03:00
multiplier / = 1.0f + treat . turn / 5.0f ;
2023-07-30 18:02:56 +03:00
if ( defendTown . getTurn ( ) > 0 & & defendTown . isCounterAttack ( ) )
2023-06-11 19:21:50 +03:00
{
2023-07-09 11:35:50 +02:00
auto ourSpeed = defendTown . hero - > movementPointsLimit ( true ) ;
2024-04-14 15:23:44 +03:00
auto enemySpeed = treat . hero . get ( evaluationContext . evaluator . ai - > cb . get ( ) ) - > movementPointsLimit ( true ) ;
2023-06-11 19:21:50 +03:00
if ( enemySpeed > ourSpeed ) multiplier * = 0.7f ;
}
auto dailyIncome = town - > dailyIncome ( ) [ EGameResID : : GOLD ] ;
auto armyGrowth = evaluationContext . evaluator . townArmyGrowth ( town ) ;
evaluationContext . armyGrowth + = armyGrowth * multiplier ;
2021-05-16 15:00:24 +03:00
evaluationContext . goldReward + = dailyIncome * 5 * multiplier ;
2023-07-28 14:17:01 +03:00
if ( evaluationContext . evaluator . ai - > buildAnalyzer - > getDevelopmentInfo ( ) . size ( ) = = 1 )
vstd : : amax ( evaluationContext . strategicalValue , 2.5f * multiplier * strategicalValue ) ;
else
evaluationContext . addNonCriticalStrategicalValue ( 1.7f * multiplier * strategicalValue ) ;
2024-08-11 18:21:24 +02:00
evaluationContext . defenseValue = town - > fortLevel ( ) ;
2024-07-12 23:36:41 +02:00
evaluationContext . isDefend = true ;
2024-08-14 22:53:48 +02:00
evaluationContext . threatTurns = treat . turn ;
2024-07-12 23:36:41 +02:00
2021-05-16 15:01:37 +03:00
vstd : : amax ( evaluationContext . danger , defendTown . getTreat ( ) . danger ) ;
2023-02-28 09:07:59 +02:00
addTileDanger ( evaluationContext , town - > visitablePos ( ) , defendTown . getTurn ( ) , defendTown . getDefenceStrength ( ) ) ;
2021-05-16 15:00:24 +03:00
}
} ;
2021-05-16 14:15:03 +03:00
class ExecuteHeroChainEvaluationContextBuilder : public IEvaluationContextBuilder
{
2021-05-16 15:08:56 +03:00
private :
const Nullkiller * ai ;
2021-05-16 14:15:03 +03:00
public :
2021-05-16 15:08:56 +03:00
ExecuteHeroChainEvaluationContextBuilder ( const Nullkiller * ai ) : ai ( ai ) { }
2024-02-13 15:21:30 +01:00
void buildEvaluationContext ( EvaluationContext & evaluationContext , Goals : : TSubgoal task ) const override
2021-05-16 14:15:03 +03:00
{
2021-05-16 14:38:53 +03:00
if ( task - > goalType ! = Goals : : EXECUTE_HERO_CHAIN )
return ;
2021-05-16 14:15:12 +03:00
Goals : : ExecuteHeroChain & chain = dynamic_cast < Goals : : ExecuteHeroChain & > ( * task ) ;
2021-05-16 14:38:53 +03:00
const AIPath & path = chain . getPath ( ) ;
2024-12-12 14:58:29 +01:00
if ( vstd : : isAlmostZero ( path . movementCost ( ) ) )
2024-12-11 13:05:51 +01:00
return ;
2021-05-16 14:38:53 +03:00
vstd : : amax ( evaluationContext . danger , path . getTotalDanger ( ) ) ;
evaluationContext . movementCost + = path . movementCost ( ) ;
evaluationContext . closestWayRatio = chain . closestWayRatio ;
2021-05-16 14:45:12 +03:00
std : : map < const CGHeroInstance * , float > costsPerHero ;
2021-05-16 14:38:53 +03:00
for ( auto & node : path . nodes )
{
2021-05-16 14:45:12 +03:00
vstd : : amax ( costsPerHero [ node . targetHero ] , node . cost ) ;
2024-07-15 18:12:52 +02:00
if ( node . layer = = EPathfindingLayer : : SAIL )
evaluationContext . involvesSailing = true ;
2021-05-16 14:45:12 +03:00
}
2024-12-03 23:09:13 +01:00
float highestCostForSingleHero = 0 ;
2021-05-16 14:45:12 +03:00
for ( auto pair : costsPerHero )
{
2020-05-04 18:58:43 +03:00
auto role = evaluationContext . evaluator . ai - > heroManager - > getHeroRole ( pair . first ) ;
2021-05-16 14:45:12 +03:00
evaluationContext . movementCostByRole [ role ] + = pair . second ;
2024-12-03 23:09:13 +01:00
if ( pair . second > highestCostForSingleHero )
highestCostForSingleHero = pair . second ;
2021-05-16 14:38:53 +03:00
}
2024-12-03 23:09:13 +01:00
if ( highestCostForSingleHero > 1 & & costsPerHero . size ( ) > 1 )
{
//Chains that involve more than 1 hero doing something for more than a turn are too expensive in my book. They often involved heroes doing nothing just standing there waiting to fulfill their part of the chain.
return ;
}
evaluationContext . movementCost * = costsPerHero . size ( ) ; //further deincentivise chaining as it often involves bringing back the army afterwards
2021-05-16 14:15:03 +03:00
2024-04-14 15:23:44 +03:00
auto hero = task - > hero ;
2021-05-16 14:15:03 +03:00
bool checkGold = evaluationContext . danger = = 0 ;
2021-05-16 14:38:53 +03:00
auto army = path . heroArmy ;
2021-05-16 14:15:03 +03:00
2021-05-16 15:08:56 +03:00
const CGObjectInstance * target = ai - > cb - > getObj ( ( ObjectInstanceID ) task - > objid , false ) ;
2024-04-14 15:23:44 +03:00
auto heroRole = evaluationContext . evaluator . ai - > heroManager - > getHeroRole ( hero ) ;
2023-06-04 16:02:02 +03:00
if ( heroRole = = HeroRole : : MAIN )
evaluationContext . heroRole = heroRole ;
2021-05-16 15:01:07 +03:00
2023-07-30 11:33:52 +03:00
if ( target )
2021-05-16 15:01:07 +03:00
{
evaluationContext . goldReward + = evaluationContext . evaluator . getGoldReward ( target , hero ) ;
evaluationContext . armyReward + = evaluationContext . evaluator . getArmyReward ( target , hero , army , checkGold ) ;
2023-05-14 09:17:15 +03:00
evaluationContext . armyGrowth + = evaluationContext . evaluator . getArmyGrowth ( target , hero , army ) ;
2023-06-04 16:02:02 +03:00
evaluationContext . skillReward + = evaluationContext . evaluator . getSkillReward ( target , hero , heroRole ) ;
evaluationContext . addNonCriticalStrategicalValue ( evaluationContext . evaluator . getStrategicalValue ( target ) ) ;
2024-07-12 17:41:46 +02:00
evaluationContext . conquestValue + = evaluationContext . evaluator . getConquestValue ( target ) ;
2024-09-22 13:06:07 +02:00
if ( target - > ID = = Obj : : HERO )
evaluationContext . isHero = true ;
2024-12-12 13:40:50 +01:00
if ( target - > getOwner ( ) . isValidPlayer ( ) & & ai - > cb - > getPlayerRelations ( ai - > playerID , target - > getOwner ( ) ) = = PlayerRelations : : ENEMIES )
2024-09-23 01:25:58 +02:00
evaluationContext . isEnemy = true ;
2021-05-16 15:01:07 +03:00
evaluationContext . goldCost + = evaluationContext . evaluator . getGoldCost ( target , hero , army ) ;
2024-11-26 15:39:35 +01:00
if ( evaluationContext . danger > 0 )
evaluationContext . skillReward + = ( float ) evaluationContext . danger / ( float ) hero - > getArmyStrength ( ) ;
2021-05-16 15:01:07 +03:00
}
2024-12-12 11:07:56 +01:00
evaluationContext . armyInvolvement + = army - > getArmyCost ( ) ;
2021-05-16 15:01:07 +03:00
2024-09-27 19:38:07 +02:00
vstd : : amax ( evaluationContext . armyLossPersentage , ( float ) path . getTotalArmyLoss ( ) / ( float ) army - > getArmyStrength ( ) ) ;
2023-02-28 09:07:59 +02:00
addTileDanger ( evaluationContext , path . targetTile ( ) , path . turn ( ) , path . getHeroStrength ( ) ) ;
2021-05-16 14:38:53 +03:00
vstd : : amax ( evaluationContext . turn , path . turn ( ) ) ;
2021-05-16 14:15:03 +03:00
}
} ;
2021-05-16 14:45:12 +03:00
class ClusterEvaluationContextBuilder : public IEvaluationContextBuilder
{
public :
2022-09-22 11:49:55 +03:00
ClusterEvaluationContextBuilder ( const Nullkiller * ai ) { }
2021-05-16 15:08:56 +03:00
2024-02-13 15:21:30 +01:00
void buildEvaluationContext ( EvaluationContext & evaluationContext , Goals : : TSubgoal task ) const override
2021-05-16 14:45:12 +03:00
{
if ( task - > goalType ! = Goals : : UNLOCK_CLUSTER )
return ;
Goals : : UnlockCluster & clusterGoal = dynamic_cast < Goals : : UnlockCluster & > ( * task ) ;
std : : shared_ptr < ObjectCluster > cluster = clusterGoal . getCluster ( ) ;
2024-04-14 15:23:44 +03:00
auto hero = clusterGoal . hero ;
auto role = evaluationContext . evaluator . ai - > heroManager - > getHeroRole ( hero ) ;
2021-05-16 14:45:12 +03:00
2024-04-14 15:23:44 +03:00
std : : vector < std : : pair < ObjectInstanceID , ClusterObjectInfo > > objects ( cluster - > objects . begin ( ) , cluster - > objects . end ( ) ) ;
2021-05-16 14:45:12 +03:00
2024-04-14 15:23:44 +03:00
std : : sort ( objects . begin ( ) , objects . end ( ) , [ ] ( std : : pair < ObjectInstanceID , ClusterObjectInfo > o1 , std : : pair < ObjectInstanceID , ClusterObjectInfo > o2 ) - > bool
2021-05-16 14:45:12 +03:00
{
return o1 . second . priority > o2 . second . priority ;
} ) ;
int boost = 1 ;
2024-04-14 15:23:44 +03:00
for ( auto & objInfo : objects )
2021-05-16 14:45:12 +03:00
{
2024-04-14 15:23:44 +03:00
auto target = evaluationContext . evaluator . ai - > cb - > getObj ( objInfo . first ) ;
2021-05-16 14:45:12 +03:00
bool checkGold = objInfo . second . danger = = 0 ;
auto army = hero ;
2020-05-04 18:58:43 +03:00
evaluationContext . goldReward + = evaluationContext . evaluator . getGoldReward ( target , hero ) / boost ;
evaluationContext . armyReward + = evaluationContext . evaluator . getArmyReward ( target , hero , army , checkGold ) / boost ;
evaluationContext . skillReward + = evaluationContext . evaluator . getSkillReward ( target , hero , role ) / boost ;
2023-06-04 16:02:02 +03:00
evaluationContext . addNonCriticalStrategicalValue ( evaluationContext . evaluator . getStrategicalValue ( target ) / boost ) ;
2024-07-12 17:41:46 +02:00
evaluationContext . conquestValue + = evaluationContext . evaluator . getConquestValue ( target ) ;
2020-05-04 18:58:43 +03:00
evaluationContext . goldCost + = evaluationContext . evaluator . getGoldCost ( target , hero , army ) / boost ;
2021-05-16 14:45:12 +03:00
evaluationContext . movementCostByRole [ role ] + = objInfo . second . movementCost / boost ;
evaluationContext . movementCost + = objInfo . second . movementCost / boost ;
2021-05-16 14:45:45 +03:00
vstd : : amax ( evaluationContext . turn , objInfo . second . turn / boost ) ;
2021-05-16 14:45:12 +03:00
boost < < = 1 ;
if ( boost > 8 )
break ;
}
}
} ;
2023-02-28 09:07:59 +02:00
class ExchangeSwapTownHeroesContextBuilder : public IEvaluationContextBuilder
{
public :
2024-02-13 15:21:30 +01:00
void buildEvaluationContext ( EvaluationContext & evaluationContext , Goals : : TSubgoal task ) const override
2023-02-28 09:07:59 +02:00
{
if ( task - > goalType ! = Goals : : EXCHANGE_SWAP_TOWN_HEROES )
return ;
Goals : : ExchangeSwapTownHeroes & swapCommand = dynamic_cast < Goals : : ExchangeSwapTownHeroes & > ( * task ) ;
const CGHeroInstance * garrisonHero = swapCommand . getGarrisonHero ( ) ;
2024-08-29 21:01:06 +02:00
logAi - > trace ( " buildEvaluationContext ExchangeSwapTownHeroesContextBuilder %s affected objects: %d " , swapCommand . toString ( ) , swapCommand . getAffectedObjects ( ) . size ( ) ) ;
for ( auto obj : swapCommand . getAffectedObjects ( ) )
{
logAi - > trace ( " affected object: %s " , evaluationContext . evaluator . ai - > cb - > getObj ( obj ) - > getObjectName ( ) ) ;
}
if ( garrisonHero )
logAi - > debug ( " with %s and %d " , garrisonHero - > getNameTranslated ( ) , int ( swapCommand . getLockingReason ( ) ) ) ;
2023-02-28 09:07:59 +02:00
if ( garrisonHero & & swapCommand . getLockingReason ( ) = = HeroLockedReason : : DEFENCE )
{
auto defenderRole = evaluationContext . evaluator . ai - > heroManager - > getHeroRole ( garrisonHero ) ;
2023-06-21 20:38:26 +03:00
auto mpLeft = garrisonHero - > movementPointsRemaining ( ) / ( float ) garrisonHero - > movementPointsLimit ( true ) ;
2023-02-28 09:07:59 +02:00
2023-03-13 19:58:44 +02:00
evaluationContext . movementCost + = mpLeft ;
evaluationContext . movementCostByRole [ defenderRole ] + = mpLeft ;
evaluationContext . heroRole = defenderRole ;
2024-08-29 21:01:06 +02:00
evaluationContext . isDefend = true ;
evaluationContext . armyInvolvement = garrisonHero - > getArmyStrength ( ) ;
logAi - > debug ( " evaluationContext.isDefend: %d " , evaluationContext . isDefend ) ;
2023-02-28 09:07:59 +02:00
}
}
} ;
2023-06-04 16:02:02 +03:00
class DismissHeroContextBuilder : public IEvaluationContextBuilder
{
private :
const Nullkiller * ai ;
public :
DismissHeroContextBuilder ( const Nullkiller * ai ) : ai ( ai ) { }
2024-02-13 15:21:30 +01:00
void buildEvaluationContext ( EvaluationContext & evaluationContext , Goals : : TSubgoal task ) const override
2023-06-04 16:02:02 +03:00
{
if ( task - > goalType ! = Goals : : DISMISS_HERO )
return ;
Goals : : DismissHero & dismissCommand = dynamic_cast < Goals : : DismissHero & > ( * task ) ;
2024-04-14 15:23:44 +03:00
const CGHeroInstance * dismissedHero = dismissCommand . getHero ( ) ;
2023-06-04 16:02:02 +03:00
auto role = ai - > heroManager - > getHeroRole ( dismissedHero ) ;
2023-07-09 11:35:50 +02:00
auto mpLeft = dismissedHero - > movementPointsRemaining ( ) ;
2023-06-04 16:02:02 +03:00
evaluationContext . movementCost + = mpLeft ;
evaluationContext . movementCostByRole [ role ] + = mpLeft ;
evaluationContext . goldCost + = GameConstants : : HERO_GOLD_COST + getArmyCost ( dismissedHero ) ;
}
} ;
2021-05-16 14:15:03 +03:00
class BuildThisEvaluationContextBuilder : public IEvaluationContextBuilder
{
public :
2024-02-13 15:21:30 +01:00
void buildEvaluationContext ( EvaluationContext & evaluationContext , Goals : : TSubgoal task ) const override
2021-05-16 14:15:03 +03:00
{
2021-05-16 14:38:53 +03:00
if ( task - > goalType ! = Goals : : BUILD_STRUCTURE )
return ;
2021-05-16 14:15:03 +03:00
Goals : : BuildThis & buildThis = dynamic_cast < Goals : : BuildThis & > ( * task ) ;
auto & bi = buildThis . buildingInfo ;
2025-01-19 19:41:38 +00:00
evaluationContext . goldReward + = 7 * bi . dailyIncome . marketValue ( ) / 2 ; // 7 day income but half we already have
2021-05-16 14:15:03 +03:00
evaluationContext . heroRole = HeroRole : : MAIN ;
2021-05-16 14:38:53 +03:00
evaluationContext . movementCostByRole [ evaluationContext . heroRole ] + = bi . prerequisitesCount ;
2024-08-30 21:02:50 +02:00
int32_t cost = bi . buildCost [ EGameResID : : GOLD ] ;
2024-07-15 18:12:52 +02:00
evaluationContext . goldCost + = cost ;
2023-07-27 15:58:49 +03:00
evaluationContext . closestWayRatio = 1 ;
2024-09-01 13:47:30 +02:00
evaluationContext . buildingCost + = bi . buildCostWithPrerequisites ;
2024-07-19 15:25:24 +02:00
if ( bi . id = = BuildingID : : MARKETPLACE | | bi . dailyIncome [ EGameResID : : WOOD ] > 0 )
evaluationContext . isTradeBuilding = true ;
2025-01-10 22:39:20 +00:00
# if NKAI_TRACE_LEVEL >= 1
2024-07-19 15:25:24 +02:00
logAi - > trace ( " Building costs for %s : %s MarketValue: %d " , bi . toString ( ) , evaluationContext . buildingCost . toString ( ) , evaluationContext . buildingCost . marketValue ( ) ) ;
2025-01-10 22:39:20 +00:00
# endif
2021-05-16 14:15:03 +03:00
if ( bi . creatureID ! = CreatureID : : NONE )
{
2023-06-04 16:02:02 +03:00
evaluationContext . addNonCriticalStrategicalValue ( buildThis . townInfo . armyStrength / 50000.0 ) ;
2023-03-08 12:41:14 +02:00
2021-05-16 14:15:03 +03:00
if ( bi . baseCreatureID = = bi . creatureID )
{
2023-06-04 16:02:02 +03:00
evaluationContext . addNonCriticalStrategicalValue ( ( 0.5f + 0.1f * bi . creatureLevel ) / ( float ) bi . prerequisitesCount ) ;
2021-05-16 14:57:29 +03:00
evaluationContext . armyReward + = bi . armyStrength ;
2021-05-16 14:15:03 +03:00
}
2021-05-16 14:38:53 +03:00
else
{
2021-05-16 15:39:38 +03:00
auto potentialUpgradeValue = evaluationContext . evaluator . getUpgradeArmyReward ( buildThis . town , bi ) ;
2023-03-08 12:41:14 +02:00
2023-06-04 16:02:02 +03:00
evaluationContext . addNonCriticalStrategicalValue ( potentialUpgradeValue / 10000.0f / ( float ) bi . prerequisitesCount ) ;
2023-03-08 12:41:14 +02:00
evaluationContext . armyReward + = potentialUpgradeValue / ( float ) bi . prerequisitesCount ;
2021-05-16 14:38:53 +03:00
}
2021-05-16 14:15:03 +03:00
}
2021-05-16 14:57:29 +03:00
else if ( bi . id = = BuildingID : : CITADEL | | bi . id = = BuildingID : : CASTLE )
{
2023-06-04 16:02:02 +03:00
evaluationContext . addNonCriticalStrategicalValue ( buildThis . town - > creatures . size ( ) * 0.2f ) ;
2021-05-16 14:57:29 +03:00
evaluationContext . armyReward + = buildThis . townInfo . armyStrength / 2 ;
}
2023-07-27 15:58:49 +03:00
else if ( bi . id > = BuildingID : : MAGES_GUILD_1 & & bi . id < = BuildingID : : MAGES_GUILD_5 )
{
evaluationContext . skillReward + = 2 * ( bi . id - BuildingID : : MAGES_GUILD_1 ) ;
2024-08-11 21:59:06 +02:00
for ( auto hero : evaluationContext . evaluator . ai - > cb - > getHeroesInfo ( ) )
2024-08-11 18:21:24 +02:00
{
evaluationContext . armyInvolvement + = hero - > getArmyCost ( ) ;
}
2023-07-27 15:58:49 +03:00
}
2024-08-30 21:02:50 +02:00
int sameTownBonus = 0 ;
for ( auto town : evaluationContext . evaluator . ai - > cb - > getTownsInfo ( ) )
{
if ( buildThis . town - > getFaction ( ) = = town - > getFaction ( ) )
sameTownBonus + = town - > getTownLevel ( ) ;
}
evaluationContext . armyReward * = sameTownBonus ;
2023-07-27 15:58:49 +03:00
if ( evaluationContext . goldReward )
2021-05-16 14:19:00 +03:00
{
2024-04-16 21:10:15 +00:00
auto goldPressure = evaluationContext . evaluator . ai - > buildAnalyzer - > getGoldPressure ( ) ;
2023-03-08 12:41:14 +02:00
2024-04-16 21:10:15 +00:00
evaluationContext . addNonCriticalStrategicalValue ( evaluationContext . goldReward * goldPressure / 3500.0f / bi . prerequisitesCount ) ;
2023-03-08 12:41:14 +02:00
}
if ( bi . notEnoughRes & & bi . prerequisitesCount = = 1 )
{
2023-07-29 18:54:20 +03:00
evaluationContext . strategicalValue / = 3 ;
evaluationContext . movementCostByRole [ evaluationContext . heroRole ] + = 5 ;
evaluationContext . turn + = 5 ;
2021-05-16 14:19:00 +03:00
}
2021-05-16 14:15:03 +03:00
}
} ;
2020-05-04 18:58:43 +03:00
uint64_t RewardEvaluator : : getUpgradeArmyReward ( const CGTownInstance * town , const BuildingInfo & bi ) const
{
2024-10-05 19:37:52 +00:00
if ( ai - > buildAnalyzer - > hasAnyBuilding ( town - > getFactionID ( ) , bi . id ) )
2020-05-04 18:58:43 +03:00
return 0 ;
auto creaturesToUpgrade = ai - > armyManager - > getTotalCreaturesAvailable ( bi . baseCreatureID ) ;
auto upgradedPower = ai - > armyManager - > evaluateStackPower ( bi . creatureID . toCreature ( ) , creaturesToUpgrade . count ) ;
return upgradedPower - creaturesToUpgrade . power ;
}
PriorityEvaluator : : PriorityEvaluator ( const Nullkiller * ai )
: ai ( ai )
2021-05-16 14:15:03 +03:00
{
initVisitTile ( ) ;
2021-05-16 15:08:56 +03:00
evaluationContextBuilders . push_back ( std : : make_shared < ExecuteHeroChainEvaluationContextBuilder > ( ai ) ) ;
2021-05-16 14:38:53 +03:00
evaluationContextBuilders . push_back ( std : : make_shared < BuildThisEvaluationContextBuilder > ( ) ) ;
2021-05-16 15:08:56 +03:00
evaluationContextBuilders . push_back ( std : : make_shared < ClusterEvaluationContextBuilder > ( ai ) ) ;
2021-05-16 14:56:35 +03:00
evaluationContextBuilders . push_back ( std : : make_shared < HeroExchangeEvaluator > ( ) ) ;
2021-05-16 15:00:24 +03:00
evaluationContextBuilders . push_back ( std : : make_shared < ArmyUpgradeEvaluator > ( ) ) ;
2021-05-16 15:01:07 +03:00
evaluationContextBuilders . push_back ( std : : make_shared < DefendTownEvaluator > ( ) ) ;
2023-02-28 09:07:59 +02:00
evaluationContextBuilders . push_back ( std : : make_shared < ExchangeSwapTownHeroesContextBuilder > ( ) ) ;
2023-06-04 16:02:02 +03:00
evaluationContextBuilders . push_back ( std : : make_shared < DismissHeroContextBuilder > ( ai ) ) ;
2023-09-24 13:07:42 +03:00
evaluationContextBuilders . push_back ( std : : make_shared < StayAtTownManaRecoveryEvaluator > ( ) ) ;
2024-05-19 10:04:45 +03:00
evaluationContextBuilders . push_back ( std : : make_shared < ExplorePointEvaluator > ( ) ) ;
2021-05-16 14:15:03 +03:00
}
2021-05-16 14:38:53 +03:00
EvaluationContext PriorityEvaluator : : buildEvaluationContext ( Goals : : TSubgoal goal ) const
2021-05-16 14:15:03 +03:00
{
2021-05-16 14:38:53 +03:00
Goals : : TGoalVec parts ;
2020-05-04 18:58:43 +03:00
EvaluationContext context ( ai ) ;
2021-05-16 14:38:53 +03:00
if ( goal - > goalType = = Goals : : COMPOSITION )
{
2024-03-31 18:39:00 +03:00
parts = goal - > decompose ( ai ) ;
2021-05-16 14:38:53 +03:00
}
else
{
parts . push_back ( goal ) ;
}
2021-05-16 14:45:12 +03:00
for ( auto subgoal : parts )
2021-05-16 14:38:53 +03:00
{
2021-05-16 14:45:12 +03:00
context . goldCost + = subgoal - > goldCost ;
2024-07-19 15:25:24 +02:00
context . buildingCost + = subgoal - > buildingCost ;
2021-05-16 14:15:03 +03:00
2021-05-16 14:38:53 +03:00
for ( auto builder : evaluationContextBuilders )
{
2021-05-16 14:45:12 +03:00
builder - > buildEvaluationContext ( context , subgoal ) ;
2021-05-16 14:38:53 +03:00
}
}
2021-05-16 14:15:03 +03:00
2021-05-16 14:38:53 +03:00
return context ;
2021-05-16 14:15:03 +03:00
}
2024-07-12 17:41:46 +02:00
float PriorityEvaluator : : evaluate ( Goals : : TSubgoal task , int priorityTier )
2021-05-15 19:23:38 +03:00
{
2021-05-16 14:15:03 +03:00
auto evaluationContext = buildEvaluationContext ( task ) ;
2021-05-15 19:23:38 +03:00
2021-05-16 14:15:03 +03:00
int rewardType = ( evaluationContext . goldReward > 0 ? 1 : 0 )
+ ( evaluationContext . armyReward > 0 ? 1 : 0 )
+ ( evaluationContext . skillReward > 0 ? 1 : 0 )
+ ( evaluationContext . strategicalValue > 0 ? 1 : 0 ) ;
2023-07-02 10:27:30 +03:00
2023-07-30 11:33:52 +03:00
float goldRewardPerTurn = evaluationContext . goldReward / std : : log2f ( 2 + evaluationContext . movementCost * 10 ) ;
2021-05-15 19:23:38 +03:00
double result = 0 ;
2021-05-16 14:15:03 +03:00
2024-07-12 17:41:46 +02:00
if ( ai - > settings - > isUseFuzzy ( ) )
{
2024-09-05 16:22:25 +02:00
float fuzzyResult = 0 ;
try
{
armyLossPersentageVariable - > setValue ( evaluationContext . armyLossPersentage ) ;
heroRoleVariable - > setValue ( evaluationContext . heroRole ) ;
mainTurnDistanceVariable - > setValue ( evaluationContext . movementCostByRole [ HeroRole : : MAIN ] ) ;
scoutTurnDistanceVariable - > setValue ( evaluationContext . movementCostByRole [ HeroRole : : SCOUT ] ) ;
goldRewardVariable - > setValue ( goldRewardPerTurn ) ;
armyRewardVariable - > setValue ( evaluationContext . armyReward ) ;
armyGrowthVariable - > setValue ( evaluationContext . armyGrowth ) ;
skillRewardVariable - > setValue ( evaluationContext . skillReward ) ;
dangerVariable - > setValue ( evaluationContext . danger ) ;
rewardTypeVariable - > setValue ( rewardType ) ;
closestHeroRatioVariable - > setValue ( evaluationContext . closestWayRatio ) ;
strategicalValueVariable - > setValue ( evaluationContext . strategicalValue ) ;
goldPressureVariable - > setValue ( ai - > buildAnalyzer - > getGoldPressure ( ) ) ;
goldCostVariable - > setValue ( evaluationContext . goldCost / ( ( float ) ai - > getFreeResources ( ) [ EGameResID : : GOLD ] + ( float ) ai - > buildAnalyzer - > getDailyIncome ( ) [ EGameResID : : GOLD ] + 1.0f ) ) ;
turnVariable - > setValue ( evaluationContext . turn ) ;
fearVariable - > setValue ( evaluationContext . enemyHeroDangerRatio ) ;
engine - > process ( ) ;
fuzzyResult = value - > getValue ( ) ;
}
catch ( fl : : Exception & fe )
{
logAi - > error ( " evaluate VisitTile: %s " , fe . getWhat ( ) ) ;
}
2024-07-12 17:41:46 +02:00
result = fuzzyResult ;
2024-07-07 22:44:52 +02:00
}
else
2021-05-15 19:23:38 +03:00
{
2024-07-12 17:41:46 +02:00
float score = 0 ;
2024-11-29 15:57:23 +00:00
const bool amIInDanger = ai - > cb - > getTownsInfo ( ) . empty ( ) | | ( evaluationContext . isDefend & & evaluationContext . threatTurns = = 0 ) ;
const float maxWillingToLose = amIInDanger ? 1 : ai - > settings - > getMaxArmyLossTarget ( ) ;
2024-09-22 13:06:07 +02:00
2024-09-23 01:25:58 +02:00
bool arriveNextWeek = false ;
2024-12-11 14:47:08 +01:00
if ( ai - > cb - > getDate ( Date : : DAY_OF_WEEK ) + evaluationContext . turn > 7 & & priorityTier < PriorityTier : : FAR_KILL )
2024-09-23 01:25:58 +02:00
arriveNextWeek = true ;
2024-09-04 16:41:47 +02:00
# if NKAI_TRACE_LEVEL >= 2
2024-12-12 13:40:50 +01:00
logAi - > trace ( " BEFORE: priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, army-involvement: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, explorePriority: %d isDefend: %d isEnemy: %d arriveNextWeek: %d " ,
2024-07-19 15:25:24 +02:00
priorityTier ,
task - > toString ( ) ,
evaluationContext . armyLossPersentage ,
( int ) evaluationContext . turn ,
evaluationContext . movementCostByRole [ HeroRole : : MAIN ] ,
evaluationContext . movementCostByRole [ HeroRole : : SCOUT ] ,
2024-12-12 11:07:56 +01:00
evaluationContext . armyInvolvement ,
2024-07-19 15:25:24 +02:00
goldRewardPerTurn ,
evaluationContext . goldCost ,
evaluationContext . armyReward ,
2024-07-24 21:22:14 +02:00
evaluationContext . armyGrowth ,
2024-07-19 15:25:24 +02:00
evaluationContext . skillReward ,
evaluationContext . danger ,
2024-08-14 22:53:48 +02:00
evaluationContext . threatTurns ,
2024-07-19 15:25:24 +02:00
evaluationContext . threat ,
evaluationContext . heroRole = = HeroRole : : MAIN ? " main " : " scout " ,
evaluationContext . strategicalValue ,
2024-07-24 21:22:14 +02:00
evaluationContext . conquestValue ,
2024-07-19 15:25:24 +02:00
evaluationContext . closestWayRatio ,
evaluationContext . enemyHeroDangerRatio ,
2024-09-14 02:58:23 +02:00
evaluationContext . explorePriority ,
2024-12-12 13:40:50 +01:00
evaluationContext . isDefend ,
evaluationContext . isEnemy ,
arriveNextWeek ) ;
2024-09-04 16:41:47 +02:00
# endif
2024-07-19 15:25:24 +02:00
2024-07-15 18:12:52 +02:00
switch ( priorityTier )
2024-07-07 22:44:52 +02:00
{
2024-09-02 00:16:19 +02:00
case PriorityTier : : INSTAKILL : //Take towns / kill heroes in immediate reach
2024-07-15 18:12:52 +02:00
{
2024-08-30 18:05:47 +02:00
if ( evaluationContext . turn > 0 )
return 0 ;
2024-12-13 21:50:45 +01:00
if ( evaluationContext . movementCost > = 1 )
return 0 ;
2024-08-24 14:55:26 +02:00
if ( evaluationContext . conquestValue > 0 )
2024-12-03 23:09:13 +01:00
score = evaluationContext . armyInvolvement ;
2024-09-05 16:40:06 +02:00
if ( vstd : : isAlmostZero ( score ) | | ( evaluationContext . enemyHeroDangerRatio > 1 & & ( evaluationContext . turn > 0 | | evaluationContext . isExchange ) & & ! ai - > cb - > getTownsInfo ( ) . empty ( ) ) )
2024-07-15 18:12:52 +02:00
return 0 ;
2024-07-24 21:22:14 +02:00
if ( maxWillingToLose - evaluationContext . armyLossPersentage < 0 )
return 0 ;
2024-07-15 18:12:52 +02:00
if ( evaluationContext . movementCost > 0 )
score / = evaluationContext . movementCost ;
break ;
}
2024-09-02 00:16:19 +02:00
case PriorityTier : : INSTADEFEND : //Defend immediately threatened towns
2024-08-30 16:46:36 +02:00
{
if ( evaluationContext . isDefend & & evaluationContext . threatTurns = = 0 & & evaluationContext . turn = = 0 )
score = evaluationContext . armyInvolvement ;
2024-10-21 08:59:18 +02:00
if ( evaluationContext . isEnemy & & maxWillingToLose - evaluationContext . armyLossPersentage < 0 )
return 0 ;
2024-08-30 16:46:36 +02:00
break ;
}
2024-09-02 00:16:19 +02:00
case PriorityTier : : KILL : //Take towns / kill heroes that are further away
2024-12-11 14:47:08 +01:00
//FALL_THROUGH
case PriorityTier : : FAR_KILL :
2024-08-30 18:05:47 +02:00
{
2024-09-22 13:06:07 +02:00
if ( evaluationContext . turn > 0 & & evaluationContext . isHero )
return 0 ;
2024-09-23 01:25:58 +02:00
if ( arriveNextWeek & & evaluationContext . isEnemy )
return 0 ;
2024-10-20 23:32:39 +02:00
if ( evaluationContext . conquestValue > 0 )
2024-12-03 23:09:13 +01:00
score = evaluationContext . armyInvolvement ;
2024-09-05 16:40:06 +02:00
if ( vstd : : isAlmostZero ( score ) | | ( evaluationContext . enemyHeroDangerRatio > 1 & & ( evaluationContext . turn > 0 | | evaluationContext . isExchange ) & & ! ai - > cb - > getTownsInfo ( ) . empty ( ) ) )
2024-08-30 18:05:47 +02:00
return 0 ;
if ( maxWillingToLose - evaluationContext . armyLossPersentage < 0 )
return 0 ;
score * = evaluationContext . closestWayRatio ;
if ( evaluationContext . movementCost > 0 )
score / = evaluationContext . movementCost ;
break ;
}
2024-10-31 00:42:33 +01:00
case PriorityTier : : UPGRADE :
{
if ( ! evaluationContext . isArmyUpgrade )
return 0 ;
if ( evaluationContext . enemyHeroDangerRatio > 1 )
return 0 ;
if ( maxWillingToLose - evaluationContext . armyLossPersentage < 0 )
return 0 ;
2024-12-12 14:58:29 +01:00
if ( vstd : : isAlmostZero ( evaluationContext . armyLossPersentage ) & & evaluationContext . closestWayRatio < 1.0 )
2024-12-03 23:09:13 +01:00
return 0 ;
2024-10-31 00:42:33 +01:00
score = 1000 ;
if ( evaluationContext . movementCost > 0 )
score / = evaluationContext . movementCost ;
break ;
}
2024-10-20 23:32:39 +02:00
case PriorityTier : : HIGH_PRIO_EXPLORE :
{
if ( evaluationContext . enemyHeroDangerRatio > 1 )
return 0 ;
if ( evaluationContext . explorePriority ! = 1 )
return 0 ;
2024-10-21 09:21:00 +02:00
if ( maxWillingToLose - evaluationContext . armyLossPersentage < 0 )
return 0 ;
2024-12-12 14:58:29 +01:00
if ( vstd : : isAlmostZero ( evaluationContext . armyLossPersentage ) & & evaluationContext . closestWayRatio < 1.0 )
2024-12-03 23:09:13 +01:00
return 0 ;
2024-10-20 23:32:39 +02:00
score = 1000 ;
if ( evaluationContext . movementCost > 0 )
score / = evaluationContext . movementCost ;
break ;
}
2024-09-02 00:16:19 +02:00
case PriorityTier : : HUNTER_GATHER : //Collect guarded stuff
2024-12-11 14:47:08 +01:00
//FALL_THROUGH
case PriorityTier : : FAR_HUNTER_GATHER :
2024-07-15 18:12:52 +02:00
{
2024-08-11 18:21:24 +02:00
if ( evaluationContext . enemyHeroDangerRatio > 1 & & ! evaluationContext . isDefend )
2024-07-25 21:49:17 +02:00
return 0 ;
2024-07-19 15:25:24 +02:00
if ( evaluationContext . buildingCost . marketValue ( ) > 0 )
2024-07-15 18:12:52 +02:00
return 0 ;
2024-08-14 22:53:48 +02:00
if ( evaluationContext . isDefend & & ( evaluationContext . enemyHeroDangerRatio < 1 | | evaluationContext . threatTurns > 0 | | evaluationContext . turn > 0 ) )
return 0 ;
2024-09-14 02:58:23 +02:00
if ( evaluationContext . explorePriority = = 3 )
return 0 ;
if ( evaluationContext . isArmyUpgrade )
return 0 ;
2024-10-02 20:12:47 +02:00
if ( ( evaluationContext . enemyHeroDangerRatio > 0 & & arriveNextWeek ) | | evaluationContext . enemyHeroDangerRatio > 1 )
return 0 ;
if ( maxWillingToLose - evaluationContext . armyLossPersentage < 0 )
2024-09-29 01:23:13 +02:00
return 0 ;
2024-12-12 14:58:29 +01:00
if ( vstd : : isAlmostZero ( evaluationContext . armyLossPersentage ) & & evaluationContext . closestWayRatio < 1.0 )
2024-12-03 23:09:13 +01:00
return 0 ;
2024-07-15 18:12:52 +02:00
score + = evaluationContext . strategicalValue * 1000 ;
score + = evaluationContext . goldReward ;
score + = evaluationContext . skillReward * evaluationContext . armyInvolvement * ( 1 - evaluationContext . armyLossPersentage ) * 0.05 ;
score + = evaluationContext . armyReward ;
score + = evaluationContext . armyGrowth ;
score - = evaluationContext . goldCost ;
score - = evaluationContext . armyInvolvement * evaluationContext . armyLossPersentage ;
if ( score > 0 )
{
2024-10-02 20:12:47 +02:00
score = 1000 ;
2024-07-15 18:12:52 +02:00
if ( evaluationContext . movementCost > 0 )
score / = evaluationContext . movementCost ;
}
break ;
}
2024-09-14 02:58:23 +02:00
case PriorityTier : : LOW_PRIO_EXPLORE :
{
if ( evaluationContext . enemyHeroDangerRatio > 1 )
return 0 ;
if ( evaluationContext . explorePriority ! = 3 )
return 0 ;
2024-10-21 09:21:00 +02:00
if ( maxWillingToLose - evaluationContext . armyLossPersentage < 0 )
return 0 ;
2024-12-03 23:09:13 +01:00
if ( evaluationContext . closestWayRatio < 1.0 )
return 0 ;
2024-09-14 02:58:23 +02:00
score = 1000 ;
if ( evaluationContext . movementCost > 0 )
score / = evaluationContext . movementCost ;
break ;
}
2024-09-02 00:16:19 +02:00
case PriorityTier : : DEFEND : //Defend whatever if nothing else is to do
2024-08-24 14:55:26 +02:00
{
2024-08-30 16:46:36 +02:00
if ( evaluationContext . enemyHeroDangerRatio > 1 & & evaluationContext . isExchange )
return 0 ;
2024-09-14 02:58:23 +02:00
if ( evaluationContext . isDefend | | evaluationContext . isArmyUpgrade )
2024-12-03 23:09:13 +01:00
score = evaluationContext . armyInvolvement ;
2024-08-29 21:01:06 +02:00
score / = ( evaluationContext . turn + 1 ) ;
2024-08-24 14:55:26 +02:00
break ;
}
2024-09-02 00:16:19 +02:00
case PriorityTier : : BUILDINGS : //For buildings and buying army
2024-07-15 18:12:52 +02:00
{
2024-07-25 21:49:17 +02:00
if ( maxWillingToLose - evaluationContext . armyLossPersentage < 0 )
return 0 ;
2024-09-01 13:47:30 +02:00
//If we already have locked resources, we don't look at other buildings
if ( ai - > getLockedResources ( ) . marketValue ( ) > 0 )
return 0 ;
2024-07-15 18:12:52 +02:00
score + = evaluationContext . conquestValue * 1000 ;
score + = evaluationContext . strategicalValue * 1000 ;
score + = evaluationContext . goldReward ;
score + = evaluationContext . skillReward * evaluationContext . armyInvolvement * ( 1 - evaluationContext . armyLossPersentage ) * 0.05 ;
score + = evaluationContext . armyReward ;
score + = evaluationContext . armyGrowth ;
2024-07-19 15:25:24 +02:00
if ( evaluationContext . buildingCost . marketValue ( ) > 0 )
2024-07-15 18:12:52 +02:00
{
2024-07-19 15:25:24 +02:00
if ( ! evaluationContext . isTradeBuilding & & ai - > getFreeResources ( ) [ EGameResID : : WOOD ] - evaluationContext . buildingCost [ EGameResID : : WOOD ] < 5 & & ai - > buildAnalyzer - > getDailyIncome ( ) [ EGameResID : : WOOD ] < 1 )
{
logAi - > trace ( " Should make sure to build market-place instead of %s " , task - > toString ( ) ) ;
2024-08-11 18:21:24 +02:00
for ( auto town : ai - > cb - > getTownsInfo ( ) )
2024-07-19 15:25:24 +02:00
{
if ( ! town - > hasBuiltSomeTradeBuilding ( ) )
return 0 ;
}
}
2024-07-15 18:12:52 +02:00
score + = 1000 ;
2024-07-19 15:25:24 +02:00
auto resourcesAvailable = evaluationContext . evaluator . ai - > getFreeResources ( ) ;
auto income = ai - > buildAnalyzer - > getDailyIncome ( ) ;
2024-09-12 22:53:45 +02:00
if ( ai - > buildAnalyzer - > isGoldPressureHigh ( ) )
score / = evaluationContext . buildingCost . marketValue ( ) ;
2024-09-06 00:12:44 +02:00
if ( ! resourcesAvailable . canAfford ( evaluationContext . buildingCost ) )
2024-07-19 15:25:24 +02:00
{
TResources needed = evaluationContext . buildingCost - resourcesAvailable ;
needed . positive ( ) ;
2024-09-05 16:40:06 +02:00
int turnsTo = needed . maxPurchasableCount ( income ) ;
2024-07-19 15:25:24 +02:00
if ( turnsTo = = INT_MAX )
return 0 ;
else
score / = turnsTo ;
}
2024-07-15 18:12:52 +02:00
}
2024-08-07 01:36:27 +02:00
else
{
2024-09-05 16:40:06 +02:00
if ( evaluationContext . enemyHeroDangerRatio > 1 & & ! evaluationContext . isDefend & & vstd : : isAlmostZero ( evaluationContext . conquestValue ) )
2024-08-07 01:36:27 +02:00
return 0 ;
}
2024-07-15 18:12:52 +02:00
break ;
}
2024-07-07 22:44:52 +02:00
}
2024-07-15 18:12:52 +02:00
result = score ;
2024-09-02 01:37:21 +02:00
//TODO: Figure out the root cause for why evaluationContext.closestWayRatio has become -nan(ind).
if ( std : : isnan ( result ) )
return 0 ;
2021-05-15 19:23:38 +03:00
}
2022-09-26 21:01:07 +03:00
# if NKAI_TRACE_LEVEL >= 2
2024-12-12 11:07:56 +01:00
logAi - > trace ( " priorityTier %d, Evaluated %s, loss: %f, turn: %d, turns main: %f, scout: %f, army-involvement: %f, gold: %f, cost: %d, army gain: %f, army growth: %f skill: %f danger: %d, threatTurns: %d, threat: %d, role: %s, strategical value: %f, conquest value: %f cwr: %f, fear: %f, result %f " ,
2024-07-15 18:12:52 +02:00
priorityTier ,
2021-05-16 14:38:26 +03:00
task - > toString ( ) ,
2021-05-16 14:15:03 +03:00
evaluationContext . armyLossPersentage ,
2021-05-16 14:39:47 +03:00
( int ) evaluationContext . turn ,
2021-05-16 14:15:03 +03:00
evaluationContext . movementCostByRole [ HeroRole : : MAIN ] ,
evaluationContext . movementCostByRole [ HeroRole : : SCOUT ] ,
2024-12-12 11:07:56 +01:00
evaluationContext . armyInvolvement ,
2023-07-30 11:33:52 +03:00
goldRewardPerTurn ,
2021-05-16 14:19:00 +03:00
evaluationContext . goldCost ,
2021-05-16 14:15:03 +03:00
evaluationContext . armyReward ,
2024-07-24 21:22:14 +02:00
evaluationContext . armyGrowth ,
2024-07-07 22:44:52 +02:00
evaluationContext . skillReward ,
2021-05-16 14:15:03 +03:00
evaluationContext . danger ,
2024-08-14 22:53:48 +02:00
evaluationContext . threatTurns ,
2024-07-15 18:12:52 +02:00
evaluationContext . threat ,
2021-05-16 14:39:47 +03:00
evaluationContext . heroRole = = HeroRole : : MAIN ? " main " : " scout " ,
2021-05-16 14:15:03 +03:00
evaluationContext . strategicalValue ,
2024-07-24 21:22:14 +02:00
evaluationContext . conquestValue ,
2021-05-16 14:19:00 +03:00
evaluationContext . closestWayRatio ,
2021-05-16 14:55:33 +03:00
evaluationContext . enemyHeroDangerRatio ,
2021-05-15 19:23:38 +03:00
result ) ;
# endif
return result ;
}
2022-09-26 21:01:07 +03:00
}