2021-05-15 18:23:38 +02:00
/*
2021-05-15 21:04:26 +02: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 18:23:38 +02:00
*/
# include "StdInc.h"
# include <limits>
2021-05-16 13:55:47 +02:00
# include "Nullkiller.h"
2023-06-02 20:47:37 +02:00
# include "../../../lib/mapObjectConstructors/AObjectTypeHandler.h"
# include "../../../lib/mapObjectConstructors/CObjectClassesHandler.h"
2023-06-06 17:32:53 +02:00
# include "../../../lib/mapObjectConstructors/CBankInstanceConstructor.h"
2021-05-15 18:23:38 +02: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 18:23:38 +02: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 18:23:38 +02:00
# include "../../../CCallback.h"
2021-05-15 21:02:27 +02:00
# include "../../../lib/filesystem/Filesystem.h"
2021-05-16 13:15:12 +02:00
# include "../Goals/ExecuteHeroChain.h"
2020-05-04 17:58:43 +02:00
# include "../Goals/BuildThis.h"
2023-09-24 12:07:42 +02:00
# include "../Goals/StayAtTown.h"
2023-02-28 09:07:59 +02:00
# include "../Goals/ExchangeSwapTownHeroes.h"
2023-06-04 15:02:02 +02:00
# include "../Goals/DismissHero.h"
2021-05-16 14:01:37 +02:00
# include "../Markers/UnlockCluster.h"
2021-05-16 13:56:35 +02:00
# include "../Markers/HeroExchange.h"
# include "../Markers/ArmyUpgrade.h"
2021-05-16 14:00:24 +02:00
# include "../Markers/DefendTown.h"
2021-05-15 18:23:38 +02:00
2022-09-26 20:01:07 +02:00
namespace NKAI
{
2024-11-28 17:08:15 +02:00
constexpr float MIN_CRITICAL_VALUE = 2.0f ;
2021-05-15 18:23:38 +02:00
2024-07-12 23:36:41 +02:00
EvaluationContext : : EvaluationContext ( const Nullkiller * ai )
2021-05-16 13:38:53 +02: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 17:58:43 +02:00
strategicalValue ( 0 ) ,
2024-07-12 17:41:46 +02:00
conquestValue ( 0 ) ,
2021-05-16 13:55:33 +02:00
evaluator ( ai ) ,
2023-05-14 08:17:15 +02: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 13:38:53 +02:00
{
}
2023-06-04 15:02:02 +02:00
void EvaluationContext : : addNonCriticalStrategicalValue ( float value )
{
vstd : : amax ( strategicalValue , std : : min ( value , MIN_CRITICAL_VALUE ) ) ;
}
2021-05-15 18:23:38 +02:00
PriorityEvaluator : : ~ PriorityEvaluator ( )
{
2021-05-15 21:02:27 +02:00
delete engine ;
2021-05-15 18:23:38 +02: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 21:02:27 +02:00
std : : string str = std : : string ( ( char * ) file . first . get ( ) , file . second ) ;
engine = fl : : FllImporter ( ) . fromString ( str ) ;
armyLossPersentageVariable = engine - > getInputVariable ( " armyLoss " ) ;
2023-05-14 08:17:15 +02:00
armyGrowthVariable = engine - > getInputVariable ( " armyGrowth " ) ;
2021-05-15 21:02:27 +02:00
heroRoleVariable = engine - > getInputVariable ( " heroRole " ) ;
dangerVariable = engine - > getInputVariable ( " danger " ) ;
2021-05-16 13:19:00 +02:00
turnVariable = engine - > getInputVariable ( " turn " ) ;
2021-05-16 13:14:07 +02:00
mainTurnDistanceVariable = engine - > getInputVariable ( " mainTurnDistance " ) ;
scoutTurnDistanceVariable = engine - > getInputVariable ( " scoutTurnDistance " ) ;
2021-05-15 21:02:27 +02:00
goldRewardVariable = engine - > getInputVariable ( " goldReward " ) ;
armyRewardVariable = engine - > getInputVariable ( " armyReward " ) ;
skillRewardVariable = engine - > getInputVariable ( " skillReward " ) ;
rewardTypeVariable = engine - > getInputVariable ( " rewardType " ) ;
closestHeroRatioVariable = engine - > getInputVariable ( " closestHeroRatio " ) ;
2021-05-16 13:13:35 +02:00
strategicalValueVariable = engine - > getInputVariable ( " strategicalValue " ) ;
2024-04-16 23:10:15 +02:00
goldPressureVariable = engine - > getInputVariable ( " goldPressure " ) ;
2021-05-16 13:19:00 +02:00
goldCostVariable = engine - > getInputVariable ( " goldCost " ) ;
2021-05-16 13:55:33 +02:00
fearVariable = engine - > getInputVariable ( " fear " ) ;
2021-05-15 21:02:27 +02:00
value = engine - > getOutputVariable ( " Value " ) ;
2021-05-15 18:23:38 +02: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 14:08:56 +02:00
int32_t estimateTownIncome ( CCallback * cb , const CGObjectInstance * target , const CGHeroInstance * hero )
2021-05-15 18:23:38 +02: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 18:23:38 +02:00
auto town = cb - > getTown ( target - > id ) ;
2023-02-28 09:07:59 +02:00
auto fortLevel = town - > fortLevel ( ) ;
2021-05-15 18:23:38 +02:00
2023-07-30 17:02:56 +02: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 18:23:38 +02:00
}
2024-04-20 14:28:42 +02:00
int32_t getResourcesGoldReward ( const TResources & res )
2023-12-23 15:53:30 +02:00
{
2024-04-20 14:28:42 +02:00
int32_t result = 0 ;
2024-08-12 20:26:30 +02:00
for ( auto r : GameResID : : ALL_RESOURCES ( ) )
2024-04-20 14:28:42 +02: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 18:23:38 +02:00
uint64_t getCreatureBankArmyReward ( const CGObjectInstance * target , const CGHeroInstance * hero )
{
2023-10-28 11:27:10 +02:00
auto objectInfo = target - > getObjectHandler ( ) - > getObjectInfo ( target - > appearance ) ;
2021-05-15 18:23:38 +02: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 18:23:38 +02:00
uint64_t result = 0 ;
2022-09-09 19:19:01 +02:00
const auto & slots = hero - > Slots ( ) ;
ui64 weakestStackPower = 0 ;
2024-04-20 09:33:37 +02:00
int duplicatingSlots = getDuplicatingSlots ( hero ) ;
2022-09-09 19:19:01 +02:00
if ( slots . size ( ) > = GameConstants : : ARMY_SIZE )
2021-05-15 18:23:38 +02: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 10:02:36 +02:00
for ( const auto & stack : slots )
2022-09-09 19:19:01 +02:00
{
vstd : : amin ( weakestStackPower , stack . second - > getPower ( ) ) ;
}
2021-05-15 18:23:38 +02: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 18:02:35 +02:00
auto ccre = dynamic_cast < const CCreature * > ( c . data . getType ( ) ) ;
2024-04-20 09:33:37 +02:00
if ( hero - > getSlotFor ( ccre ) . validSlot ( ) | | duplicatingSlots > 0 )
2022-09-09 19:19:01 +02:00
{
2024-10-12 18:02:35 +02:00
result + = ( c . data . getType ( ) - > getAIValue ( ) * c . data . count ) * c . chance ;
2022-09-09 19:19:01 +02:00
}
2023-07-30 10:33:52 +02:00
/*else
2022-09-09 19:19:01 +02:00
{
//we will need to discard the weakest stack
2023-04-05 02:26:29 +02:00
result + = ( c . data . type - > getAIValue ( ) * c . data . count - weakestStackPower ) * c . chance ;
2023-07-30 10:33:52 +02:00
} */
2022-09-09 19:19:01 +02:00
}
result / = 100 ; //divide by total chance
2021-05-15 18:23:38 +02:00
return result ;
}
2023-05-14 08:17:15 +02:00
uint64_t getDwellingArmyValue ( CCallback * cb , const CGObjectInstance * target , bool checkGold )
2021-05-15 18:23:38 +02: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 02:26:29 +02:00
auto creaturesAreFree = creature - > getLevel ( ) = = 1 ;
if ( ! creaturesAreFree & & checkGold & & ! cb - > getResourceAmount ( ) . canAfford ( creature - > getFullRecruitCost ( ) * creLevel . first ) )
2021-05-15 21:02:57 +02:00
continue ;
2023-04-05 02:26:29 +02:00
score + = creature - > getAIValue ( ) * creLevel . first ;
2021-05-15 18:23:38 +02:00
}
}
return score ;
}
2023-05-14 08:17:15 +02: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 13:19:00 +02: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 02:26:29 +02:00
auto creaturesAreFree = creature - > getLevel ( ) = = 1 ;
2021-05-16 13:19:00 +02:00
if ( ! creaturesAreFree )
2024-07-15 18:12:52 +02:00
cost + = creature - > getFullRecruitCost ( ) . marketValue ( ) * creLevel . first ;
2021-05-16 13:19:00 +02:00
}
}
return cost ;
}
2024-04-20 14:28:42 +02:00
static uint64_t evaluateArtifactArmyValue ( const CArtifact * art )
2021-05-16 12:52:34 +02:00
{
2024-04-20 14:28:42 +02:00
if ( art - > getId ( ) = = ArtifactID : : SPELL_SCROLL )
2021-05-16 12:52:34 +02:00
return 1500 ;
auto statsValue =
2023-10-21 13:50:42 +02:00
10 * art - > valOfBonuses ( BonusType : : MOVEMENT , BonusCustomSubtype : : heroMovementLand )
2023-05-01 00:20:01 +02:00
+ 1200 * art - > valOfBonuses ( BonusType : : STACKS_SPEED )
+ 700 * art - > valOfBonuses ( BonusType : : MORALE )
2023-10-21 13:50:42 +02:00
+ 700 * art - > valOfBonuses ( BonusType : : PRIMARY_SKILL , BonusSubtypeID ( PrimarySkill : : ATTACK ) )
+ 700 * art - > valOfBonuses ( BonusType : : PRIMARY_SKILL , BonusSubtypeID ( PrimarySkill : : DEFENSE ) )
+ 700 * art - > valOfBonuses ( BonusType : : PRIMARY_SKILL , BonusSubtypeID ( PrimarySkill : : KNOWLEDGE ) )
+ 700 * art - > valOfBonuses ( BonusType : : PRIMARY_SKILL , BonusSubtypeID ( PrimarySkill : : SPELL_POWER ) )
2023-05-01 00:20:01 +02:00
+ 500 * art - > valOfBonuses ( BonusType : : LUCK ) ;
2021-05-16 12:52:34 +02:00
auto classValue = 0 ;
2024-04-20 14:28:42 +02:00
switch ( art - > aClass )
2021-05-16 12:52:34 +02:00
{
2024-09-13 23:06:49 +02:00
case CArtifact : : EartClass : : ART_TREASURE :
//FALL_THROUGH
2021-05-16 12:52:34 +02:00
case CArtifact : : EartClass : : ART_MINOR :
classValue = 1000 ;
break ;
case CArtifact : : EartClass : : ART_MAJOR :
classValue = 3000 ;
break ;
case CArtifact : : EartClass : : ART_RELIC :
case CArtifact : : EartClass : : ART_SPECIAL :
classValue = 8000 ;
break ;
}
return statsValue > classValue ? statsValue : classValue ;
}
2020-05-04 17:58:43 +02:00
uint64_t RewardEvaluator : : getArmyReward (
const CGObjectInstance * target ,
const CGHeroInstance * hero ,
const CCreatureSet * army ,
bool checkGold ) const
2021-05-15 18:23:38 +02:00
{
2021-05-16 12:53:05 +02:00
const float enemyArmyEliminationRewardRatio = 0.5f ;
2023-07-30 10:33:52 +02:00
auto relations = ai - > cb - > getPlayerRelations ( target - > tempOwner , ai - > playerID ) ;
2021-05-15 18:23:38 +02:00
if ( ! target )
return 0 ;
switch ( target - > ID )
{
2021-05-16 13:15:12 +02:00
case Obj : : HILL_FORT :
2021-05-16 19:53:11 +02:00
return ai - > armyManager - > calculateCreaturesUpgrade ( army , target , ai - > cb - > getResourceAmount ( ) ) . upgradeValue ;
2021-05-15 18:23:38 +02:00
case Obj : : CREATURE_GENERATOR1 :
2021-05-15 21:02:57 +02:00
case Obj : : CREATURE_GENERATOR2 :
case Obj : : CREATURE_GENERATOR3 :
case Obj : : CREATURE_GENERATOR4 :
2023-05-14 08:17:15 +02: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 18:23:38 +02:00
case Obj : : ARTIFACT :
2024-10-12 10:41:59 +02:00
return evaluateArtifactArmyValue ( dynamic_cast < const CGArtifact * > ( target ) - > storedArtifact - > getType ( ) ) ;
2021-05-16 12:53:05 +02:00
case Obj : : HERO :
2023-07-30 10:33:52 +02:00
return relations = = PlayerRelations : : ENEMIES
2021-05-16 12:53:05 +02: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 12:07:42 +02:00
case Obj : : MAGIC_WELL :
case Obj : : MAGIC_SPRING :
return getManaRecoveryArmyReward ( hero ) ;
2021-05-15 18:23:38 +02:00
default :
2024-04-20 14:28:42 +02:00
break ;
2021-05-15 18:23:38 +02:00
}
2024-04-20 14:28:42 +02: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 20:26:30 +02:00
const auto * art = dynamic_cast < const CArtifact * > ( VLC - > artifacts ( ) - > getById ( artID ) ) ;
2024-04-20 14:28:42 +02:00
rewardValue + = evaluateArtifactArmyValue ( art ) ;
}
}
if ( ! info . reward . creatures . empty ( ) )
{
2024-08-12 20:26:30 +02:00
for ( const auto & stackInfo : info . reward . creatures )
2024-04-20 14:28:42 +02: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 18:23:38 +02:00
}
2023-05-14 08:17:15 +02:00
uint64_t RewardEvaluator : : getArmyGrowth (
const CGObjectInstance * target ,
const CGHeroInstance * hero ,
const CCreatureSet * army ) const
{
if ( ! target )
return 0 ;
2023-07-30 10:33:52 +02:00
auto relations = ai - > cb - > getPlayerRelations ( target - > tempOwner , hero - > tempOwner ) ;
if ( relations ! = PlayerRelations : : ENEMIES )
return 0 ;
2023-05-14 08:17:15 +02: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 17:58:43 +02:00
int RewardEvaluator : : getGoldCost ( const CGObjectInstance * target , const CGHeroInstance * hero , const CCreatureSet * army ) const
2021-05-16 13:19:00 +02:00
{
if ( ! target )
return 0 ;
2023-05-01 13:29:56 +02:00
if ( auto * m = dynamic_cast < const IMarket * > ( target ) )
{
if ( m - > allowsTrade ( EMarketMode : : RESOURCE_SKILL ) )
return 2000 ;
}
2021-05-16 13:19:00 +02:00
switch ( target - > ID )
{
case Obj : : HILL_FORT :
2023-04-05 02:26:29 +02:00
return ai - > armyManager - > calculateCreaturesUpgrade ( army , target , ai - > cb - > getResourceAmount ( ) ) . upgradeCost [ EGameResID : : GOLD ] ;
2021-05-16 13:19:00 +02: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 17:58:43 +02:00
float RewardEvaluator : : getEnemyHeroStrategicalValue ( const CGHeroInstance * enemy ) const
2021-05-16 13:13:35 +02:00
{
2020-05-04 17:58:43 +02:00
auto objectsUnderTreat = ai - > dangerHitMap - > getOneTurnAccessibleObjects ( enemy ) ;
2021-05-16 13:13:35 +02:00
float objectValue = 0 ;
for ( auto obj : objectsUnderTreat )
{
2021-05-16 13:13:56 +02:00
vstd : : amax ( objectValue , getStrategicalValue ( obj ) ) ;
2021-05-16 13:13:35 +02: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 15:02:02 +02:00
return std : : min ( 1.5f , objectValue * 0.9f + ( 1.5f - ( 1.5f / ( 1 + enemy - > level ) ) ) ) ;
2021-05-16 13:13:35 +02:00
}
2020-05-04 17:58:43 +02:00
float RewardEvaluator : : getResourceRequirementStrength ( int resType ) const
2021-05-16 13:15:03 +02:00
{
2020-05-04 17:58:43 +02:00
TResources requiredResources = ai - > buildAnalyzer - > getResourcesRequiredNow ( ) ;
TResources dailyIncome = ai - > buildAnalyzer - > getDailyIncome ( ) ;
2021-05-16 13:15:03 +02:00
if ( requiredResources [ resType ] = = 0 )
return 0 ;
if ( dailyIncome [ resType ] = = 0 )
2021-05-16 13:19:00 +02:00
return 1.0f ;
2021-05-16 13:15:03 +02:00
2021-05-16 13:19:00 +02:00
float ratio = ( float ) requiredResources [ resType ] / dailyIncome [ resType ] / 2 ;
return std : : min ( ratio , 1.0f ) ;
2021-05-16 13:15:03 +02:00
}
2020-05-04 17:58:43 +02:00
float RewardEvaluator : : getTotalResourceRequirementStrength ( int resType ) const
2021-05-16 13:15:03 +02:00
{
2020-05-04 17:58:43 +02:00
TResources requiredResources = ai - > buildAnalyzer - > getTotalResourcesRequired ( ) ;
TResources dailyIncome = ai - > buildAnalyzer - > getDailyIncome ( ) ;
2021-05-16 13:15:03 +02:00
if ( requiredResources [ resType ] = = 0 )
return 0 ;
2021-05-16 13:19:00 +02:00
float ratio = dailyIncome [ resType ] = = 0
2023-07-29 17:54:20 +02:00
? ( float ) requiredResources [ resType ] / 10.0f
: ( float ) requiredResources [ resType ] / dailyIncome [ resType ] / 20.0f ;
2021-05-16 13:15:03 +02:00
2023-07-29 17:54:20 +02:00
return std : : min ( ratio , 2.0f ) ;
2021-05-16 13:15:03 +02:00
}
2023-06-11 18:21:50 +02: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 12:07:42 +02:00
{
return ai - > heroManager - > getMagicStrength ( hero ) * 10000 * ( 1.0f - std : : sqrt ( static_cast < float > ( hero - > mana ) / hero - > manaLimit ( ) ) ) ;
}
2024-04-20 14:28:42 +02: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 13:13:35 +02:00
{
if ( ! target )
return 0 ;
switch ( target - > ID )
{
2021-05-16 13:15:03 +02:00
case Obj : : MINE :
2023-10-28 11:27:10 +02:00
{
auto mine = dynamic_cast < const CGMine * > ( target ) ;
return mine - > producedResource = = EGameResID : : GOLD
2020-05-04 17:58:43 +02:00
? 0.5f
2023-10-28 11:27:10 +02:00
: 0.4f * getTotalResourceRequirementStrength ( mine - > producedResource ) + 0.1f * getResourceRequirementStrength ( mine - > producedResource ) ;
}
2021-05-16 13:15:03 +02:00
case Obj : : RESOURCE :
2023-10-28 11:27:10 +02:00
{
auto resource = dynamic_cast < const CGResource * > ( target ) ;
2024-04-20 14:28:42 +02:00
TResources res ;
res [ resource - > resourceID ( ) ] = resource - > amount ;
return getResourceRequirementStrength ( res ) ;
2023-10-28 11:27:10 +02:00
}
2021-05-16 13:15:03 +02:00
2021-05-16 13:13:35 +02:00
case Obj : : TOWN :
2023-02-28 09:07:59 +02:00
{
2021-05-16 13:57:36 +02:00
if ( ai - > buildAnalyzer - > getDevelopmentInfo ( ) . empty ( ) )
2023-06-11 18:21:50 +02:00
return 10.0f ;
2021-05-16 13:57:36 +02:00
2023-02-28 09:07:59 +02:00
auto town = dynamic_cast < const CGTownInstance * > ( target ) ;
2023-06-11 18:21:50 +02: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 17:02:56 +02:00
auto booster = isAnotherAi ( town , * ai - > cb ) ? 0.4f : 1.0f ;
2023-02-28 09:07:59 +02:00
2023-07-30 17:02:56 +02:00
if ( town - > hasCapitol ( ) )
return booster * 1.5 ;
2023-02-28 09:07:59 +02:00
if ( fortLevel < CGTownInstance : : CITADEL )
2023-07-30 17:02:56 +02:00
return booster * ( town - > hasFort ( ) ? 1.0 : 0.8 ) ;
2023-02-28 09:07:59 +02:00
else
2023-07-30 17:02:56 +02:00
return booster * ( fortLevel = = CGTownInstance : : CASTLE ? 1.4 : 1.2 ) ;
2023-02-28 09:07:59 +02:00
}
2021-05-16 13:13:35 +02:00
case Obj : : HERO :
2021-05-16 14:08:56 +02:00
return ai - > cb - > getPlayerRelations ( target - > tempOwner , ai - > playerID ) = = PlayerRelations : : ENEMIES
2021-05-16 13:13:35 +02:00
? getEnemyHeroStrategicalValue ( dynamic_cast < const CGHeroInstance * > ( target ) )
: 0 ;
2021-05-16 13:19:00 +02:00
2023-12-23 15:53:51 +02:00
case Obj : : KEYMASTER :
return 0.6f ;
2021-05-16 13:13:35 +02:00
default :
2024-04-20 14:28:42 +02:00
break ;
2021-05-16 13:13:35 +02:00
}
2024-04-20 14:28:42 +02: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 13:13:35 +02: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 17:47:47 +02:00
float RewardEvaluator : : evaluateWitchHutSkillScore ( const CGObjectInstance * hut , const CGHeroInstance * hero , HeroRole role ) const
2021-05-15 21:02:27 +02:00
{
2023-09-30 17:47:47 +02:00
auto rewardable = dynamic_cast < const CRewardableObject * > ( hut ) ;
assert ( rewardable ) ;
auto skill = SecondarySkill ( * rewardable - > configuration . getVariable ( " secondarySkill " , " gainedSkill " ) ) ;
2021-05-15 21:02:27 +02:00
if ( ! hut - > wasVisited ( hero - > tempOwner ) )
return role = = HeroRole : : SCOUT ? 2 : 0 ;
2023-10-05 15:13:52 +02:00
if ( hero - > getSecSkillLevel ( skill ) ! = MasteryLevel : : NONE
2021-05-15 21:02:27 +02:00
| | hero - > secSkills . size ( ) > = GameConstants : : SKILL_PER_HERO )
return 0 ;
2020-05-04 17:58:43 +02:00
auto score = ai - > heroManager - > evaluateSecSkill ( skill , hero ) ;
2021-05-15 21:02:27 +02:00
return score > = 2 ? ( role = = HeroRole : : MAIN ? 10 : 4 ) : score ;
}
2020-05-04 17:58:43 +02:00
float RewardEvaluator : : getSkillReward ( const CGObjectInstance * target , const CGHeroInstance * hero , HeroRole role ) const
2021-05-15 21:02:27 +02:00
{
2021-05-16 12:53:05 +02:00
const float enemyHeroEliminationSkillRewardRatio = 0.5f ;
2021-05-15 21:02:27 +02: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 13:15:03 +02:00
case Obj : : TREE_OF_KNOWLEDGE :
2021-05-15 21:02:27 +02:00
return 1 ;
2021-05-16 13:15:03 +02:00
case Obj : : LEARNING_STONE :
2021-05-16 13:20:09 +02:00
return 1.0f / std : : sqrt ( hero - > level ) ;
2021-05-15 21:02:27 +02:00
case Obj : : ARENA :
return 2 ;
2023-07-30 10:33:52 +02:00
case Obj : : SHRINE_OF_MAGIC_INCANTATION :
2024-04-20 14:28:42 +02:00
return 0.25f ;
2023-07-30 10:33:52 +02:00
case Obj : : SHRINE_OF_MAGIC_GESTURE :
2024-04-20 14:28:42 +02:00
return 1.0f ;
2023-07-30 10:33:52 +02:00
case Obj : : SHRINE_OF_MAGIC_THOUGHT :
2024-04-20 14:28:42 +02:00
return 2.0f ;
2021-05-15 21:02:27 +02:00
case Obj : : LIBRARY_OF_ENLIGHTENMENT :
return 8 ;
case Obj : : WITCH_HUT :
2023-09-30 17:47:47 +02: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 12:53:05 +02:00
case Obj : : HERO :
2021-05-16 14:08:56 +02:00
return ai - > cb - > getPlayerRelations ( target - > tempOwner , ai - > playerID ) = = PlayerRelations : : ENEMIES
2021-05-16 12:53:05 +02:00
? enemyHeroEliminationSkillRewardRatio * dynamic_cast < const CGHeroInstance * > ( target ) - > level
: 0 ;
2024-04-20 14:28:42 +02:00
2021-05-15 21:02:27 +02:00
default :
2024-04-20 14:28:42 +02:00
break ;
2021-05-15 21:02:27 +02:00
}
2024-04-20 14:28:42 +02: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 21:02:27 +02:00
}
2023-02-28 09:07:59 +02:00
const HitMapInfo & RewardEvaluator : : getEnemyHeroDanger ( const int3 & tile , uint8_t turn ) const
2021-05-16 13:55:33 +02:00
{
2023-10-27 21:32:52 +02:00
auto & treatNode = ai - > dangerHitMap - > getTileThreat ( tile ) ;
2021-05-16 13:55:33 +02:00
if ( treatNode . maximumDanger . danger = = 0 )
2023-10-27 21:32:52 +02:00
return HitMapInfo : : NoThreat ;
2021-05-16 14:01:37 +02:00
if ( treatNode . maximumDanger . turn < = turn )
2023-02-28 09:07:59 +02:00
return treatNode . maximumDanger ;
2021-05-16 14:01:37 +02:00
2023-10-27 21:32:52 +02:00
return treatNode . fastestDanger . turn < = turn ? treatNode . fastestDanger : HitMapInfo : : NoThreat ;
2021-05-16 13:55:33 +02:00
}
2021-05-16 12:53:05 +02: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 12:53:05 +02:00
}
return value ;
}
2020-05-04 17:58:43 +02:00
int32_t RewardEvaluator : : getGoldReward ( const CGObjectInstance * target , const CGHeroInstance * hero ) const
2021-05-15 18:23:38 +02:00
{
if ( ! target )
return 0 ;
2023-07-30 10:33:52 +02:00
auto relations = ai - > cb - > getPlayerRelations ( target - > tempOwner , hero - > tempOwner ) ;
2021-05-15 18:23:38 +02:00
const int dailyIncomeMultiplier = 5 ;
2021-05-16 12:53:05 +02:00
const float enemyArmyEliminationGoldRewardRatio = 0.2f ;
const int32_t heroEliminationBonus = GameConstants : : HERO_GOLD_COST / 2 ;
2021-05-15 18:23:38 +02:00
switch ( target - > ID )
{
case Obj : : RESOURCE :
2023-10-28 11:27:10 +02:00
{
auto * res = dynamic_cast < const CGResource * > ( target ) ;
2024-04-14 14:23:44 +02:00
return res & & res - > resourceID ( ) = = GameResID : : GOLD ? 600 : 100 ;
2023-10-28 11:27:10 +02:00
}
2021-05-15 18:23:38 +02:00
case Obj : : TREASURE_CHEST :
return 1500 ;
case Obj : : WATER_WHEEL :
return 1000 ;
case Obj : : TOWN :
2021-05-16 14:08:56 +02:00
return dailyIncomeMultiplier * estimateTownIncome ( ai - > cb . get ( ) , target , hero ) ;
2021-05-15 18:23:38 +02:00
case Obj : : MINE :
case Obj : : ABANDONED_MINE :
2023-10-28 11:27:10 +02: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 12:53:05 +02:00
case Obj : : HERO :
2023-07-30 10:33:52 +02:00
return relations = = PlayerRelations : : ENEMIES
2021-05-16 12:53:05 +02:00
? heroEliminationBonus + enemyArmyEliminationGoldRewardRatio * getArmyCost ( dynamic_cast < const CGHeroInstance * > ( target ) )
: 0 ;
2021-05-15 18:23:38 +02:00
default :
2024-04-20 14:28:42 +02:00
break ;
2021-05-15 18:23:38 +02:00
}
2024-04-20 14:28:42 +02: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 18:23:38 +02:00
}
2021-05-16 13:56:35 +02:00
class HeroExchangeEvaluator : public IEvaluationContextBuilder
{
public :
2024-02-13 16:21:30 +02:00
void buildEvaluationContext ( EvaluationContext & evaluationContext , Goals : : TSubgoal task ) const override
2021-05-16 13:56:35 +02:00
{
if ( task - > goalType ! = Goals : : HERO_EXCHANGE )
return ;
Goals : : HeroExchange & heroExchange = dynamic_cast < Goals : : HeroExchange & > ( * task ) ;
2024-03-31 17:39:00 +02:00
uint64_t armyStrength = heroExchange . getReinforcementArmyStrength ( evaluationContext . evaluator . ai ) ;
2021-05-16 13:56:35 +02:00
2024-04-14 14:23:44 +02: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 14:23:44 +02:00
evaluationContext . heroRole = evaluationContext . evaluator . ai - > heroManager - > getHeroRole ( heroExchange . hero ) ;
2024-08-30 16:46:36 +02:00
evaluationContext . isExchange = true ;
2021-05-16 13:56:35 +02:00
}
} ;
class ArmyUpgradeEvaluator : public IEvaluationContextBuilder
{
public :
2024-02-13 16:21:30 +02:00
void buildEvaluationContext ( EvaluationContext & evaluationContext , Goals : : TSubgoal task ) const override
2021-05-16 13:56:35 +02: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 15:02:02 +02:00
evaluationContext . addNonCriticalStrategicalValue ( upgradeValue / ( float ) armyUpgrade . hero - > getArmyStrength ( ) ) ;
2024-09-14 02:58:23 +02:00
evaluationContext . isArmyUpgrade = true ;
2021-05-16 13:56:35 +02:00
}
} ;
2024-05-19 09:04:45 +02: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 18:37:18 +02: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 09:04:45 +02:00
}
} ;
2023-09-24 12:07:42 +02: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 12:07:42 +02:00
{
2024-09-29 01:23:13 +02:00
if ( task - > goalType ! = Goals : : STAY_AT_TOWN )
2023-09-24 12:07:42 +02:00
return ;
2024-09-29 01:23:13 +02:00
Goals : : StayAtTown & stayAtTown = dynamic_cast < Goals : : StayAtTown & > ( * task ) ;
2023-09-24 12:07:42 +02:00
2024-04-14 14:23:44 +02: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 12:07:42 +02: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 14:00:24 +02:00
class DefendTownEvaluator : public IEvaluationContextBuilder
{
public :
2024-02-13 16:21:30 +02:00
void buildEvaluationContext ( EvaluationContext & evaluationContext , Goals : : TSubgoal task ) const override
2021-05-16 14:00:24 +02: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 18:21:50 +02:00
auto strategicalValue = evaluationContext . evaluator . getStrategicalValue ( town ) ;
2021-05-16 14:00:24 +02:00
float multiplier = 1 ;
if ( treat . turn < defendTown . getTurn ( ) )
multiplier / = 1 + ( defendTown . getTurn ( ) - treat . turn ) ;
2023-06-04 15:02:02 +02:00
multiplier / = 1.0f + treat . turn / 5.0f ;
2023-07-30 17:02:56 +02:00
if ( defendTown . getTurn ( ) > 0 & & defendTown . isCounterAttack ( ) )
2023-06-11 18:21:50 +02:00
{
2023-07-09 11:35:50 +02:00
auto ourSpeed = defendTown . hero - > movementPointsLimit ( true ) ;
2024-04-14 14:23:44 +02:00
auto enemySpeed = treat . hero . get ( evaluationContext . evaluator . ai - > cb . get ( ) ) - > movementPointsLimit ( true ) ;
2023-06-11 18:21:50 +02: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 14:00:24 +02:00
evaluationContext . goldReward + = dailyIncome * 5 * multiplier ;
2023-07-28 13:17:01 +02: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 14:01:37 +02: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 14:00:24 +02:00
}
} ;
2021-05-16 13:15:03 +02:00
class ExecuteHeroChainEvaluationContextBuilder : public IEvaluationContextBuilder
{
2021-05-16 14:08:56 +02:00
private :
const Nullkiller * ai ;
2021-05-16 13:15:03 +02:00
public :
2021-05-16 14:08:56 +02:00
ExecuteHeroChainEvaluationContextBuilder ( const Nullkiller * ai ) : ai ( ai ) { }
2024-02-13 16:21:30 +02:00
void buildEvaluationContext ( EvaluationContext & evaluationContext , Goals : : TSubgoal task ) const override
2021-05-16 13:15:03 +02:00
{
2021-05-16 13:38:53 +02:00
if ( task - > goalType ! = Goals : : EXECUTE_HERO_CHAIN )
return ;
2021-05-16 13:15:12 +02:00
Goals : : ExecuteHeroChain & chain = dynamic_cast < Goals : : ExecuteHeroChain & > ( * task ) ;
2021-05-16 13:38:53 +02:00
const AIPath & path = chain . getPath ( ) ;
2024-12-12 15:58:29 +02:00
if ( vstd : : isAlmostZero ( path . movementCost ( ) ) )
2024-12-11 14:05:51 +02:00
return ;
2021-05-16 13:38:53 +02:00
vstd : : amax ( evaluationContext . danger , path . getTotalDanger ( ) ) ;
evaluationContext . movementCost + = path . movementCost ( ) ;
evaluationContext . closestWayRatio = chain . closestWayRatio ;
2021-05-16 13:45:12 +02:00
std : : map < const CGHeroInstance * , float > costsPerHero ;
2021-05-16 13:38:53 +02:00
for ( auto & node : path . nodes )
{
2021-05-16 13:45:12 +02: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 13:45:12 +02:00
}
2024-12-04 00:09:13 +02:00
float highestCostForSingleHero = 0 ;
2021-05-16 13:45:12 +02:00
for ( auto pair : costsPerHero )
{
2020-05-04 17:58:43 +02:00
auto role = evaluationContext . evaluator . ai - > heroManager - > getHeroRole ( pair . first ) ;
2021-05-16 13:45:12 +02:00
evaluationContext . movementCostByRole [ role ] + = pair . second ;
2024-12-04 00:09:13 +02:00
if ( pair . second > highestCostForSingleHero )
highestCostForSingleHero = pair . second ;
2021-05-16 13:38:53 +02:00
}
2024-12-04 00:09:13 +02: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 13:15:03 +02:00
2024-04-14 14:23:44 +02:00
auto hero = task - > hero ;
2021-05-16 13:15:03 +02:00
bool checkGold = evaluationContext . danger = = 0 ;
2021-05-16 13:38:53 +02:00
auto army = path . heroArmy ;
2021-05-16 13:15:03 +02:00
2021-05-16 14:08:56 +02:00
const CGObjectInstance * target = ai - > cb - > getObj ( ( ObjectInstanceID ) task - > objid , false ) ;
2024-04-14 14:23:44 +02:00
auto heroRole = evaluationContext . evaluator . ai - > heroManager - > getHeroRole ( hero ) ;
2023-06-04 15:02:02 +02:00
if ( heroRole = = HeroRole : : MAIN )
evaluationContext . heroRole = heroRole ;
2021-05-16 14:01:07 +02:00
2023-07-30 10:33:52 +02:00
if ( target )
2021-05-16 14:01:07 +02:00
{
evaluationContext . goldReward + = evaluationContext . evaluator . getGoldReward ( target , hero ) ;
evaluationContext . armyReward + = evaluationContext . evaluator . getArmyReward ( target , hero , army , checkGold ) ;
2023-05-14 08:17:15 +02:00
evaluationContext . armyGrowth + = evaluationContext . evaluator . getArmyGrowth ( target , hero , army ) ;
2023-06-04 15:02:02 +02: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 14:40:50 +02: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 14:01:07 +02:00
evaluationContext . goldCost + = evaluationContext . evaluator . getGoldCost ( target , hero , army ) ;
2024-11-26 16:39:35 +02:00
if ( evaluationContext . danger > 0 )
evaluationContext . skillReward + = ( float ) evaluationContext . danger / ( float ) hero - > getArmyStrength ( ) ;
2021-05-16 14:01:07 +02:00
}
2024-12-12 12:07:56 +02:00
evaluationContext . armyInvolvement + = army - > getArmyCost ( ) ;
2021-05-16 14:01:07 +02: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 13:38:53 +02:00
vstd : : amax ( evaluationContext . turn , path . turn ( ) ) ;
2021-05-16 13:15:03 +02:00
}
} ;
2021-05-16 13:45:12 +02:00
class ClusterEvaluationContextBuilder : public IEvaluationContextBuilder
{
public :
2022-09-22 10:49:55 +02:00
ClusterEvaluationContextBuilder ( const Nullkiller * ai ) { }
2021-05-16 14:08:56 +02:00
2024-02-13 16:21:30 +02:00
void buildEvaluationContext ( EvaluationContext & evaluationContext , Goals : : TSubgoal task ) const override
2021-05-16 13:45:12 +02: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 14:23:44 +02:00
auto hero = clusterGoal . hero ;
auto role = evaluationContext . evaluator . ai - > heroManager - > getHeroRole ( hero ) ;
2021-05-16 13:45:12 +02:00
2024-04-14 14:23:44 +02:00
std : : vector < std : : pair < ObjectInstanceID , ClusterObjectInfo > > objects ( cluster - > objects . begin ( ) , cluster - > objects . end ( ) ) ;
2021-05-16 13:45:12 +02:00
2024-04-14 14:23:44 +02:00
std : : sort ( objects . begin ( ) , objects . end ( ) , [ ] ( std : : pair < ObjectInstanceID , ClusterObjectInfo > o1 , std : : pair < ObjectInstanceID , ClusterObjectInfo > o2 ) - > bool
2021-05-16 13:45:12 +02:00
{
return o1 . second . priority > o2 . second . priority ;
} ) ;
int boost = 1 ;
2024-04-14 14:23:44 +02:00
for ( auto & objInfo : objects )
2021-05-16 13:45:12 +02:00
{
2024-04-14 14:23:44 +02:00
auto target = evaluationContext . evaluator . ai - > cb - > getObj ( objInfo . first ) ;
2021-05-16 13:45:12 +02:00
bool checkGold = objInfo . second . danger = = 0 ;
auto army = hero ;
2020-05-04 17:58:43 +02: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 15:02:02 +02: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 17:58:43 +02:00
evaluationContext . goldCost + = evaluationContext . evaluator . getGoldCost ( target , hero , army ) / boost ;
2021-05-16 13:45:12 +02:00
evaluationContext . movementCostByRole [ role ] + = objInfo . second . movementCost / boost ;
evaluationContext . movementCost + = objInfo . second . movementCost / boost ;
2021-05-16 13:45:45 +02:00
vstd : : amax ( evaluationContext . turn , objInfo . second . turn / boost ) ;
2021-05-16 13:45:12 +02:00
boost < < = 1 ;
if ( boost > 8 )
break ;
}
}
} ;
2023-02-28 09:07:59 +02:00
class ExchangeSwapTownHeroesContextBuilder : public IEvaluationContextBuilder
{
public :
2024-02-13 16:21:30 +02: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 19:38:26 +02: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 15:02:02 +02:00
class DismissHeroContextBuilder : public IEvaluationContextBuilder
{
private :
const Nullkiller * ai ;
public :
DismissHeroContextBuilder ( const Nullkiller * ai ) : ai ( ai ) { }
2024-02-13 16:21:30 +02:00
void buildEvaluationContext ( EvaluationContext & evaluationContext , Goals : : TSubgoal task ) const override
2023-06-04 15:02:02 +02:00
{
if ( task - > goalType ! = Goals : : DISMISS_HERO )
return ;
Goals : : DismissHero & dismissCommand = dynamic_cast < Goals : : DismissHero & > ( * task ) ;
2024-04-14 14:23:44 +02:00
const CGHeroInstance * dismissedHero = dismissCommand . getHero ( ) ;
2023-06-04 15:02:02 +02:00
auto role = ai - > heroManager - > getHeroRole ( dismissedHero ) ;
2023-07-09 11:35:50 +02:00
auto mpLeft = dismissedHero - > movementPointsRemaining ( ) ;
2023-06-04 15:02:02 +02:00
evaluationContext . movementCost + = mpLeft ;
evaluationContext . movementCostByRole [ role ] + = mpLeft ;
evaluationContext . goldCost + = GameConstants : : HERO_GOLD_COST + getArmyCost ( dismissedHero ) ;
}
} ;
2021-05-16 13:15:03 +02:00
class BuildThisEvaluationContextBuilder : public IEvaluationContextBuilder
{
public :
2024-02-13 16:21:30 +02:00
void buildEvaluationContext ( EvaluationContext & evaluationContext , Goals : : TSubgoal task ) const override
2021-05-16 13:15:03 +02:00
{
2021-05-16 13:38:53 +02:00
if ( task - > goalType ! = Goals : : BUILD_STRUCTURE )
return ;
2021-05-16 13:15:03 +02:00
Goals : : BuildThis & buildThis = dynamic_cast < Goals : : BuildThis & > ( * task ) ;
auto & bi = buildThis . buildingInfo ;
2023-04-05 02:26:29 +02:00
evaluationContext . goldReward + = 7 * bi . dailyIncome [ EGameResID : : GOLD ] / 2 ; // 7 day income but half we already have
2021-05-16 13:15:03 +02:00
evaluationContext . heroRole = HeroRole : : MAIN ;
2021-05-16 13:38:53 +02: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 14:58:49 +02: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 ;
logAi - > trace ( " Building costs for %s : %s MarketValue: %d " , bi . toString ( ) , evaluationContext . buildingCost . toString ( ) , evaluationContext . buildingCost . marketValue ( ) ) ;
2021-05-16 13:15:03 +02:00
if ( bi . creatureID ! = CreatureID : : NONE )
{
2023-06-04 15:02:02 +02:00
evaluationContext . addNonCriticalStrategicalValue ( buildThis . townInfo . armyStrength / 50000.0 ) ;
2023-03-08 12:41:14 +02:00
2021-05-16 13:15:03 +02:00
if ( bi . baseCreatureID = = bi . creatureID )
{
2023-06-04 15:02:02 +02:00
evaluationContext . addNonCriticalStrategicalValue ( ( 0.5f + 0.1f * bi . creatureLevel ) / ( float ) bi . prerequisitesCount ) ;
2021-05-16 13:57:29 +02:00
evaluationContext . armyReward + = bi . armyStrength ;
2021-05-16 13:15:03 +02:00
}
2021-05-16 13:38:53 +02:00
else
{
2021-05-16 14:39:38 +02:00
auto potentialUpgradeValue = evaluationContext . evaluator . getUpgradeArmyReward ( buildThis . town , bi ) ;
2023-03-08 12:41:14 +02:00
2023-06-04 15:02:02 +02: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 13:38:53 +02:00
}
2021-05-16 13:15:03 +02:00
}
2021-05-16 13:57:29 +02:00
else if ( bi . id = = BuildingID : : CITADEL | | bi . id = = BuildingID : : CASTLE )
{
2023-06-04 15:02:02 +02:00
evaluationContext . addNonCriticalStrategicalValue ( buildThis . town - > creatures . size ( ) * 0.2f ) ;
2021-05-16 13:57:29 +02:00
evaluationContext . armyReward + = buildThis . townInfo . armyStrength / 2 ;
}
2023-07-27 14:58:49 +02: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 14:58:49 +02: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 14:58:49 +02:00
if ( evaluationContext . goldReward )
2021-05-16 13:19:00 +02:00
{
2024-04-16 23:10:15 +02:00
auto goldPressure = evaluationContext . evaluator . ai - > buildAnalyzer - > getGoldPressure ( ) ;
2023-03-08 12:41:14 +02:00
2024-04-16 23:10:15 +02: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 17:54:20 +02:00
evaluationContext . strategicalValue / = 3 ;
evaluationContext . movementCostByRole [ evaluationContext . heroRole ] + = 5 ;
evaluationContext . turn + = 5 ;
2021-05-16 13:19:00 +02:00
}
2021-05-16 13:15:03 +02:00
}
} ;
2020-05-04 17:58:43 +02:00
uint64_t RewardEvaluator : : getUpgradeArmyReward ( const CGTownInstance * town , const BuildingInfo & bi ) const
{
2024-10-05 21:37:52 +02:00
if ( ai - > buildAnalyzer - > hasAnyBuilding ( town - > getFactionID ( ) , bi . id ) )
2020-05-04 17:58:43 +02: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 13:15:03 +02:00
{
initVisitTile ( ) ;
2021-05-16 14:08:56 +02:00
evaluationContextBuilders . push_back ( std : : make_shared < ExecuteHeroChainEvaluationContextBuilder > ( ai ) ) ;
2021-05-16 13:38:53 +02:00
evaluationContextBuilders . push_back ( std : : make_shared < BuildThisEvaluationContextBuilder > ( ) ) ;
2021-05-16 14:08:56 +02:00
evaluationContextBuilders . push_back ( std : : make_shared < ClusterEvaluationContextBuilder > ( ai ) ) ;
2021-05-16 13:56:35 +02:00
evaluationContextBuilders . push_back ( std : : make_shared < HeroExchangeEvaluator > ( ) ) ;
2021-05-16 14:00:24 +02:00
evaluationContextBuilders . push_back ( std : : make_shared < ArmyUpgradeEvaluator > ( ) ) ;
2021-05-16 14:01:07 +02: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 15:02:02 +02:00
evaluationContextBuilders . push_back ( std : : make_shared < DismissHeroContextBuilder > ( ai ) ) ;
2023-09-24 12:07:42 +02:00
evaluationContextBuilders . push_back ( std : : make_shared < StayAtTownManaRecoveryEvaluator > ( ) ) ;
2024-05-19 09:04:45 +02:00
evaluationContextBuilders . push_back ( std : : make_shared < ExplorePointEvaluator > ( ) ) ;
2021-05-16 13:15:03 +02:00
}
2021-05-16 13:38:53 +02:00
EvaluationContext PriorityEvaluator : : buildEvaluationContext ( Goals : : TSubgoal goal ) const
2021-05-16 13:15:03 +02:00
{
2021-05-16 13:38:53 +02:00
Goals : : TGoalVec parts ;
2020-05-04 17:58:43 +02:00
EvaluationContext context ( ai ) ;
2021-05-16 13:38:53 +02:00
if ( goal - > goalType = = Goals : : COMPOSITION )
{
2024-03-31 17:39:00 +02:00
parts = goal - > decompose ( ai ) ;
2021-05-16 13:38:53 +02:00
}
else
{
parts . push_back ( goal ) ;
}
2021-05-16 13:45:12 +02:00
for ( auto subgoal : parts )
2021-05-16 13:38:53 +02:00
{
2021-05-16 13:45:12 +02:00
context . goldCost + = subgoal - > goldCost ;
2024-07-19 15:25:24 +02:00
context . buildingCost + = subgoal - > buildingCost ;
2021-05-16 13:15:03 +02:00
2021-05-16 13:38:53 +02:00
for ( auto builder : evaluationContextBuilders )
{
2021-05-16 13:45:12 +02:00
builder - > buildEvaluationContext ( context , subgoal ) ;
2021-05-16 13:38:53 +02:00
}
}
2021-05-16 13:15:03 +02:00
2021-05-16 13:38:53 +02:00
return context ;
2021-05-16 13:15:03 +02:00
}
2024-07-12 17:41:46 +02:00
float PriorityEvaluator : : evaluate ( Goals : : TSubgoal task , int priorityTier )
2021-05-15 18:23:38 +02:00
{
2021-05-16 13:15:03 +02:00
auto evaluationContext = buildEvaluationContext ( task ) ;
2021-05-15 18:23:38 +02:00
2021-05-16 13:15:03 +02: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 09:27:30 +02:00
2023-07-30 10:33:52 +02:00
float goldRewardPerTurn = evaluationContext . goldReward / std : : log2f ( 2 + evaluationContext . movementCost * 10 ) ;
2021-05-15 18:23:38 +02:00
double result = 0 ;
2021-05-16 13:15:03 +02: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 18:23:38 +02:00
{
2024-07-12 17:41:46 +02:00
float score = 0 ;
2024-11-29 17:57:23 +02: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 15:47:08 +02: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 14:40:50 +02: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 12:07:56 +02: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 14:40:50 +02: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 22:50:45 +02:00
if ( evaluationContext . movementCost > = 1 )
return 0 ;
2024-08-24 14:55:26 +02:00
if ( evaluationContext . conquestValue > 0 )
2024-12-04 00:09:13 +02: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 15:47:08 +02: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-04 00:09:13 +02: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 01:42:33 +02: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 15:58:29 +02:00
if ( vstd : : isAlmostZero ( evaluationContext . armyLossPersentage ) & & evaluationContext . closestWayRatio < 1.0 )
2024-12-04 00:09:13 +02:00
return 0 ;
2024-10-31 01:42:33 +02: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 15:58:29 +02:00
if ( vstd : : isAlmostZero ( evaluationContext . armyLossPersentage ) & & evaluationContext . closestWayRatio < 1.0 )
2024-12-04 00:09:13 +02: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 15:47:08 +02: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 15:58:29 +02:00
if ( vstd : : isAlmostZero ( evaluationContext . armyLossPersentage ) & & evaluationContext . closestWayRatio < 1.0 )
2024-12-04 00:09:13 +02: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-04 00:09:13 +02: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-04 00:09:13 +02: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 18:23:38 +02:00
}
2022-09-26 20:01:07 +02:00
# if NKAI_TRACE_LEVEL >= 2
2024-12-12 12:07:56 +02: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 13:38:26 +02:00
task - > toString ( ) ,
2021-05-16 13:15:03 +02:00
evaluationContext . armyLossPersentage ,
2021-05-16 13:39:47 +02:00
( int ) evaluationContext . turn ,
2021-05-16 13:15:03 +02:00
evaluationContext . movementCostByRole [ HeroRole : : MAIN ] ,
evaluationContext . movementCostByRole [ HeroRole : : SCOUT ] ,
2024-12-12 12:07:56 +02:00
evaluationContext . armyInvolvement ,
2023-07-30 10:33:52 +02:00
goldRewardPerTurn ,
2021-05-16 13:19:00 +02:00
evaluationContext . goldCost ,
2021-05-16 13:15:03 +02: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 13:15:03 +02: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 13:39:47 +02:00
evaluationContext . heroRole = = HeroRole : : MAIN ? " main " : " scout " ,
2021-05-16 13:15:03 +02:00
evaluationContext . strategicalValue ,
2024-07-24 21:22:14 +02:00
evaluationContext . conquestValue ,
2021-05-16 13:19:00 +02:00
evaluationContext . closestWayRatio ,
2021-05-16 13:55:33 +02:00
evaluationContext . enemyHeroDangerRatio ,
2021-05-15 18:23:38 +02:00
result ) ;
# endif
return result ;
}
2022-09-26 20:01:07 +02:00
}