1 /***************************************************************************
2  *   Copyright (C) 2010 by Andrey Afletdinov <fheroes2@gmail.com>          *
3  *                                                                         *
4  *   Part of the Free Heroes2 Engine:                                      *
5  *   http://sourceforge.net/projects/fheroes2                              *
6  *                                                                         *
7  *   This program is free software; you can redistribute it and/or modify  *
8  *   it under the terms of the GNU General Public License as published by  *
9  *   the Free Software Foundation; either version 2 of the License, or     *
10  *   (at your option) any later version.                                   *
11  *                                                                         *
12  *   This program is distributed in the hope that it will be useful,       *
13  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
14  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
15  *   GNU General Public License for more details.                          *
16  *                                                                         *
17  *   You should have received a copy of the GNU General Public License     *
18  *   along with this program; if not, write to the                         *
19  *   Free Software Foundation, Inc.,                                       *
20  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
21  ***************************************************************************/
22 
23 #include <algorithm>
24 #include <cassert>
25 #include <cstring>
26 #include <iomanip>
27 
28 #include "agg_image.h"
29 #include "battle.h"
30 #include "battle_arena.h"
31 #include "battle_army.h"
32 #include "battle_cell.h"
33 #include "battle_interface.h"
34 #include "battle_troop.h"
35 #include "game_static.h"
36 #include "logging.h"
37 #include "monster_anim.h"
38 #include "morale.h"
39 #include "speed.h"
40 #include "spell_info.h"
41 #include "tools.h"
42 #include "translations.h"
43 #include "world.h"
44 
ModeDuration(u32 mode,u32 duration)45 Battle::ModeDuration::ModeDuration( u32 mode, u32 duration )
46     : std::pair<u32, u32>( mode, duration )
47 {}
48 
isMode(u32 mode) const49 bool Battle::ModeDuration::isMode( u32 mode ) const
50 {
51     return ( first & mode ) != 0;
52 }
53 
isZeroDuration(void) const54 bool Battle::ModeDuration::isZeroDuration( void ) const
55 {
56     return 0 == second;
57 }
58 
DecreaseDuration(void)59 void Battle::ModeDuration::DecreaseDuration( void )
60 {
61     if ( second )
62         --second;
63 }
64 
ModesAffected()65 Battle::ModesAffected::ModesAffected()
66 {
67     reserve( 3 );
68 }
69 
GetMode(u32 mode) const70 u32 Battle::ModesAffected::GetMode( u32 mode ) const
71 {
72     const_iterator it = std::find_if( begin(), end(), [mode]( const Battle::ModeDuration & v ) { return v.isMode( mode ); } );
73     return it == end() ? 0 : ( *it ).second;
74 }
75 
AddMode(u32 mode,u32 duration)76 void Battle::ModesAffected::AddMode( u32 mode, u32 duration )
77 {
78     iterator it = std::find_if( begin(), end(), [mode]( const Battle::ModeDuration & v ) { return v.isMode( mode ); } );
79     if ( it == end() )
80         emplace_back( mode, duration );
81     else
82         ( *it ).second = duration;
83 }
84 
RemoveMode(u32 mode)85 void Battle::ModesAffected::RemoveMode( u32 mode )
86 {
87     iterator it = std::find_if( begin(), end(), [mode]( const Battle::ModeDuration & v ) { return v.isMode( mode ); } );
88     if ( it != end() ) {
89         if ( it + 1 != end() )
90             std::swap( *it, back() );
91         pop_back();
92     }
93 }
94 
DecreaseDuration(void)95 void Battle::ModesAffected::DecreaseDuration( void )
96 {
97     std::for_each( begin(), end(), []( Battle::ModeDuration & v ) { v.DecreaseDuration(); } );
98 }
99 
FindZeroDuration(void) const100 u32 Battle::ModesAffected::FindZeroDuration( void ) const
101 {
102     const_iterator it = std::find_if( begin(), end(), []( const Battle::ModeDuration & v ) { return v.isZeroDuration(); } );
103     return it == end() ? 0 : ( *it ).first;
104 }
105 
Unit(const Troop & t,int32_t pos,bool ref,const Rand::DeterministicRandomGenerator & randomGenerator,const uint32_t uid)106 Battle::Unit::Unit( const Troop & t, int32_t pos, bool ref, const Rand::DeterministicRandomGenerator & randomGenerator, const uint32_t uid )
107     : ArmyTroop( nullptr, t )
108     , animation( id )
109     , _uid( uid )
110     , hp( t.GetHitPoints() )
111     , count0( t.GetCount() )
112     , dead( 0 )
113     , shots( t.GetShots() )
114     , disruptingray( 0 )
115     , reflect( ref )
116     , mirror( nullptr )
117     , idleTimer( animation.getIdleDelay() )
118     , blindanswer( false )
119     , customAlphaMask( 255 )
120     , _randomGenerator( randomGenerator )
121 {
122     // set position
123     if ( Board::isValidIndex( pos ) ) {
124         if ( t.isWide() )
125             pos += ( reflect ? -1 : 1 );
126         SetPosition( pos );
127     }
128     else {
129         DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "Invalid position " << pos << " for board" );
130     }
131 }
132 
~Unit()133 Battle::Unit::~Unit()
134 {
135     // reset summon elemental and mirror image
136     if ( Modes( CAP_SUMMONELEM ) || Modes( CAP_MIRRORIMAGE ) ) {
137         SetCount( 0 );
138     }
139 }
140 
SetPosition(s32 pos)141 void Battle::Unit::SetPosition( s32 pos )
142 {
143     if ( position.GetHead() )
144         position.GetHead()->SetUnit( nullptr );
145     if ( position.GetTail() )
146         position.GetTail()->SetUnit( nullptr );
147 
148     position.Set( pos, isWide(), reflect );
149 
150     if ( position.GetHead() )
151         position.GetHead()->SetUnit( this );
152     if ( position.GetTail() )
153         position.GetTail()->SetUnit( this );
154 }
155 
SetPosition(const Position & pos)156 void Battle::Unit::SetPosition( const Position & pos )
157 {
158     if ( position.GetHead() )
159         position.GetHead()->SetUnit( nullptr );
160     if ( position.GetTail() )
161         position.GetTail()->SetUnit( nullptr );
162 
163     position = pos;
164 
165     if ( position.GetHead() )
166         position.GetHead()->SetUnit( this );
167     if ( position.GetTail() )
168         position.GetTail()->SetUnit( this );
169 
170     if ( isWide() ) {
171         reflect = GetHeadIndex() < GetTailIndex();
172     }
173 }
174 
SetReflection(bool r)175 void Battle::Unit::SetReflection( bool r )
176 {
177     if ( reflect != r )
178         position.Swap();
179 
180     reflect = r;
181 }
182 
UpdateDirection(void)183 void Battle::Unit::UpdateDirection( void )
184 {
185     // set auto reflect
186     SetReflection( GetArena()->GetArmyColor1() != GetArmyColor() );
187 }
188 
UpdateDirection(const fheroes2::Rect & pos)189 bool Battle::Unit::UpdateDirection( const fheroes2::Rect & pos )
190 {
191     bool need = position.GetRect().x == pos.x ? reflect : position.GetRect().x > pos.x;
192 
193     if ( need != reflect ) {
194         SetReflection( need );
195         return true;
196     }
197     return false;
198 }
199 
isBattle(void) const200 bool Battle::Unit::isBattle( void ) const
201 {
202     return true;
203 }
204 
isModes(u32 v) const205 bool Battle::Unit::isModes( u32 v ) const
206 {
207     return Modes( v );
208 }
209 
GetShotString(void) const210 std::string Battle::Unit::GetShotString( void ) const
211 {
212     if ( Troop::GetShots() == GetShots() )
213         return std::to_string( Troop::GetShots() );
214 
215     std::string output( std::to_string( Troop::GetShots() ) );
216     output += " (";
217     output += std::to_string( GetShots() );
218     output += ')';
219 
220     return output;
221 }
222 
GetSpeedString() const223 std::string Battle::Unit::GetSpeedString() const
224 {
225     const uint32_t speedValue = GetSpeed( true, false );
226 
227     std::string output( Speed::String( speedValue ) );
228     output += " (";
229     output += std::to_string( speedValue );
230     output += ')';
231 
232     return output;
233 }
234 
GetInitialCount() const235 uint32_t Battle::Unit::GetInitialCount() const
236 {
237     return count0;
238 }
239 
GetDead(void) const240 u32 Battle::Unit::GetDead( void ) const
241 {
242     return dead;
243 }
244 
GetHitPointsLeft(void) const245 u32 Battle::Unit::GetHitPointsLeft( void ) const
246 {
247     return GetHitPoints() - ( GetCount() - 1 ) * Monster::GetHitPoints();
248 }
249 
GetMissingHitPoints() const250 uint32_t Battle::Unit::GetMissingHitPoints() const
251 {
252     const uint32_t totalHitPoints = count0 * Monster::GetHitPoints();
253     assert( totalHitPoints > hp );
254     return totalHitPoints - hp;
255 }
256 
GetAffectedDuration(u32 mod) const257 u32 Battle::Unit::GetAffectedDuration( u32 mod ) const
258 {
259     return affected.GetMode( mod );
260 }
261 
GetSpeed(void) const262 u32 Battle::Unit::GetSpeed( void ) const
263 {
264     return GetSpeed( false, false );
265 }
266 
GetMorale() const267 int Battle::Unit::GetMorale() const
268 {
269     int armyTroopMorale = ArmyTroop::GetMorale();
270 
271     // enemy Bone dragons affect morale
272     if ( isAffectedByMorale() && GetArena()->getEnemyForce( GetArmyColor() ).HasMonster( Monster::BONE_DRAGON ) && armyTroopMorale > Morale::TREASON )
273         --armyTroopMorale;
274 
275     return armyTroopMorale;
276 }
277 
isUID(u32 v) const278 bool Battle::Unit::isUID( u32 v ) const
279 {
280     return _uid == v;
281 }
282 
GetUID(void) const283 u32 Battle::Unit::GetUID( void ) const
284 {
285     return _uid;
286 }
287 
GetMirror()288 Battle::Unit * Battle::Unit::GetMirror()
289 {
290     return mirror;
291 }
292 
SetMirror(Unit * ptr)293 void Battle::Unit::SetMirror( Unit * ptr )
294 {
295     mirror = ptr;
296 }
297 
GetShots(void) const298 u32 Battle::Unit::GetShots( void ) const
299 {
300     return shots;
301 }
302 
GetPosition(void) const303 const Battle::Position & Battle::Unit::GetPosition( void ) const
304 {
305     return position;
306 }
307 
GetHeadIndex(void) const308 s32 Battle::Unit::GetHeadIndex( void ) const
309 {
310     return position.GetHead() ? position.GetHead()->GetIndex() : -1;
311 }
312 
GetTailIndex(void) const313 s32 Battle::Unit::GetTailIndex( void ) const
314 {
315     return position.GetTail() ? position.GetTail()->GetIndex() : -1;
316 }
317 
SetRandomMorale(void)318 void Battle::Unit::SetRandomMorale( void )
319 {
320     const int morale = GetMorale();
321 
322     if ( morale > 0 && static_cast<int32_t>( _randomGenerator.Get( 1, 24 ) ) <= morale ) {
323         SetModes( MORALE_GOOD );
324     }
325     else if ( morale < 0 && static_cast<int32_t>( _randomGenerator.Get( 1, 12 ) ) <= -morale ) {
326         if ( isControlHuman() ) {
327             SetModes( MORALE_BAD );
328         }
329         // AI is given a cheeky 25% chance to avoid it - because they build armies from random troops
330         else if ( _randomGenerator.Get( 1, 4 ) != 1 ) {
331             SetModes( MORALE_BAD );
332         }
333     }
334 }
335 
SetRandomLuck(void)336 void Battle::Unit::SetRandomLuck( void )
337 {
338     const int32_t luck = GetLuck();
339     const int32_t chance = static_cast<int32_t>( _randomGenerator.Get( 1, 24 ) );
340 
341     if ( luck > 0 && chance <= luck ) {
342         SetModes( LUCK_GOOD );
343     }
344     else if ( luck < 0 && chance <= -luck ) {
345         SetModes( LUCK_BAD );
346     }
347 
348     // Bless, Curse and Luck do stack
349 }
350 
isFlying(void) const351 bool Battle::Unit::isFlying( void ) const
352 {
353     return ArmyTroop::isFlying() && !Modes( SP_SLOW );
354 }
355 
isValid(void) const356 bool Battle::Unit::isValid( void ) const
357 {
358     return GetCount() != 0;
359 }
360 
isReflect(void) const361 bool Battle::Unit::isReflect( void ) const
362 {
363     return reflect;
364 }
365 
OutOfWalls(void) const366 bool Battle::Unit::OutOfWalls( void ) const
367 {
368     return Board::isOutOfWallsIndex( GetHeadIndex() ) || ( isWide() && Board::isOutOfWallsIndex( GetTailIndex() ) );
369 }
370 
canReach(int index) const371 bool Battle::Unit::canReach( int index ) const
372 {
373     if ( !Board::isValidIndex( index ) )
374         return false;
375 
376     if ( isFlying() || ( isArchers() && !isHandFighting() ) )
377         return true;
378 
379     const bool isIndirectAttack = isReflect() == Board::isNegativeDistance( GetHeadIndex(), index );
380     const int from = ( isWide() && isIndirectAttack ) ? GetTailIndex() : GetHeadIndex();
381     return Board::GetDistance( from, index ) <= GetSpeed( true, false );
382 }
383 
canReach(const Unit & unit) const384 bool Battle::Unit::canReach( const Unit & unit ) const
385 {
386     if ( unit.Modes( CAP_TOWER ) )
387         return false;
388 
389     const bool isIndirectAttack = isReflect() == Board::isNegativeDistance( GetHeadIndex(), unit.GetHeadIndex() );
390     const int target = ( unit.isWide() && isIndirectAttack ) ? unit.GetTailIndex() : unit.GetHeadIndex();
391     return canReach( target );
392 }
393 
isHandFighting(void) const394 bool Battle::Unit::isHandFighting( void ) const
395 {
396     if ( GetCount() && !Modes( CAP_TOWER ) ) {
397         const Indexes around = Board::GetAroundIndexes( *this );
398 
399         for ( Indexes::const_iterator it = around.begin(); it != around.end(); ++it ) {
400             const Unit * enemy = Board::GetCell( *it )->GetUnit();
401             if ( enemy && enemy->GetColor() != GetColor() )
402                 return true;
403         }
404     }
405 
406     return false;
407 }
408 
isHandFighting(const Unit & a,const Unit & b)409 bool Battle::Unit::isHandFighting( const Unit & a, const Unit & b )
410 {
411     return a.isValid() && !a.Modes( CAP_TOWER ) && b.isValid() && b.GetColor() != a.GetCurrentColor()
412            && ( Board::isNearIndexes( a.GetHeadIndex(), b.GetHeadIndex() ) || ( b.isWide() && Board::isNearIndexes( a.GetHeadIndex(), b.GetTailIndex() ) )
413                 || ( a.isWide()
414                      && ( Board::isNearIndexes( a.GetTailIndex(), b.GetHeadIndex() )
415                           || ( b.isWide() && Board::isNearIndexes( a.GetTailIndex(), b.GetTailIndex() ) ) ) ) );
416 }
417 
GetAnimationState() const418 int Battle::Unit::GetAnimationState() const
419 {
420     return animation.getCurrentState();
421 }
422 
isIdling() const423 bool Battle::Unit::isIdling() const
424 {
425     return GetAnimationState() == Monster_Info::IDLE;
426 }
427 
checkIdleDelay()428 bool Battle::Unit::checkIdleDelay()
429 {
430     return idleTimer.checkDelay();
431 }
432 
NewTurn(void)433 void Battle::Unit::NewTurn( void )
434 {
435     if ( isRegenerating() )
436         hp = ArmyTroop::GetHitPoints();
437 
438     ResetModes( TR_RESPONSED );
439     ResetModes( TR_MOVED );
440     ResetModes( TR_HARDSKIP );
441     ResetModes( TR_SKIPMOVE );
442     ResetModes( LUCK_GOOD );
443     ResetModes( LUCK_BAD );
444     ResetModes( MORALE_GOOD );
445     ResetModes( MORALE_BAD );
446 
447     // decrease spell duration
448     affected.DecreaseDuration();
449 
450     // remove spell duration
451     u32 mode = 0;
452     while ( 0 != ( mode = affected.FindZeroDuration() ) ) {
453         affected.RemoveMode( mode );
454         ResetModes( mode );
455 
456         // cancel mirror image
457         if ( mode == CAP_MIRROROWNER && mirror ) {
458             if ( Arena::GetInterface() ) {
459                 std::vector<Unit *> images;
460                 images.push_back( mirror );
461                 Arena::GetInterface()->RedrawActionRemoveMirrorImage( images );
462             }
463 
464             mirror->SetCount( 0 );
465             mirror = nullptr;
466         }
467     }
468 }
469 
GetSpeed(bool skipStandingCheck,bool skipMovedCheck) const470 u32 Battle::Unit::GetSpeed( bool skipStandingCheck, bool skipMovedCheck ) const
471 {
472     uint32_t modesToCheck = SP_BLIND | IS_PARALYZE_MAGIC;
473     if ( !skipMovedCheck ) {
474         modesToCheck |= TR_MOVED;
475     }
476 
477     if ( !skipStandingCheck && ( !GetCount() || Modes( modesToCheck ) ) )
478         return Speed::STANDING;
479 
480     uint32_t speed = Monster::GetSpeed();
481     Spell spell;
482 
483     if ( Modes( SP_HASTE ) ) {
484         return Speed::GetHasteSpeedFromSpell( speed );
485     }
486     else if ( Modes( SP_SLOW ) ) {
487         return Speed::GetSlowSpeedFromSpell( speed );
488     }
489 
490     return speed;
491 }
492 
GetMoveRange() const493 uint32_t Battle::Unit::GetMoveRange() const
494 {
495     return isFlying() ? ARENASIZE : GetSpeed( false, false );
496 }
497 
CalculateRetaliationDamage(uint32_t damageTaken) const498 uint32_t Battle::Unit::CalculateRetaliationDamage( uint32_t damageTaken ) const
499 {
500     // Check if there will be retaliation in the first place
501     if ( damageTaken > hp || Modes( CAP_MIRRORIMAGE ) || !AllowResponse() )
502         return 0;
503 
504     const uint32_t unitsLeft = ( hp - damageTaken ) / Monster::GetHitPoints();
505 
506     uint32_t damagePerUnit = 0;
507     if ( Modes( SP_CURSE ) )
508         damagePerUnit = Monster::GetDamageMin();
509     else if ( Modes( SP_BLESS ) )
510         damagePerUnit = Monster::GetDamageMax();
511     else
512         damagePerUnit = ( Monster::GetDamageMin() + Monster::GetDamageMax() ) / 2;
513 
514     return unitsLeft * damagePerUnit;
515 }
516 
CalculateMinDamage(const Unit & enemy) const517 u32 Battle::Unit::CalculateMinDamage( const Unit & enemy ) const
518 {
519     return CalculateDamageUnit( enemy, ArmyTroop::GetDamageMin() );
520 }
521 
CalculateMaxDamage(const Unit & enemy) const522 u32 Battle::Unit::CalculateMaxDamage( const Unit & enemy ) const
523 {
524     return CalculateDamageUnit( enemy, ArmyTroop::GetDamageMax() );
525 }
526 
CalculateDamageUnit(const Unit & enemy,double dmg) const527 u32 Battle::Unit::CalculateDamageUnit( const Unit & enemy, double dmg ) const
528 {
529     if ( isArchers() ) {
530         if ( !isHandFighting() ) {
531             // check skill archery +%10, +%25, +%50
532             if ( GetCommander() ) {
533                 dmg += ( dmg * GetCommander()->GetSecondaryValues( Skill::Secondary::ARCHERY ) / 100 );
534             }
535 
536             // check castle defense
537             if ( GetArena()->IsShootingPenalty( *this, enemy ) )
538                 dmg /= 2;
539 
540             // check spell shield
541             if ( enemy.Modes( SP_SHIELD ) )
542                 dmg /= Spell( Spell::SHIELD ).ExtraValue();
543         }
544         else if ( !isAbilityPresent( fheroes2::MonsterAbilityType::NO_MELEE_PENALTY ) ) {
545             dmg /= 2;
546         }
547     }
548 
549     // after blind
550     if ( blindanswer )
551         dmg /= 2;
552 
553     // stone cap.
554     if ( enemy.Modes( SP_STONE ) )
555         dmg /= 2;
556 
557     // check monster capability
558     switch ( GetID() ) {
559     case Monster::CRUSADER:
560         // double damage for undead
561         if ( enemy.isUndead() )
562             dmg *= 2;
563         break;
564     case Monster::FIRE_ELEMENT:
565         if ( enemy.GetID() == Monster::WATER_ELEMENT )
566             dmg *= 2;
567         break;
568     case Monster::WATER_ELEMENT:
569         if ( enemy.GetID() == Monster::FIRE_ELEMENT )
570             dmg *= 2;
571         break;
572     case Monster::AIR_ELEMENT:
573         if ( enemy.GetID() == Monster::EARTH_ELEMENT )
574             dmg *= 2;
575         break;
576     case Monster::EARTH_ELEMENT:
577         if ( enemy.GetID() == Monster::AIR_ELEMENT )
578             dmg *= 2;
579         break;
580     default:
581         break;
582     }
583 
584     int r = GetAttack() - enemy.GetDefense();
585     if ( enemy.isDragons() && Modes( SP_DRAGONSLAYER ) )
586         r += Spell( Spell::DRAGONSLAYER ).ExtraValue();
587 
588     // Attack bonus is 20% to 300%
589     dmg *= 1 + ( 0 < r ? 0.1 * std::min( r, 20 ) : 0.05 * std::max( r, -16 ) );
590 
591     return static_cast<u32>( dmg ) < 1 ? 1 : static_cast<u32>( dmg );
592 }
593 
GetDamage(const Unit & enemy) const594 u32 Battle::Unit::GetDamage( const Unit & enemy ) const
595 {
596     u32 res = 0;
597 
598     if ( Modes( SP_BLESS ) )
599         res = CalculateMaxDamage( enemy );
600     else if ( Modes( SP_CURSE ) )
601         res = CalculateMinDamage( enemy );
602     else
603         res = _randomGenerator.Get( CalculateMinDamage( enemy ), CalculateMaxDamage( enemy ) );
604 
605     if ( Modes( LUCK_GOOD ) )
606         res <<= 1; // mul 2
607     else if ( Modes( LUCK_BAD ) )
608         res >>= 1; // div 2
609 
610     return res;
611 }
612 
HowManyWillKilled(u32 dmg) const613 u32 Battle::Unit::HowManyWillKilled( u32 dmg ) const
614 {
615     return dmg >= hp ? GetCount() : GetCount() - Monster::GetCountFromHitPoints( *this, hp - dmg );
616 }
617 
ApplyDamage(u32 dmg)618 u32 Battle::Unit::ApplyDamage( u32 dmg )
619 {
620     if ( dmg && GetCount() ) {
621         u32 killed = HowManyWillKilled( dmg );
622 
623         // mirror image dies if recieves any damage
624         if ( Modes( CAP_MIRRORIMAGE ) ) {
625             dmg = hp;
626             killed = GetCount();
627         }
628 
629         DEBUG_LOG( DBG_BATTLE, DBG_TRACE, dmg << " to " << String() << " and killed: " << killed );
630 
631         // clean paralyze or stone magic
632         if ( Modes( IS_PARALYZE_MAGIC ) ) {
633             SetModes( TR_RESPONSED );
634             SetModes( TR_MOVED );
635             ResetModes( IS_PARALYZE_MAGIC );
636             affected.RemoveMode( IS_PARALYZE_MAGIC );
637         }
638 
639         // blind
640         if ( Modes( SP_BLIND ) ) {
641             ResetBlind();
642         }
643 
644         if ( killed >= GetCount() ) {
645             dead += GetCount();
646             SetCount( 0 );
647         }
648         else {
649             dead += killed;
650             SetCount( GetCount() - killed );
651         }
652         hp -= ( dmg >= hp ? hp : dmg );
653 
654         return killed;
655     }
656 
657     return 0;
658 }
659 
PostKilledAction(void)660 void Battle::Unit::PostKilledAction( void )
661 {
662     // kill mirror image (master)
663     if ( Modes( CAP_MIRROROWNER ) ) {
664         modes = 0;
665         mirror->hp = 0;
666         mirror->SetCount( 0 );
667         mirror->mirror = nullptr;
668         mirror = nullptr;
669         ResetModes( CAP_MIRROROWNER );
670     }
671     // kill mirror image (slave)
672     if ( Modes( CAP_MIRRORIMAGE ) && mirror != nullptr ) {
673         mirror->ResetModes( CAP_MIRROROWNER );
674         mirror = nullptr;
675     }
676 
677     ResetModes( TR_RESPONSED );
678     ResetModes( TR_HARDSKIP );
679     ResetModes( TR_SKIPMOVE );
680     ResetModes( LUCK_GOOD );
681     ResetModes( LUCK_BAD );
682     ResetModes( MORALE_GOOD );
683     ResetModes( MORALE_BAD );
684     ResetModes( IS_MAGIC );
685 
686     SetModes( TR_MOVED );
687 
688     // save troop to graveyard
689     // skip mirror and summon
690     if ( !Modes( CAP_MIRRORIMAGE ) && !Modes( CAP_SUMMONELEM ) )
691         Arena::GetGraveyard()->AddTroop( *this );
692 
693     Cell * head = Board::GetCell( GetHeadIndex() );
694     Cell * tail = Board::GetCell( GetTailIndex() );
695     if ( head )
696         head->SetUnit( nullptr );
697     if ( tail )
698         tail->SetUnit( nullptr );
699 
700     DEBUG_LOG( DBG_BATTLE, DBG_TRACE, String() << ", is dead..." );
701     // possible also..
702 }
703 
Resurrect(u32 points,bool allow_overflow,bool skip_dead)704 u32 Battle::Unit::Resurrect( u32 points, bool allow_overflow, bool skip_dead )
705 {
706     u32 resurrect = Monster::GetCountFromHitPoints( *this, hp + points ) - GetCount();
707 
708     if ( hp == 0 ) // Skip turn if already dead
709         SetModes( TR_MOVED );
710 
711     SetCount( GetCount() + resurrect );
712     hp += points;
713 
714     if ( allow_overflow ) {
715         if ( count0 < GetCount() )
716             count0 = GetCount();
717     }
718     else if ( GetCount() > count0 ) {
719         resurrect -= GetCount() - count0;
720         SetCount( count0 );
721         hp = ArmyTroop::GetHitPoints();
722     }
723 
724     if ( !skip_dead )
725         dead -= ( resurrect < dead ? resurrect : dead );
726 
727     return resurrect;
728 }
729 
ApplyDamage(Unit & enemy,u32 dmg)730 u32 Battle::Unit::ApplyDamage( Unit & enemy, u32 dmg )
731 {
732     u32 killed = ApplyDamage( dmg );
733     u32 resurrect;
734 
735     if ( killed )
736         switch ( enemy.GetID() ) {
737         case Monster::GHOST:
738             resurrect = killed * static_cast<Monster &>( enemy ).GetHitPoints();
739             DEBUG_LOG( DBG_BATTLE, DBG_TRACE, String() << ", enemy: " << enemy.String() << " resurrect: " << resurrect );
740             // grow troop
741             enemy.Resurrect( resurrect, true, false );
742             break;
743 
744         case Monster::VAMPIRE_LORD:
745             resurrect = killed * Monster::GetHitPoints();
746             DEBUG_LOG( DBG_BATTLE, DBG_TRACE, String() << ", enemy: " << enemy.String() << " resurrect: " << resurrect );
747             // restore hit points
748             enemy.Resurrect( resurrect, false, false );
749             break;
750 
751         default:
752             break;
753         }
754 
755     return killed;
756 }
757 
AllowApplySpell(const Spell & spell,const HeroBase * hero,std::string * msg,bool forceApplyToAlly) const758 bool Battle::Unit::AllowApplySpell( const Spell & spell, const HeroBase * hero, std::string * msg, bool forceApplyToAlly ) const
759 {
760     if ( Modes( CAP_MIRRORIMAGE ) && ( spell == Spell::ANTIMAGIC || spell == Spell::MIRRORIMAGE ) ) {
761         return false;
762     }
763 
764     if ( Modes( CAP_MIRROROWNER ) && spell == Spell::MIRRORIMAGE ) {
765         return false;
766     }
767 
768     // check global
769     // if(GetArena()->DisableCastSpell(spell, msg)) return false; // disable - recursion!
770 
771     if ( hero && spell.isApplyToFriends() && GetColor() != hero->GetColor() )
772         return false;
773     if ( hero && spell.isApplyToEnemies() && GetColor() == hero->GetColor() && !forceApplyToAlly )
774         return false;
775     if ( isMagicResist( spell, ( hero ? hero->GetPower() : 0 ) ) )
776         return false;
777 
778     const HeroBase * myhero = GetCommander();
779     if ( !myhero )
780         return true;
781 
782     // check artifact
783     Artifact guard_art( Artifact::UNKNOWN );
784     switch ( spell.GetID() ) {
785     case Spell::CURSE:
786     case Spell::MASSCURSE:
787         guard_art = Artifact::HOLY_PENDANT;
788         break;
789     case Spell::HYPNOTIZE:
790         guard_art = Artifact::PENDANT_FREE_WILL;
791         break;
792     case Spell::DEATHRIPPLE:
793     case Spell::DEATHWAVE:
794         guard_art = Artifact::PENDANT_LIFE;
795         break;
796     case Spell::BERSERKER:
797         guard_art = Artifact::SERENITY_PENDANT;
798         break;
799     case Spell::BLIND:
800         guard_art = Artifact::SEEING_EYE_PENDANT;
801         break;
802     case Spell::PARALYZE:
803         guard_art = Artifact::KINETIC_PENDANT;
804         break;
805     case Spell::HOLYWORD:
806     case Spell::HOLYSHOUT:
807         guard_art = Artifact::PENDANT_DEATH;
808         break;
809     case Spell::DISPEL:
810         guard_art = Artifact::WAND_NEGATION;
811         break;
812 
813     default:
814         break;
815     }
816 
817     if ( guard_art.isValid() && myhero->hasArtifact( guard_art ) ) {
818         if ( msg ) {
819             *msg = _( "The %{artifact} artifact is in effect for this battle, disabling %{spell} spell." );
820             StringReplace( *msg, "%{artifact}", guard_art.GetName() );
821             StringReplace( *msg, "%{spell}", spell.GetName() );
822         }
823         return false;
824     }
825 
826     return true;
827 }
828 
isUnderSpellEffect(const Spell & spell) const829 bool Battle::Unit::isUnderSpellEffect( const Spell & spell ) const
830 {
831     switch ( spell.GetID() ) {
832     case Spell::BLESS:
833     case Spell::MASSBLESS:
834         return Modes( SP_BLESS );
835 
836     case Spell::BLOODLUST:
837         return Modes( SP_BLOODLUST );
838 
839     case Spell::CURSE:
840     case Spell::MASSCURSE:
841         return Modes( SP_CURSE );
842 
843     case Spell::HASTE:
844     case Spell::MASSHASTE:
845         return Modes( SP_HASTE );
846 
847     case Spell::SHIELD:
848     case Spell::MASSSHIELD:
849         return Modes( SP_SHIELD );
850 
851     case Spell::SLOW:
852     case Spell::MASSSLOW:
853         return Modes( SP_SLOW );
854 
855     case Spell::STONESKIN:
856     case Spell::STEELSKIN:
857         return Modes( SP_STONESKIN | SP_STEELSKIN );
858 
859     case Spell::BLIND:
860     case Spell::PARALYZE:
861     case Spell::STONE:
862         return Modes( SP_BLIND | SP_PARALYZE | SP_STONE );
863 
864     case Spell::DRAGONSLAYER:
865         return Modes( SP_DRAGONSLAYER );
866 
867     case Spell::ANTIMAGIC:
868         return Modes( SP_ANTIMAGIC );
869 
870     case Spell::BERSERKER:
871         return Modes( SP_BERSERKER );
872 
873     case Spell::HYPNOTIZE:
874         return Modes( SP_HYPNOTIZE );
875 
876     case Spell::MIRRORIMAGE:
877         return Modes( CAP_MIRROROWNER );
878 
879     case Spell::DISRUPTINGRAY:
880         return GetDefense() < spell.ExtraValue();
881 
882     default:
883         break;
884     }
885     return false;
886 }
887 
ApplySpell(const Spell & spell,const HeroBase * hero,TargetInfo & target)888 bool Battle::Unit::ApplySpell( const Spell & spell, const HeroBase * hero, TargetInfo & target )
889 {
890     // HACK!!! Chain lightining is the only spell which can't be casted on allies but could be applied on them
891     const bool isForceApply = ( spell.GetID() == Spell::CHAINLIGHTNING );
892 
893     if ( !AllowApplySpell( spell, hero, nullptr, isForceApply ) )
894         return false;
895 
896     DEBUG_LOG( DBG_BATTLE, DBG_TRACE, spell.GetName() << " to " << String() );
897 
898     const u32 spoint = hero ? hero->GetPower() : DEFAULT_SPELL_DURATION;
899 
900     if ( spell.isDamage() )
901         SpellApplyDamage( spell, spoint, hero, target );
902     else if ( spell.isRestore() )
903         SpellRestoreAction( spell, spoint, hero );
904     else {
905         SpellModesAction( spell, spoint, hero );
906     }
907 
908     return true;
909 }
910 
getCurrentSpellEffects() const911 std::vector<Spell> Battle::Unit::getCurrentSpellEffects() const
912 {
913     std::vector<Spell> spellList;
914 
915     if ( Modes( SP_BLESS ) ) {
916         spellList.emplace_back( Spell::BLESS );
917     }
918     if ( Modes( SP_CURSE ) ) {
919         spellList.emplace_back( Spell::CURSE );
920     }
921     if ( Modes( SP_HASTE ) ) {
922         spellList.emplace_back( Spell::HASTE );
923     }
924     if ( Modes( SP_SLOW ) ) {
925         spellList.emplace_back( Spell::SLOW );
926     }
927     if ( Modes( SP_SHIELD ) ) {
928         spellList.emplace_back( Spell::SHIELD );
929     }
930     if ( Modes( SP_BLOODLUST ) ) {
931         spellList.emplace_back( Spell::BLOODLUST );
932     }
933     if ( Modes( SP_STONESKIN ) ) {
934         spellList.emplace_back( Spell::STONESKIN );
935     }
936     if ( Modes( SP_STEELSKIN ) ) {
937         spellList.emplace_back( Spell::STEELSKIN );
938     }
939     if ( Modes( SP_BLIND ) ) {
940         spellList.emplace_back( Spell::BLIND );
941     }
942     if ( Modes( SP_PARALYZE ) ) {
943         spellList.emplace_back( Spell::PARALYZE );
944     }
945     if ( Modes( SP_STONE ) ) {
946         spellList.emplace_back( Spell::STONE );
947     }
948     if ( Modes( SP_DRAGONSLAYER ) ) {
949         spellList.emplace_back( Spell::DRAGONSLAYER );
950     }
951     if ( Modes( SP_BERSERKER ) ) {
952         spellList.emplace_back( Spell::BERSERKER );
953     }
954     if ( Modes( SP_HYPNOTIZE ) ) {
955         spellList.emplace_back( Spell::HYPNOTIZE );
956     }
957     if ( Modes( CAP_MIRROROWNER ) ) {
958         spellList.emplace_back( Spell::MIRRORIMAGE );
959     }
960 
961     return spellList;
962 }
963 
String(bool more) const964 std::string Battle::Unit::String( bool more ) const
965 {
966     std::stringstream ss;
967 
968     ss << "Unit: "
969        << "[ " <<
970         // info
971         GetCount() << " " << GetName() << ", " << Color::String( GetColor() ) << ", pos: " << GetHeadIndex() << ", " << GetTailIndex() << ( reflect ? ", reflect" : "" );
972 
973     if ( more )
974         ss << ", mode("
975            << "0x" << std::hex << modes << std::dec << ")"
976            << ", uid("
977            << "0x" << std::setw( 8 ) << std::setfill( '0' ) << std::hex << _uid << std::dec << ")"
978            << ", speed(" << Speed::String( GetSpeed() ) << ", " << static_cast<int>( GetSpeed() ) << ")"
979            << ", hp(" << hp << ")"
980            << ", die(" << dead << ")"
981            << ")";
982 
983     ss << " ]";
984 
985     return ss.str();
986 }
987 
AllowResponse(void) const988 bool Battle::Unit::AllowResponse( void ) const
989 {
990     return ( !Modes( SP_BLIND ) || blindanswer ) && !Modes( IS_PARALYZE_MAGIC ) && !Modes( SP_HYPNOTIZE )
991            && ( isAbilityPresent( fheroes2::MonsterAbilityType::ALWAYS_RETALIATE ) || !Modes( TR_RESPONSED ) );
992 }
993 
SetResponse(void)994 void Battle::Unit::SetResponse( void )
995 {
996     SetModes( TR_RESPONSED );
997 }
998 
PostAttackAction()999 void Battle::Unit::PostAttackAction()
1000 {
1001     // decrease shots
1002     if ( isArchers() && !isHandFighting() ) {
1003         // check ammo cart artifact
1004         const HeroBase * hero = GetCommander();
1005         if ( !hero || !hero->hasArtifact( Artifact::AMMO_CART ) )
1006             --shots;
1007     }
1008 
1009     // clean berserker spell
1010     if ( Modes( SP_BERSERKER ) ) {
1011         ResetModes( SP_BERSERKER );
1012         affected.RemoveMode( SP_BERSERKER );
1013     }
1014 
1015     // clean hypnotize spell
1016     if ( Modes( SP_HYPNOTIZE ) ) {
1017         ResetModes( SP_HYPNOTIZE );
1018         affected.RemoveMode( SP_HYPNOTIZE );
1019     }
1020 
1021     // clean luck capability
1022     ResetModes( LUCK_GOOD );
1023     ResetModes( LUCK_BAD );
1024 }
1025 
ResetBlind(void)1026 void Battle::Unit::ResetBlind( void )
1027 {
1028     // remove blind action
1029     if ( Modes( SP_BLIND ) ) {
1030         SetModes( TR_MOVED );
1031         ResetModes( SP_BLIND );
1032         affected.RemoveMode( SP_BLIND );
1033     }
1034 }
1035 
SetBlindAnswer(bool value)1036 void Battle::Unit::SetBlindAnswer( bool value )
1037 {
1038     blindanswer = value;
1039 }
1040 
GetAttack(void) const1041 u32 Battle::Unit::GetAttack( void ) const
1042 {
1043     u32 res = ArmyTroop::GetAttack();
1044 
1045     if ( Modes( SP_BLOODLUST ) )
1046         res += Spell( Spell::BLOODLUST ).ExtraValue();
1047 
1048     return res;
1049 }
1050 
GetDefense(void) const1051 u32 Battle::Unit::GetDefense( void ) const
1052 {
1053     u32 res = ArmyTroop::GetDefense();
1054 
1055     if ( Modes( SP_STONESKIN ) )
1056         res += Spell( Spell::STONESKIN ).ExtraValue();
1057     else if ( Modes( SP_STEELSKIN ) )
1058         res += Spell( Spell::STEELSKIN ).ExtraValue();
1059 
1060     // disrupting ray accumulate effect
1061     if ( disruptingray ) {
1062         const u32 step = disruptingray * Spell( Spell::DISRUPTINGRAY ).ExtraValue();
1063 
1064         if ( step >= res )
1065             res = 1;
1066         else
1067             res -= step;
1068     }
1069 
1070     // check moat
1071     const Castle * castle = Arena::GetCastle();
1072 
1073     if ( castle && castle->isBuild( BUILD_MOAT ) && ( Board::isMoatIndex( GetHeadIndex(), *this ) || Board::isMoatIndex( GetTailIndex(), *this ) ) ) {
1074         const uint32_t step = GameStatic::GetBattleMoatReduceDefense();
1075 
1076         if ( step >= res )
1077             res = 1;
1078         else
1079             res -= step;
1080     }
1081 
1082     return res;
1083 }
1084 
GetScoreQuality(const Unit & defender) const1085 s32 Battle::Unit::GetScoreQuality( const Unit & defender ) const
1086 {
1087     const Unit & attacker = *this;
1088 
1089     const double defendersDamage = CalculateDamageUnit( attacker, ( static_cast<double>( defender.GetDamageMin() ) + defender.GetDamageMax() ) / 2.0 );
1090     const double attackerPowerLost = ( attacker.Modes( CAP_MIRRORIMAGE ) || defender.Modes( CAP_TOWER ) || defendersDamage >= hp ) ? 1.0 : defendersDamage / hp;
1091     const bool attackerIsArchers = isArchers();
1092 
1093     double attackerThreat = CalculateDamageUnit( defender, ( static_cast<double>( GetDamageMin() ) + GetDamageMax() ) / 2.0 );
1094 
1095     if ( !canReach( defender ) && !defender.Modes( CAP_TOWER ) && !attackerIsArchers ) {
1096         // Can't reach, so unit is not dangerous to defender at the moment
1097         attackerThreat /= 2;
1098     }
1099 
1100     // Monster special abilities
1101     if ( isTwiceAttack() ) {
1102         if ( attackerIsArchers || ignoreRetaliation() || defender.Modes( TR_RESPONSED ) ) {
1103             attackerThreat *= 2;
1104         }
1105         else {
1106             // check how much we will lose to retaliation
1107             attackerThreat += attackerThreat * ( 1.0 - attackerPowerLost );
1108         }
1109     }
1110 
1111     switch ( id ) {
1112     case Monster::UNICORN:
1113         attackerThreat += defendersDamage * 0.2 * ( 100 - defender.GetMagicResist( Spell::BLIND, DEFAULT_SPELL_DURATION ) ) / 100.0;
1114         break;
1115     case Monster::CYCLOPS:
1116         attackerThreat += defendersDamage * 0.2 * ( 100 - defender.GetMagicResist( Spell::PARALYZE, DEFAULT_SPELL_DURATION ) ) / 100.0;
1117         break;
1118     case Monster::MEDUSA:
1119         attackerThreat += defendersDamage * 0.2 * ( 100 - defender.GetMagicResist( Spell::STONE, DEFAULT_SPELL_DURATION ) ) / 100.0;
1120         break;
1121     case Monster::VAMPIRE_LORD:
1122         // Lifesteal
1123         attackerThreat *= 1.3;
1124         break;
1125     case Monster::GENIE:
1126         // Genie's ability to half enemy troops
1127         attackerThreat *= 2;
1128         break;
1129     case Monster::GHOST:
1130         // Ghost's ability to increase the numbers
1131         attackerThreat *= 3;
1132         break;
1133     }
1134 
1135     // force big priority on mirror images as they get destroyed in 1 hit
1136     if ( attacker.Modes( CAP_MIRRORIMAGE ) )
1137         attackerThreat *= 10;
1138 
1139     // Negative value of units that changed the side
1140     if ( attacker.Modes( SP_BERSERKER ) || attacker.Modes( SP_HYPNOTIZE ) ) {
1141         attackerThreat *= -1;
1142     }
1143     // Otherwise heavy penalty for hiting our own units
1144     else if ( attacker.GetArmyColor() == defender.GetArmyColor() ) {
1145         attackerThreat *= -2;
1146     }
1147     // Finally ignore disabled units (if belong to the enemy)
1148     else if ( attacker.Modes( SP_BLIND ) || attacker.Modes( IS_PARALYZE_MAGIC ) ) {
1149         attackerThreat = 0;
1150     }
1151 
1152     // Avoid effectiveness scaling if we're dealing with archers
1153     if ( !attackerIsArchers || defender.isArchers() )
1154         attackerThreat *= attackerPowerLost;
1155 
1156     return static_cast<int>( attackerThreat * 100 );
1157 }
1158 
GetHitPoints(void) const1159 u32 Battle::Unit::GetHitPoints( void ) const
1160 {
1161     return hp;
1162 }
1163 
GetControl(void) const1164 int Battle::Unit::GetControl( void ) const
1165 {
1166     return !GetArmy() ? CONTROL_AI : GetArmy()->GetControl();
1167 }
1168 
isArchers(void) const1169 bool Battle::Unit::isArchers( void ) const
1170 {
1171     return ArmyTroop::isArchers() && shots;
1172 }
1173 
SpellModesAction(const Spell & spell,u32 duration,const HeroBase * hero)1174 void Battle::Unit::SpellModesAction( const Spell & spell, u32 duration, const HeroBase * hero )
1175 {
1176     if ( hero ) {
1177         for ( const Artifact::type_t art : { Artifact::WIZARD_HAT, Artifact::ENCHANTED_HOURGLASS } )
1178             duration += hero->artifactCount( art ) * Artifact( art ).ExtraValue();
1179     }
1180 
1181     switch ( spell.GetID() ) {
1182     case Spell::BLESS:
1183     case Spell::MASSBLESS:
1184         if ( Modes( SP_CURSE ) ) {
1185             ResetModes( SP_CURSE );
1186             affected.RemoveMode( SP_CURSE );
1187         }
1188         SetModes( SP_BLESS );
1189         affected.AddMode( SP_BLESS, duration );
1190         break;
1191 
1192     case Spell::BLOODLUST:
1193         SetModes( SP_BLOODLUST );
1194         affected.AddMode( SP_BLOODLUST, 3 );
1195         break;
1196 
1197     case Spell::CURSE:
1198     case Spell::MASSCURSE:
1199         if ( Modes( SP_BLESS ) ) {
1200             ResetModes( SP_BLESS );
1201             affected.RemoveMode( SP_BLESS );
1202         }
1203         SetModes( SP_CURSE );
1204         affected.AddMode( SP_CURSE, duration );
1205         break;
1206 
1207     case Spell::HASTE:
1208     case Spell::MASSHASTE:
1209         if ( Modes( SP_SLOW ) ) {
1210             ResetModes( SP_SLOW );
1211             affected.RemoveMode( SP_SLOW );
1212         }
1213         SetModes( SP_HASTE );
1214         affected.AddMode( SP_HASTE, duration );
1215         break;
1216 
1217     case Spell::DISPEL:
1218     case Spell::MASSDISPEL:
1219         if ( Modes( IS_MAGIC ) ) {
1220             ResetModes( IS_MAGIC );
1221             affected.RemoveMode( IS_MAGIC );
1222         }
1223         break;
1224 
1225     case Spell::SHIELD:
1226     case Spell::MASSSHIELD:
1227         SetModes( SP_SHIELD );
1228         affected.AddMode( SP_SHIELD, duration );
1229         break;
1230 
1231     case Spell::SLOW:
1232     case Spell::MASSSLOW:
1233         if ( Modes( SP_HASTE ) ) {
1234             ResetModes( SP_HASTE );
1235             affected.RemoveMode( SP_HASTE );
1236         }
1237         SetModes( SP_SLOW );
1238         affected.AddMode( SP_SLOW, duration );
1239         break;
1240 
1241     case Spell::STONESKIN:
1242         if ( Modes( SP_STEELSKIN ) ) {
1243             ResetModes( SP_STEELSKIN );
1244             affected.RemoveMode( SP_STEELSKIN );
1245         }
1246         SetModes( SP_STONESKIN );
1247         affected.AddMode( SP_STONESKIN, duration );
1248         break;
1249 
1250     case Spell::BLIND:
1251         SetModes( SP_BLIND );
1252         blindanswer = false;
1253         affected.AddMode( SP_BLIND, duration );
1254         break;
1255 
1256     case Spell::DRAGONSLAYER:
1257         SetModes( SP_DRAGONSLAYER );
1258         affected.AddMode( SP_DRAGONSLAYER, duration );
1259         break;
1260 
1261     case Spell::STEELSKIN:
1262         if ( Modes( SP_STONESKIN ) ) {
1263             ResetModes( SP_STONESKIN );
1264             affected.RemoveMode( SP_STONESKIN );
1265         }
1266         SetModes( SP_STEELSKIN );
1267         affected.AddMode( SP_STEELSKIN, duration );
1268         break;
1269 
1270     case Spell::ANTIMAGIC:
1271         ResetModes( IS_MAGIC );
1272         SetModes( SP_ANTIMAGIC );
1273         affected.AddMode( SP_ANTIMAGIC, duration );
1274         break;
1275 
1276     case Spell::PARALYZE:
1277         SetModes( SP_PARALYZE );
1278         affected.AddMode( SP_PARALYZE, duration );
1279         break;
1280 
1281     case Spell::BERSERKER:
1282         SetModes( SP_BERSERKER );
1283         affected.AddMode( SP_BERSERKER, duration );
1284         break;
1285 
1286     case Spell::HYPNOTIZE: {
1287         SetModes( SP_HYPNOTIZE );
1288         uint32_t acount = hero ? hero->artifactCount( Artifact::GOLD_WATCH ) : 0;
1289         affected.AddMode( SP_HYPNOTIZE, ( acount ? duration * acount * 2 : duration ) );
1290         break;
1291     }
1292 
1293     case Spell::STONE:
1294         SetModes( SP_STONE );
1295         affected.AddMode( SP_STONE, duration );
1296         break;
1297 
1298     case Spell::MIRRORIMAGE:
1299         affected.AddMode( CAP_MIRRORIMAGE, duration );
1300         break;
1301 
1302     case Spell::DISRUPTINGRAY:
1303         ++disruptingray;
1304         break;
1305 
1306     default:
1307         break;
1308     }
1309 }
1310 
SpellApplyDamage(const Spell & spell,u32 spoint,const HeroBase * hero,TargetInfo & target)1311 void Battle::Unit::SpellApplyDamage( const Spell & spell, u32 spoint, const HeroBase * hero, TargetInfo & target )
1312 {
1313     // TODO: use fheroes2::getSpellDamage function to remove code duplication.
1314     u32 dmg = spell.Damage() * spoint;
1315 
1316     switch ( GetID() ) {
1317     case Monster::IRON_GOLEM:
1318     case Monster::STEEL_GOLEM:
1319         switch ( spell.GetID() ) {
1320             // 50% damage
1321         case Spell::COLDRAY:
1322         case Spell::COLDRING:
1323         case Spell::FIREBALL:
1324         case Spell::FIREBLAST:
1325         case Spell::LIGHTNINGBOLT:
1326         case Spell::CHAINLIGHTNING:
1327         case Spell::ELEMENTALSTORM:
1328         case Spell::ARMAGEDDON:
1329             dmg /= 2;
1330             break;
1331         default:
1332             break;
1333         }
1334         break;
1335 
1336     case Monster::WATER_ELEMENT:
1337         switch ( spell.GetID() ) {
1338             // 200% damage
1339         case Spell::FIREBALL:
1340         case Spell::FIREBLAST:
1341             dmg *= 2;
1342             break;
1343         default:
1344             break;
1345         }
1346         break;
1347 
1348     case Monster::AIR_ELEMENT:
1349         switch ( spell.GetID() ) {
1350             // 200% damage
1351         case Spell::ELEMENTALSTORM:
1352         case Spell::LIGHTNINGBOLT:
1353         case Spell::CHAINLIGHTNING:
1354             dmg *= 2;
1355             break;
1356         default:
1357             break;
1358         }
1359         break;
1360 
1361     case Monster::FIRE_ELEMENT:
1362         switch ( spell.GetID() ) {
1363             // 200% damage
1364         case Spell::COLDRAY:
1365         case Spell::COLDRING:
1366             dmg *= 2;
1367             break;
1368         default:
1369             break;
1370         }
1371         break;
1372 
1373     default:
1374         break;
1375     }
1376 
1377     // check artifact
1378     if ( hero ) {
1379         const HeroBase * defendingHero = GetCommander();
1380 
1381         switch ( spell.GetID() ) {
1382         case Spell::COLDRAY:
1383         case Spell::COLDRING:
1384             // +50%
1385             if ( hero->hasArtifact( Artifact::EVERCOLD_ICICLE ) )
1386                 dmg += dmg * Artifact( Artifact::EVERCOLD_ICICLE ).ExtraValue() / 100;
1387 
1388             if ( defendingHero ) {
1389                 // -50%
1390                 if ( defendingHero->hasArtifact( Artifact::ICE_CLOAK ) )
1391                     dmg -= dmg * Artifact( Artifact::ICE_CLOAK ).ExtraValue() / 100;
1392 
1393                 if ( defendingHero->hasArtifact( Artifact::HEART_ICE ) )
1394                     dmg -= dmg * Artifact( Artifact::HEART_ICE ).ExtraValue() / 100;
1395 
1396                 // 100%
1397                 if ( defendingHero->hasArtifact( Artifact::HEART_FIRE ) )
1398                     dmg *= 2;
1399             }
1400             break;
1401 
1402         case Spell::FIREBALL:
1403         case Spell::FIREBLAST:
1404             // +50%
1405             if ( hero->hasArtifact( Artifact::EVERHOT_LAVA_ROCK ) )
1406                 dmg += dmg * Artifact( Artifact::EVERHOT_LAVA_ROCK ).ExtraValue() / 100;
1407 
1408             if ( defendingHero ) {
1409                 // -50%
1410                 if ( defendingHero->hasArtifact( Artifact::FIRE_CLOAK ) )
1411                     dmg -= dmg * Artifact( Artifact::FIRE_CLOAK ).ExtraValue() / 100;
1412 
1413                 if ( defendingHero->hasArtifact( Artifact::HEART_FIRE ) )
1414                     dmg -= dmg * Artifact( Artifact::HEART_FIRE ).ExtraValue() / 100;
1415 
1416                 // 100%
1417                 if ( defendingHero->hasArtifact( Artifact::HEART_ICE ) )
1418                     dmg *= 2;
1419             }
1420             break;
1421 
1422         case Spell::LIGHTNINGBOLT:
1423             // +50%
1424             if ( hero->hasArtifact( Artifact::LIGHTNING_ROD ) )
1425                 dmg += dmg * Artifact( Artifact::LIGHTNING_ROD ).ExtraValue() / 100;
1426             // -50%
1427             if ( defendingHero && defendingHero->hasArtifact( Artifact::LIGHTNING_HELM ) )
1428                 dmg -= dmg * Artifact( Artifact::LIGHTNING_HELM ).ExtraValue() / 100;
1429             break;
1430 
1431         case Spell::CHAINLIGHTNING:
1432             // +50%
1433             if ( hero->hasArtifact( Artifact::LIGHTNING_ROD ) )
1434                 dmg += dmg * Artifact( Artifact::LIGHTNING_ROD ).ExtraValue() / 100;
1435             // -50%
1436             if ( defendingHero && defendingHero->hasArtifact( Artifact::LIGHTNING_HELM ) )
1437                 dmg -= dmg * Artifact( Artifact::LIGHTNING_HELM ).ExtraValue() / 100;
1438             // update orders damage
1439             switch ( target.damage ) {
1440             case 0:
1441                 break;
1442             case 1:
1443                 dmg /= 2;
1444                 break;
1445             case 2:
1446                 dmg /= 4;
1447                 break;
1448             case 3:
1449                 dmg /= 8;
1450                 break;
1451             default:
1452                 break;
1453             }
1454             break;
1455 
1456         case Spell::ELEMENTALSTORM:
1457         case Spell::ARMAGEDDON:
1458             // -50%
1459             if ( defendingHero && defendingHero->hasArtifact( Artifact::BROACH_SHIELDING ) )
1460                 dmg /= 2;
1461             break;
1462 
1463         default:
1464             break;
1465         }
1466     }
1467 
1468     // apply damage
1469     if ( dmg ) {
1470         target.damage = dmg;
1471         target.killed = ApplyDamage( dmg );
1472     }
1473 }
1474 
SpellRestoreAction(const Spell & spell,u32 spoint,const HeroBase * hero)1475 void Battle::Unit::SpellRestoreAction( const Spell & spell, u32 spoint, const HeroBase * hero )
1476 {
1477     switch ( spell.GetID() ) {
1478     case Spell::CURE:
1479     case Spell::MASSCURE:
1480         // clear bad magic
1481         if ( Modes( IS_BAD_MAGIC ) ) {
1482             ResetModes( IS_BAD_MAGIC );
1483             affected.RemoveMode( IS_BAD_MAGIC );
1484         }
1485         // restore
1486         hp += ( spell.Restore() * spoint );
1487         if ( hp > ArmyTroop::GetHitPoints() )
1488             hp = ArmyTroop::GetHitPoints();
1489         break;
1490 
1491     case Spell::RESURRECT:
1492     case Spell::ANIMATEDEAD:
1493     case Spell::RESURRECTTRUE: {
1494         // remove from graveyard
1495         if ( !isValid() ) {
1496             // TODO: buggy behaviour
1497             Arena::GetGraveyard()->RemoveTroop( *this );
1498         }
1499 
1500         const uint32_t restore = fheroes2::getResurrectPoints( spell, spoint, hero );
1501         const u32 resurrect = Resurrect( restore, false, ( spell == Spell::RESURRECT ) );
1502 
1503         // Puts back the unit in the board
1504         SetPosition( GetPosition() );
1505 
1506         if ( Arena::GetInterface() ) {
1507             std::string str( _( "%{count} %{name} rise(s) from the dead!" ) );
1508             StringReplace( str, "%{count}", resurrect );
1509             StringReplace( str, "%{name}", GetName() );
1510             Arena::GetInterface()->SetStatus( str, true );
1511         }
1512         break;
1513     }
1514 
1515     default:
1516         break;
1517     }
1518 }
1519 
isTwiceAttack(void) const1520 bool Battle::Unit::isTwiceAttack( void ) const
1521 {
1522     switch ( GetID() ) {
1523     case Monster::ELF:
1524     case Monster::GRAND_ELF:
1525     case Monster::RANGER:
1526         return !isHandFighting();
1527 
1528     default:
1529         break;
1530     }
1531 
1532     return ArmyTroop::isTwiceAttack();
1533 }
1534 
isMagicResist(const Spell & spell,u32 spower) const1535 bool Battle::Unit::isMagicResist( const Spell & spell, u32 spower ) const
1536 {
1537     return 100 <= GetMagicResist( spell, spower );
1538 }
1539 
GetMagicResist(const Spell & spell,u32 spower) const1540 u32 Battle::Unit::GetMagicResist( const Spell & spell, u32 spower ) const
1541 {
1542     if ( Modes( SP_ANTIMAGIC ) )
1543         return 100;
1544 
1545     switch ( spell.GetID() ) {
1546     case Spell::CURE:
1547     case Spell::MASSCURE:
1548         if ( !isHaveDamage() && !( modes & IS_MAGIC ) )
1549             return 100;
1550         break;
1551 
1552     case Spell::RESURRECT:
1553     case Spell::RESURRECTTRUE:
1554     case Spell::ANIMATEDEAD:
1555         if ( GetCount() == count0 )
1556             return 100;
1557         break;
1558 
1559     case Spell::DISPEL:
1560         if ( !( modes & IS_MAGIC ) )
1561             return 100;
1562         break;
1563 
1564     case Spell::HYPNOTIZE:
1565         if ( fheroes2::getHypnorizeMonsterHPPoints( spell, spower, nullptr ) < hp )
1566             return 100;
1567         break;
1568 
1569     default:
1570         break;
1571     }
1572 
1573     return fheroes2::getSpellResistance( id, spell.GetID() );
1574 }
1575 
GetSpellMagic() const1576 int Battle::Unit::GetSpellMagic() const
1577 {
1578     const std::vector<fheroes2::MonsterAbility> & abilities = fheroes2::getMonsterData( GetID() ).battleStats.abilities;
1579     const auto foundAbility = std::find( abilities.begin(), abilities.end(), fheroes2::MonsterAbility( fheroes2::MonsterAbilityType::SPELL_CASTER ) );
1580     if ( foundAbility == abilities.end() ) {
1581         // Not a spell caster.
1582         return Spell::NONE;
1583     }
1584 
1585     if ( _randomGenerator.Get( 1, 100 ) > foundAbility->percentage ) {
1586         // No luck to cast the spell.
1587         return Spell::NONE;
1588     }
1589 
1590     return foundAbility->value;
1591 }
1592 
isHaveDamage(void) const1593 bool Battle::Unit::isHaveDamage( void ) const
1594 {
1595     return hp < count0 * Monster::GetHitPoints();
1596 }
1597 
GetFrame(void) const1598 int Battle::Unit::GetFrame( void ) const
1599 {
1600     return animation.getFrame();
1601 }
1602 
SetCustomAlpha(uint32_t alpha)1603 void Battle::Unit::SetCustomAlpha( uint32_t alpha )
1604 {
1605     customAlphaMask = alpha;
1606 }
1607 
GetCustomAlpha() const1608 uint32_t Battle::Unit::GetCustomAlpha() const
1609 {
1610     return customAlphaMask;
1611 }
1612 
IncreaseAnimFrame(bool loop)1613 void Battle::Unit::IncreaseAnimFrame( bool loop )
1614 {
1615     animation.playAnimation( loop );
1616 }
1617 
isFinishAnimFrame(void) const1618 bool Battle::Unit::isFinishAnimFrame( void ) const
1619 {
1620     return animation.isLastFrame();
1621 }
1622 
SwitchAnimation(int rule,bool reverse)1623 bool Battle::Unit::SwitchAnimation( int rule, bool reverse )
1624 {
1625     animation.switchAnimation( rule, reverse );
1626     return animation.isValid();
1627 }
1628 
SwitchAnimation(const std::vector<int> & animationList,bool reverse)1629 bool Battle::Unit::SwitchAnimation( const std::vector<int> & animationList, bool reverse )
1630 {
1631     animation.switchAnimation( animationList, reverse );
1632     return animation.isValid();
1633 }
1634 
M82Attk(void) const1635 int Battle::Unit::M82Attk( void ) const
1636 {
1637     const fheroes2::MonsterSound & sounds = fheroes2::getMonsterData( id ).sounds;
1638 
1639     if ( isArchers() && !isHandFighting() ) {
1640         // Added a new shooter without sound? Grant him a voice!
1641         assert( sounds.rangeAttack != M82::UNKNOWN );
1642         return sounds.rangeAttack;
1643     }
1644 
1645     assert( sounds.meleeAttack != M82::UNKNOWN );
1646     return sounds.meleeAttack;
1647 }
1648 
M82Kill() const1649 int Battle::Unit::M82Kill() const
1650 {
1651     return fheroes2::getMonsterData( id ).sounds.death;
1652 }
1653 
M82Move() const1654 int Battle::Unit::M82Move() const
1655 {
1656     return fheroes2::getMonsterData( id ).sounds.movement;
1657 }
1658 
M82Wnce() const1659 int Battle::Unit::M82Wnce() const
1660 {
1661     return fheroes2::getMonsterData( id ).sounds.wince;
1662 }
1663 
M82Expl(void) const1664 int Battle::Unit::M82Expl( void ) const
1665 {
1666     switch ( GetID() ) {
1667     case Monster::VAMPIRE:
1668     case Monster::VAMPIRE_LORD:
1669         return M82::VAMPEXT1;
1670     case Monster::LICH:
1671     case Monster::POWER_LICH:
1672         return M82::LICHEXPL;
1673 
1674     default:
1675         break;
1676     }
1677 
1678     return M82::UNKNOWN;
1679 }
1680 
GetRectPosition() const1681 fheroes2::Rect Battle::Unit::GetRectPosition() const
1682 {
1683     return position.GetRect();
1684 }
1685 
GetBackPoint() const1686 fheroes2::Point Battle::Unit::GetBackPoint() const
1687 {
1688     const fheroes2::Rect & rt = position.GetRect();
1689     return reflect ? fheroes2::Point( rt.x + rt.width, rt.y + rt.height / 2 ) : fheroes2::Point( rt.x, rt.y + rt.height / 2 );
1690 }
1691 
GetCenterPoint() const1692 fheroes2::Point Battle::Unit::GetCenterPoint() const
1693 {
1694     const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( GetMonsterSprite(), GetFrame() );
1695 
1696     const fheroes2::Rect & pos = position.GetRect();
1697     const s32 centerY = pos.y + pos.height + sprite.y() / 2 - 10;
1698 
1699     return fheroes2::Point( pos.x + pos.width / 2, centerY );
1700 }
1701 
GetStartMissileOffset(size_t direction) const1702 fheroes2::Point Battle::Unit::GetStartMissileOffset( size_t direction ) const
1703 {
1704     return animation.getProjectileOffset( direction );
1705 }
1706 
GetArmyColor(void) const1707 int Battle::Unit::GetArmyColor( void ) const
1708 {
1709     return ArmyTroop::GetColor();
1710 }
1711 
GetColor(void) const1712 int Battle::Unit::GetColor( void ) const
1713 {
1714     return GetArmyColor();
1715 }
1716 
GetCurrentColor() const1717 int Battle::Unit::GetCurrentColor() const
1718 {
1719     if ( Modes( SP_BERSERKER ) )
1720         return -1; // be aware of unknown color
1721     else if ( Modes( SP_HYPNOTIZE ) )
1722         return GetArena()->GetOppositeColor( GetArmyColor() );
1723 
1724     // default
1725     return GetColor();
1726 }
1727 
GetCurrentOrArmyColor() const1728 int Battle::Unit::GetCurrentOrArmyColor() const
1729 {
1730     const int color = GetCurrentColor();
1731 
1732     if ( color < 0 ) { // unknown color in case of SP_BERSERKER mode
1733         return GetArmyColor();
1734     }
1735 
1736     return color;
1737 }
1738 
GetCurrentControl() const1739 int Battle::Unit::GetCurrentControl() const
1740 {
1741     if ( Modes( SP_BERSERKER ) )
1742         return CONTROL_AI; // let's say that it belongs to AI which is not present in the battle
1743 
1744     if ( Modes( SP_HYPNOTIZE ) ) {
1745         const int color = GetCurrentColor();
1746         if ( color == GetArena()->GetForce1().GetColor() )
1747             return GetArena()->GetForce1().GetControl();
1748         else
1749             return GetArena()->GetForce2().GetControl();
1750     }
1751 
1752     return GetControl();
1753 }
1754 
GetCommander(void) const1755 const HeroBase * Battle::Unit::GetCommander( void ) const
1756 {
1757     return GetArmy() ? GetArmy()->GetCommander() : nullptr;
1758 }
1759 
GetCurrentOrArmyCommander() const1760 const HeroBase * Battle::Unit::GetCurrentOrArmyCommander() const
1761 {
1762     return GetArena()->getCommander( GetCurrentOrArmyColor() );
1763 }
1764