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