Skip to content

Commit 4258edf

Browse files
authored
Merge branch 'ihhub:master' into master
2 parents 938831e + c9d116d commit 4258edf

9 files changed

Lines changed: 198 additions & 113 deletions

File tree

‎docs/_layouts/default.html‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,20 @@
4242
href="{{ '/assets/css/fonts.css?v=' | append: site.github.build_revision | relative_url }}"
4343
rel="preload"
4444
as="style"
45-
onload="this.rel='stylesheet';this.removeAttribute('onload');"
45+
onload="
46+
this.rel = 'stylesheet';
47+
this.removeAttribute('onload');
48+
"
4649
/>
4750

4851
<link
4952
href="{{ '/assets/css/style.css?v=' | append: site.github.build_revision | relative_url }}"
5053
rel="preload"
5154
as="style"
52-
onload="this.rel='stylesheet';this.removeAttribute('onload');"
55+
onload="
56+
this.rel = 'stylesheet';
57+
this.removeAttribute('onload');
58+
"
5359
/>
5460

5561
<!-- Scripts -->

‎src/fheroes2/ai/ai_battle.h‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ namespace AI
9898

9999
bool isPositionLocatedInDefendedArea( const Battle::Unit & currentUnit, const Battle::Position & pos ) const;
100100

101-
SpellSelection selectBestSpell( Battle::Arena & arena, const Battle::Unit & currentUnit, bool retreating ) const;
101+
SpellSelection selectBestSpell( Battle::Arena & arena, const Battle::Unit & currentUnit, const bool retreating ) const;
102102

103103
SpellcastOutcome spellDamageValue( const Spell & spell, Battle::Arena & arena, const Battle::Unit & currentUnit, const Battle::Units & friendly,
104104
const Battle::Units & enemies, bool retreating ) const;
@@ -108,14 +108,16 @@ namespace AI
108108
SpellcastOutcome spellDragonSlayerValue( const Spell & spell, const Battle::Units & friendly, const Battle::Units & enemies ) const;
109109
SpellcastOutcome spellTeleportValue( Battle::Arena & arena, const Spell & spell, const Battle::Unit & currentUnit, const Battle::Units & enemies ) const;
110110
SpellcastOutcome spellEarthquakeValue( const Battle::Arena & arena, const Spell & spell, const Battle::Units & friendly ) const;
111-
SpellcastOutcome spellEffectValue( const Spell & spell, const Battle::Units & targets ) const;
111+
SpellcastOutcome spellEffectValue( const Spell & spell, const Battle::Units & targets, const Battle::Units & enemies ) const;
112112

113-
double spellEffectValue( const Spell & spell, const Battle::Unit & target, bool targetIsLast, bool forDispel ) const;
113+
double spellEffectValue( const Spell & spell, const Battle::Unit & target, const Battle::Units & enemies, bool targetIsLast, const bool forDispel ) const;
114114
double getSpellDisruptingRayRatio( const Battle::Unit & target ) const;
115115
double getSpellSlowRatio( const Battle::Unit & target ) const;
116116
double getSpellHasteRatio( const Battle::Unit & target ) const;
117117
int32_t spellDurationMultiplier( const Battle::Unit & target ) const;
118118

119+
bool isSpellcastUselessForUnit( const Battle::Unit & unit, const Battle::Units & enemies, const Spell & spell ) const;
120+
119121
static double getMeleeBestOutcome( Battle::Arena & arena, const Battle::Unit & currentUnit, const Battle::Units & enemies, BattleTargetPair & bestTarget );
120122

121123
// When this limit of turns without deaths is exceeded for an attacking AI-controlled hero,

‎src/fheroes2/ai/ai_battle_spell.cpp‎

Lines changed: 100 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <algorithm>
2222
#include <cassert>
2323
#include <cmath>
24+
#include <cstddef>
2425
#include <cstdint>
2526
#include <ostream>
2627
#include <vector>
@@ -58,74 +59,15 @@ namespace
5859
return result;
5960
}
6061

61-
bool isSpellcastUselessForUnit( const Battle::Unit & unit, const Spell & spell )
62+
int32_t getSpellPower( const HeroBase * hero )
6263
{
63-
const int spellID = spell.GetID();
64+
assert( hero != nullptr );
6465

65-
if ( unit.isImmovable() && spellID != Spell::ANTIMAGIC ) {
66-
return true;
67-
}
68-
69-
switch ( spellID ) {
70-
case Spell::BLESS:
71-
case Spell::MASSBLESS:
72-
return unit.Modes( Battle::SP_BLESS );
73-
74-
case Spell::BLOODLUST:
75-
return unit.Modes( Battle::SP_BLOODLUST );
76-
77-
case Spell::CURSE:
78-
case Spell::MASSCURSE:
79-
return unit.Modes( Battle::SP_CURSE );
80-
81-
case Spell::HASTE:
82-
case Spell::MASSHASTE:
83-
return unit.Modes( Battle::SP_HASTE );
84-
85-
case Spell::SHIELD:
86-
case Spell::MASSSHIELD:
87-
return unit.Modes( Battle::SP_SHIELD );
88-
89-
case Spell::SLOW:
90-
case Spell::MASSSLOW:
91-
return unit.Modes( Battle::SP_SLOW );
92-
93-
case Spell::STONESKIN:
94-
case Spell::STEELSKIN:
95-
return unit.Modes( Battle::SP_STONESKIN | Battle::SP_STEELSKIN );
96-
97-
case Spell::BLIND:
98-
case Spell::PARALYZE:
99-
case Spell::PETRIFY:
100-
return unit.Modes( Battle::SP_BLIND | Battle::SP_PARALYZE | Battle::SP_STONE );
101-
102-
case Spell::DRAGONSLAYER:
103-
return unit.Modes( Battle::SP_DRAGONSLAYER );
104-
105-
case Spell::ANTIMAGIC:
106-
return unit.Modes( Battle::SP_ANTIMAGIC );
107-
108-
case Spell::BERSERKER:
109-
return unit.Modes( Battle::SP_BERSERKER );
110-
111-
case Spell::HYPNOTIZE:
112-
return unit.Modes( Battle::SP_HYPNOTIZE );
113-
114-
case Spell::MIRRORIMAGE:
115-
return unit.Modes( Battle::CAP_MIRROROWNER );
116-
117-
case Spell::DISRUPTINGRAY:
118-
return unit.GetDefense() <= 1;
119-
120-
default:
121-
break;
122-
}
123-
124-
return false;
66+
return hero->GetPower() + hero->GetBagArtifacts().getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::EVERY_COMBAT_SPELL_DURATION );
12567
}
12668
}
12769

128-
AI::SpellSelection AI::BattlePlanner::selectBestSpell( Battle::Arena & arena, const Battle::Unit & currentUnit, bool retreating ) const
70+
AI::SpellSelection AI::BattlePlanner::selectBestSpell( Battle::Arena & arena, const Battle::Unit & currentUnit, const bool retreating ) const
12971
{
13072
SpellSelection bestSpell;
13173

@@ -197,10 +139,10 @@ AI::SpellSelection AI::BattlePlanner::selectBestSpell( Battle::Arena & arena, co
197139
checkSelectBestSpell( spell, spellEarthquakeValue( arena, spell, friendly ) );
198140
}
199141
else if ( spell.isApplyToFriends() ) {
200-
checkSelectBestSpell( spell, spellEffectValue( spell, trueFriendly ) );
142+
checkSelectBestSpell( spell, spellEffectValue( spell, trueFriendly, enemies ) );
201143
}
202144
else if ( spell.isApplyToEnemies() ) {
203-
checkSelectBestSpell( spell, spellEffectValue( spell, trueEnemies ) );
145+
checkSelectBestSpell( spell, spellEffectValue( spell, trueEnemies, enemies ) );
204146
}
205147
}
206148

@@ -324,8 +266,7 @@ AI::SpellcastOutcome AI::BattlePlanner::spellDamageValue( const Spell & spell, B
324266

325267
int32_t AI::BattlePlanner::spellDurationMultiplier( const Battle::Unit & target ) const
326268
{
327-
const int32_t duration
328-
= _commander->GetPower() + _commander->GetBagArtifacts().getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::EVERY_COMBAT_SPELL_DURATION );
269+
const int32_t duration = getSpellPower( _commander );
329270

330271
if ( duration < 2 && target.Modes( Battle::TR_MOVED ) ) {
331272
return 0;
@@ -402,12 +343,13 @@ double AI::BattlePlanner::getSpellHasteRatio( const Battle::Unit & target ) cons
402343
return ratio;
403344
}
404345

405-
double AI::BattlePlanner::spellEffectValue( const Spell & spell, const Battle::Unit & target, bool targetIsLast, bool forDispel ) const
346+
double AI::BattlePlanner::spellEffectValue( const Spell & spell, const Battle::Unit & target, const Battle::Units & enemies, bool targetIsLast,
347+
const bool forDispel ) const
406348
{
407349
const int spellID = spell.GetID();
408350

409351
// Make sure that this spell makes sense to apply (skip this check to evaluate the effect of dispelling)
410-
if ( !forDispel && ( isSpellcastUselessForUnit( target, spell ) || !target.AllowApplySpell( spell, _commander ) ) ) {
352+
if ( !forDispel && ( isSpellcastUselessForUnit( target, enemies, spell ) || !target.AllowApplySpell( spell, _commander ) ) ) {
411353
return 0.0;
412354
}
413355

@@ -535,15 +477,15 @@ double AI::BattlePlanner::spellEffectValue( const Spell & spell, const Battle::U
535477
return target.GetStrength() * ratio * spellDurationMultiplier( target );
536478
}
537479

538-
AI::SpellcastOutcome AI::BattlePlanner::spellEffectValue( const Spell & spell, const Battle::Units & targets ) const
480+
AI::SpellcastOutcome AI::BattlePlanner::spellEffectValue( const Spell & spell, const Battle::Units & targets, const Battle::Units & enemies ) const
539481
{
540482
const bool isSingleTargetLeft = targets.size() == 1;
541483
const bool isMassSpell = spell.isMassActions();
542484

543485
SpellcastOutcome bestOutcome;
544486

545487
for ( const Battle::Unit * unit : targets ) {
546-
bestOutcome.updateOutcome( spellEffectValue( spell, *unit, isSingleTargetLeft, false ), unit->GetHeadIndex(), isMassSpell );
488+
bestOutcome.updateOutcome( spellEffectValue( spell, *unit, enemies, isSingleTargetLeft, false ), unit->GetHeadIndex(), isMassSpell );
547489
}
548490

549491
return bestOutcome;
@@ -565,7 +507,7 @@ AI::SpellcastOutcome AI::BattlePlanner::spellDispelValue( const Spell & spell, c
565507
double unitValue = 0;
566508
const std::vector<Spell> & spellList = unit->getCurrentSpellEffects();
567509
for ( const Spell & spellOnFriend : spellList ) {
568-
const double effectValue = spellEffectValue( spellOnFriend, *unit, false, true );
510+
const double effectValue = spellEffectValue( spellOnFriend, *unit, enemies, false, true );
569511
if ( spellOnFriend.isApplyToEnemies() ) {
570512
unitValue += effectValue;
571513
}
@@ -587,7 +529,7 @@ AI::SpellcastOutcome AI::BattlePlanner::spellDispelValue( const Spell & spell, c
587529
double unitValue = 0;
588530
const std::vector<Spell> & spellList = unit->getCurrentSpellEffects();
589531
for ( const Spell & spellOnEnemy : spellList ) {
590-
const double effectValue = spellEffectValue( spellOnEnemy, *unit, enemyLastUnit, true );
532+
const double effectValue = spellEffectValue( spellOnEnemy, *unit, enemies, enemyLastUnit, true );
591533
unitValue += spellOnEnemy.isApplyToFriends() ? effectValue : -effectValue;
592534
}
593535

@@ -716,7 +658,7 @@ AI::SpellcastOutcome AI::BattlePlanner::spellDragonSlayerValue( const Spell & sp
716658
SpellcastOutcome bestOutcome;
717659

718660
for ( const Battle::Unit * unit : friendly ) {
719-
if ( isSpellcastUselessForUnit( *unit, spell ) ) {
661+
if ( isSpellcastUselessForUnit( *unit, enemies, spell ) ) {
720662
continue;
721663
}
722664

@@ -727,6 +669,89 @@ AI::SpellcastOutcome AI::BattlePlanner::spellDragonSlayerValue( const Spell & sp
727669
return bestOutcome;
728670
}
729671

672+
bool AI::BattlePlanner::isSpellcastUselessForUnit( const Battle::Unit & unit, const Battle::Units & enemies, const Spell & spell ) const
673+
{
674+
const int spellID = spell.GetID();
675+
676+
if ( unit.isImmovable() && spellID != Spell::ANTIMAGIC ) {
677+
return true;
678+
}
679+
680+
switch ( spellID ) {
681+
case Spell::BLESS:
682+
case Spell::MASSBLESS:
683+
return unit.Modes( Battle::SP_BLESS );
684+
685+
case Spell::BLOODLUST:
686+
return unit.Modes( Battle::SP_BLOODLUST );
687+
688+
case Spell::CURSE:
689+
case Spell::MASSCURSE:
690+
return unit.Modes( Battle::SP_CURSE );
691+
692+
case Spell::HASTE:
693+
case Spell::MASSHASTE:
694+
return unit.Modes( Battle::SP_HASTE );
695+
696+
case Spell::SHIELD:
697+
case Spell::MASSSHIELD:
698+
// If a spell duration is just 1 round and all shooters already completed their turn
699+
// then this spell is useless.
700+
if ( getSpellPower( _commander ) == 1 ) {
701+
size_t activeShooters{ 0 };
702+
703+
for ( const auto * enemy : enemies ) {
704+
if ( enemy->isArchers() && !enemy->isImmovable() && !enemy->Modes( Battle::TR_MOVED ) ) {
705+
++activeShooters;
706+
}
707+
}
708+
709+
if ( activeShooters == 0 ) {
710+
// No shooters are going to make their move.
711+
return true;
712+
}
713+
}
714+
715+
return unit.Modes( Battle::SP_SHIELD );
716+
717+
case Spell::SLOW:
718+
case Spell::MASSSLOW:
719+
return unit.Modes( Battle::SP_SLOW );
720+
721+
case Spell::STONESKIN:
722+
case Spell::STEELSKIN:
723+
return unit.Modes( Battle::SP_STONESKIN | Battle::SP_STEELSKIN );
724+
725+
case Spell::BLIND:
726+
case Spell::PARALYZE:
727+
case Spell::PETRIFY:
728+
return unit.Modes( Battle::SP_BLIND | Battle::SP_PARALYZE | Battle::SP_STONE );
729+
730+
case Spell::DRAGONSLAYER:
731+
return unit.Modes( Battle::SP_DRAGONSLAYER );
732+
733+
case Spell::ANTIMAGIC:
734+
return unit.Modes( Battle::SP_ANTIMAGIC );
735+
736+
case Spell::BERSERKER:
737+
return unit.Modes( Battle::SP_BERSERKER );
738+
739+
case Spell::HYPNOTIZE:
740+
return unit.Modes( Battle::SP_HYPNOTIZE );
741+
742+
case Spell::MIRRORIMAGE:
743+
return unit.Modes( Battle::CAP_MIRROROWNER );
744+
745+
case Spell::DISRUPTINGRAY:
746+
return unit.GetDefense() <= 1;
747+
748+
default:
749+
break;
750+
}
751+
752+
return false;
753+
}
754+
730755
AI::SpellcastOutcome AI::BattlePlanner::spellTeleportValue( Battle::Arena & arena, const Spell & spell, const Battle::Unit & currentUnit,
731756
const Battle::Units & enemies ) const
732757
{
@@ -739,7 +764,7 @@ AI::SpellcastOutcome AI::BattlePlanner::spellTeleportValue( Battle::Arena & aren
739764
return {};
740765
}
741766

742-
if ( isSpellcastUselessForUnit( currentUnit, spell ) ) {
767+
if ( isSpellcastUselessForUnit( currentUnit, enemies, spell ) ) {
743768
return {};
744769
}
745770

0 commit comments

Comments
 (0)