Skip to content

Commit

Permalink
Add individual Spell strategic value for AI (ihhub#5568)
Browse files Browse the repository at this point in the history
  • Loading branch information
idshibanov authored Jun 26, 2022
1 parent a4419b1 commit f2c4c49
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 47 deletions.
22 changes: 6 additions & 16 deletions src/fheroes2/ai/normal/ai_normal_hero.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -787,14 +787,9 @@ namespace AI
else if ( objectType == MP2::OBJ_XANADU ) {
return 3000.0;
}
else if ( objectType == MP2::OBJ_SHRINE1 ) {
return 100;
}
else if ( objectType == MP2::OBJ_SHRINE2 ) {
return 250;
}
else if ( objectType == MP2::OBJ_SHRINE3 ) {
return 500;
else if ( objectType == MP2::OBJ_SHRINE1 || objectType == MP2::OBJ_SHRINE2 || objectType == MP2::OBJ_SHRINE3 ) {
const Spell & spell = tile.QuantitySpell();
return spell.getStrategicValue( hero.GetArmy().GetStrength(), hero.GetMaxSpellPoints(), hero.GetPower() );
}
else if ( MP2::isHeroUpgradeObject( objectType ) ) {
return 500.0;
Expand Down Expand Up @@ -1070,14 +1065,9 @@ namespace AI
else if ( objectType == MP2::OBJ_XANADU ) {
return 3000.0;
}
else if ( objectType == MP2::OBJ_SHRINE1 ) {
return 250;
}
else if ( objectType == MP2::OBJ_SHRINE2 ) {
return 500;
}
else if ( objectType == MP2::OBJ_SHRINE3 ) {
return 500;
else if ( objectType == MP2::OBJ_SHRINE1 || objectType == MP2::OBJ_SHRINE2 || objectType == MP2::OBJ_SHRINE3 ) {
const Spell & spell = tile.QuantitySpell();
return spell.getStrategicValue( hero.GetArmy().GetStrength(), hero.GetMaxSpellPoints(), hero.GetPower() );
}
else if ( MP2::isHeroUpgradeObject( objectType ) ) {
return 1250.0;
Expand Down
15 changes: 6 additions & 9 deletions src/fheroes2/castle/castle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -470,22 +470,19 @@ double Castle::getArmyRecruitmentValue() const

double Castle::getVisitValue( const Heroes & hero ) const
{
const Troops & heroArmy = hero.GetArmy();
Troops futureArmy( heroArmy );
const double heroArmyStrength = futureArmy.GetStrength();

double spellValue = 0;
const int spellPower = hero.GetPower();
const SpellStorage & guildSpells = mageguild.GetSpells( GetLevelMageGuild(), isLibraryBuild() );
for ( const Spell & spell : guildSpells ) {
if ( spell.isAdventure() ) {
// AI is stupid to use Adventure spells.
continue;
}
if ( hero.CanLearnSpell( spell ) && !hero.HaveSpell( spell, true ) ) {
spellValue += spell.Level() * 50.0;
spellValue += spell.getStrategicValue( heroArmyStrength, hero.GetMaxSpellPoints(), spellPower );
}
}

const Troops & heroArmy = hero.GetArmy();
Troops futureArmy( heroArmy );
const double heroArmyStrength = futureArmy.GetStrength();

Funds potentialFunds = GetKingdom().GetFunds();

for ( size_t i = 0; i < futureArmy.Size(); ++i ) {
Expand Down
23 changes: 1 addition & 22 deletions src/fheroes2/heroes/heroes_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -339,28 +339,7 @@ double HeroBase::GetMagicStrategicValue( const double armyStrength ) const
double bestValue = 0;
for ( const Spell & spell : spells ) {
if ( spell.isCombat() ) {
const int id = spell.GetID();

const uint32_t spellCost = spell.spellPoints();
const uint32_t casts = spellCost ? std::min( 10U, currentSpellPoints / spellCost ) : 0;

// use quadratic formula to diminish returns from subsequent spell casts, (up to x5 when spell has 10 uses)
const double amountModifier = ( casts == 1 ) ? 1 : casts - ( 0.05 * casts * casts );

if ( spell.isDamage() ) {
// Benchmark for Lightning for 20 power * 20 knowledge (maximum uses) is 2500.0
bestValue = std::max( bestValue, amountModifier * spell.Damage() * spellPower );
}
// These high impact spells can turn tide of battle
else if ( spell.isResurrect() || spell.isMassActions() || id == Spell::BLIND || id == Spell::PARALYZE ) {
bestValue = std::max( bestValue, armyStrength * 0.1 * amountModifier );
}
else if ( spell.isSummon() ) {
bestValue = std::max( bestValue, Monster( spell ).GetMonsterStrength() * spell.ExtraValue() * spellPower * amountModifier );
}
else {
bestValue = std::max( bestValue, armyStrength * 0.04 * amountModifier );
}
bestValue = std::max( bestValue, spell.getStrategicValue( armyStrength, currentSpellPoints, spellPower ) );
}
}

Expand Down
34 changes: 34 additions & 0 deletions src/fheroes2/spell/spell.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "spell.h"
#include "artifact.h"
#include "heroes_base.h"
#include "monster.h"
#include "race.h"
#include "rand.h"
#include "resource.h"
Expand Down Expand Up @@ -202,6 +203,39 @@ uint32_t Spell::spellPoints( const HeroBase * hero ) const
return static_cast<uint32_t>( spellCost );
}

double Spell::getStrategicValue( double armyStrength, uint32_t currentSpellPoints, int spellPower ) const
{
const uint32_t spellCost = spellPoints();
const uint32_t casts = spellCost ? std::min( 10U, currentSpellPoints / spellCost ) : 0;

// use quadratic formula to diminish returns from subsequent spell casts, (up to x5 when spell has 10 uses)
const double amountModifier = ( casts == 1 ) ? 1 : casts - ( 0.05 * casts * casts );

if ( isAdventure() ) {
// AI uses Dimension door and View All only spells right now
if ( id == Spell::DIMENSIONDOOR ) {
return 500.0 * amountModifier;
}
if ( id == Spell::VIEWALL ) {
return 500.0;
}
return 0.0;
}

if ( isDamage() ) {
// Benchmark for Lightning for 20 power * 20 knowledge (maximum uses) is 2500.0
return amountModifier * Damage() * spellPower;
}
// These high impact spells can turn tide of battle
if ( isResurrect() || isMassActions() || id == Spell::BLIND || id == Spell::PARALYZE ) {
return armyStrength * 0.1 * amountModifier;
}
if ( isSummon() ) {
return Monster( id ).GetMonsterStrength() * ExtraValue() * spellPower * amountModifier;
}
return armyStrength * 0.04 * amountModifier;
}

int Spell::Level() const
{
switch ( id ) {
Expand Down
6 changes: 6 additions & 0 deletions src/fheroes2/spell/spell.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,16 @@ class Spell

// Returns the number of spell points consumed/required by this spell
uint32_t spellPoints( const HeroBase * hero = nullptr ) const;

// Returns the number of movement points consumed by this spell
uint32_t movePoints() const;

// Returns the minimum number of movement points required to cast this spell
uint32_t minMovePoints() const;

// Returns the value of the spell using the provided context
double getStrategicValue( double armyStrength, uint32_t currentSpellPoints, int spellPower ) const;

int Level() const;
uint32_t Damage() const;
uint32_t Restore() const;
Expand Down

0 comments on commit f2c4c49

Please sign in to comment.