Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Dragon Slayer spell selection for the AI #9618

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/fheroes2/ai/ai_battle.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ namespace AI
SpellcastOutcome spellDispelValue( const Spell & spell, const Battle::Units & friendly, const Battle::Units & enemies ) const;
SpellcastOutcome spellResurrectValue( const Spell & spell, const Battle::Arena & arena ) const;
SpellcastOutcome spellSummonValue( const Spell & spell, const Battle::Arena & arena, const int heroColor ) const;
SpellcastOutcome spellDragonSlayerValue( const Spell & spell, const Battle::Units & friendly, const Battle::Units & enemies ) const;
SpellcastOutcome spellEffectValue( const Spell & spell, const Battle::Units & targets ) const;

double spellEffectValue( const Spell & spell, const Battle::Unit & target, bool targetIsLast, bool forDispel ) const;
Expand Down
68 changes: 62 additions & 6 deletions src/fheroes2/ai/ai_battle_spell.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/***************************************************************************
* fheroes2: https://github.com/ihhub/fheroes2 *
* Copyright (C) 2024 *
* Copyright (C) 2024 - 2025 *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
Expand Down Expand Up @@ -47,6 +47,8 @@ namespace
{
const double antimagicLowLimit = 200.0;

const double bloodLustRatio = 0.1;

double ReduceEffectivenessByDistance( const Battle::Unit & unit )
{
// Reduce spell effectiveness if unit already crossed the battlefield
Expand Down Expand Up @@ -184,6 +186,9 @@ AI::SpellSelection AI::BattlePlanner::selectBestSpell( Battle::Arena & arena, co
else if ( spell.isResurrect() ) {
checkSelectBestSpell( spell, spellResurrectValue( spell, arena ) );
}
else if ( spell == Spell::DRAGONSLAYER ) {
checkSelectBestSpell( spell, spellDragonSlayerValue( spell, friendly, enemies ) );
}
else if ( spell.isApplyToFriends() ) {
checkSelectBestSpell( spell, spellEffectValue( spell, trueFriendly ) );
}
Expand Down Expand Up @@ -434,7 +439,7 @@ double AI::BattlePlanner::spellEffectValue( const Spell & spell, const Battle::U
ratio = getSpellHasteRatio( target );
break;
case Spell::BLOODLUST:
ratio = 0.1;
ratio = bloodLustRatio;
break;
case Spell::BLESS:
case Spell::MASSBLESS: {
Expand All @@ -450,7 +455,6 @@ double AI::BattlePlanner::spellEffectValue( const Spell & spell, const Battle::U
ratio = 0.2;
break;
// Following spell usefulness is conditional; ratio will be determined later
case Spell::DRAGONSLAYER:
case Spell::ANTIMAGIC:
case Spell::MIRRORIMAGE:
case Spell::SHIELD:
Expand Down Expand Up @@ -514,9 +518,6 @@ double AI::BattlePlanner::spellEffectValue( const Spell & spell, const Battle::U
ratio *= 1.25;
}
}
else if ( spellID == Spell::DRAGONSLAYER ) {
// TODO: add logic to check if the enemy army contains a dragon.
}

return target.GetStrength() * ratio * spellDurationMultiplier( target );
}
Expand Down Expand Up @@ -657,3 +658,58 @@ AI::SpellcastOutcome AI::BattlePlanner::spellSummonValue( const Spell & spell, c

return bestOutcome;
}

AI::SpellcastOutcome AI::BattlePlanner::spellDragonSlayerValue( const Spell & spell, const Battle::Units & friendly, const Battle::Units & enemies ) const
{
assert( spell.GetID() == Spell::DRAGONSLAYER );

double enemyArmyStrength = 0;
double dragonsStrength = 0;

int32_t numOfSlotsWithEnemyDragons = 0;

for ( const Battle::Unit * unit : enemies ) {
assert( unit != nullptr );

const double strength = unit->GetStrength();

if ( unit->isDragons() ) {
++numOfSlotsWithEnemyDragons;
dragonsStrength += strength;
}

enemyArmyStrength += strength;
}

if ( numOfSlotsWithEnemyDragons == 0 ) {
// This spell is useless as no Dragons exist in the enemy army.
return {};
}

const auto getSpellAttackBonus = []( const Spell & sp ) {
const uint32_t bonus = sp.ExtraValue();
assert( bonus > 0 );

return bonus;
};

// Make an estimation based on the value of Bloodlust since this spell also increases Attack but against every creature.
// If the enemy army consists of other monsters that are not Dragons then Dragon Slayer spell isn't that valuable anymore.
const double bloodlustAttackBonus = getSpellAttackBonus( Spell::BLOODLUST );
const double dragonSlayerAttackBonus = getSpellAttackBonus( spell );

const double dragonSlayerRatio = bloodLustRatio * dragonSlayerAttackBonus / bloodlustAttackBonus * dragonsStrength / enemyArmyStrength;

SpellcastOutcome bestOutcome;

for ( const Battle::Unit * unit : friendly ) {
if ( isSpellcastUselessForUnit( *unit, spell ) ) {
continue;
}

const double unitValue = unit->GetStrength() * dragonSlayerRatio * spellDurationMultiplier( *unit );
bestOutcome.updateOutcome( unitValue, unit->GetHeadIndex(), false );
}

return bestOutcome;
}
Loading