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 <cstdlib>
25 
26 #include "agg.h"
27 #include "agg_image.h"
28 #include "audio.h"
29 #include "battle_arena.h"
30 #include "battle_army.h"
31 #include "battle_bridge.h"
32 #include "battle_catapult.h"
33 #include "battle_cell.h"
34 #include "battle_command.h"
35 #include "battle_interface.h"
36 #include "battle_tower.h"
37 #include "battle_troop.h"
38 #include "castle.h"
39 #include "game.h"
40 #include "game_delays.h"
41 #include "ground.h"
42 #include "icn.h"
43 #include "interface_list.h"
44 #include "logging.h"
45 #include "monster_anim.h"
46 #include "mus.h"
47 #include "pal.h"
48 #include "race.h"
49 #include "rand.h"
50 #include "settings.h"
51 #include "tools.h"
52 #include "translations.h"
53 #include "ui_window.h"
54 #include "world.h"
55 
56 #include <cassert>
57 
58 #define ARMYORDERW 40
59 
60 namespace
61 {
62     const int32_t cellYOffset = -9;
63 
64     struct LightningPoint
65     {
LightningPoint__anondf995ea00111::LightningPoint66         explicit LightningPoint( const fheroes2::Point & p = fheroes2::Point(), uint32_t thick = 1 )
67             : point( p )
68             , thickness( thick )
69         {}
70 
71         fheroes2::Point point;
72         uint32_t thickness;
73     };
74 
getDistance(const fheroes2::Point & p1,const fheroes2::Point & p2)75     double getDistance( const fheroes2::Point & p1, const fheroes2::Point & p2 )
76     {
77         const double diffX = p1.x - p2.x;
78         const double diffY = p1.y - p2.y;
79 
80         return std::sqrt( diffX * diffX + diffY * diffY );
81     }
82 
rotate(const fheroes2::Point & point,double angle)83     fheroes2::Point rotate( const fheroes2::Point & point, double angle )
84     {
85         const double sinValue = sin( angle );
86         const double cosValue = cos( angle );
87 
88         return fheroes2::Point( static_cast<int32_t>( point.x * cosValue - point.y * sinValue ), static_cast<int32_t>( point.x * sinValue + point.y * cosValue ) );
89     }
90 
getAngle(const fheroes2::Point & start,const fheroes2::Point & end)91     double getAngle( const fheroes2::Point & start, const fheroes2::Point & end )
92     {
93         return std::atan2( end.y - start.y, end.x - start.x );
94     }
95 
GenerateLightning(const fheroes2::Point & src,const fheroes2::Point & dst)96     std::vector<std::pair<LightningPoint, LightningPoint> > GenerateLightning( const fheroes2::Point & src, const fheroes2::Point & dst )
97     {
98         const int distance = static_cast<int>( getDistance( src, dst ) );
99         const double angle = getAngle( src, dst );
100 
101         int iterationCount = ( distance + 50 ) / 100;
102         if ( iterationCount < 3 )
103             iterationCount = 3;
104         if ( iterationCount > 5 )
105             iterationCount = 5;
106 
107         std::vector<std::pair<LightningPoint, LightningPoint> > lines;
108         lines.emplace_back( LightningPoint( fheroes2::Point( 0, 0 ), 5 ), LightningPoint( fheroes2::Point( distance, 0 ), 3 ) );
109 
110         int maxOffset = distance;
111 
112         for ( int step = 0; step < iterationCount; ++step ) {
113             std::vector<std::pair<LightningPoint, LightningPoint> > oldLines;
114             std::swap( lines, oldLines );
115 
116             for ( size_t i = 0; i < oldLines.size(); ++i ) {
117                 fheroes2::Point middle( oldLines[i].first.point + oldLines[i].second.point );
118                 middle.x /= 2;
119                 middle.y /= 2;
120 
121                 const bool isPositive = ( Rand::Get( 1, 2 ) == 1 );
122                 int offsetY = static_cast<int>( Rand::Get( 1, 10 ) ) * maxOffset / 100;
123                 if ( offsetY < 1 )
124                     offsetY = 1;
125 
126                 middle.y += isPositive ? offsetY : -offsetY;
127 
128                 const uint32_t middleThickness = ( oldLines[i].first.thickness + oldLines[i].second.thickness ) / 2;
129 
130                 const LightningPoint middlePoint( middle, middleThickness );
131 
132                 lines.emplace_back( oldLines[i].first, middlePoint );
133                 lines.emplace_back( middlePoint, oldLines[i].second );
134 
135                 if ( Rand::Get( 1, 4 ) == 1 ) { // 25%
136                     offsetY = static_cast<int>( Rand::Get( 1, 10 ) ) * maxOffset / 100;
137                     const int32_t x = static_cast<int32_t>( ( middle.x - oldLines[i].first.point.x ) * 0.7 ) + middle.x;
138                     const int32_t y = static_cast<int32_t>( ( middle.y - oldLines[i].first.point.y ) * 0.7 ) + middle.y + ( isPositive ? offsetY : -offsetY );
139                     lines.emplace_back( middlePoint, LightningPoint( fheroes2::Point( x, y ), 1 ) );
140                 }
141             }
142 
143             maxOffset /= 2;
144         }
145 
146         for ( size_t i = 0; i < lines.size(); ++i ) {
147             lines[i].first.point = rotate( lines[i].first.point, angle ) + src;
148             lines[i].second.point = rotate( lines[i].second.point, angle ) + src;
149         }
150 
151         return lines;
152     }
153 
RedrawLightning(const std::vector<std::pair<LightningPoint,LightningPoint>> & lightning,uint8_t color,fheroes2::Image & surface,const fheroes2::Rect & roi=fheroes2::Rect ())154     void RedrawLightning( const std::vector<std::pair<LightningPoint, LightningPoint> > & lightning, uint8_t color, fheroes2::Image & surface,
155                           const fheroes2::Rect & roi = fheroes2::Rect() )
156     {
157         for ( size_t i = 0; i < lightning.size(); ++i ) {
158             const fheroes2::Point & first = lightning[i].first.point;
159             const fheroes2::Point & second = lightning[i].second.point;
160             const bool isHorizontal = std::abs( first.x - second.x ) >= std::abs( first.y - second.y );
161             const int xOffset = isHorizontal ? 0 : 1;
162             const int yOffset = isHorizontal ? 1 : 0;
163 
164             fheroes2::DrawLine( surface, first, second, color, roi );
165 
166             for ( uint32_t thickness = 1; thickness < lightning[i].second.thickness; ++thickness ) {
167                 const bool isUpper = ( ( thickness % 2 ) == 1 );
168                 const int offset = isUpper ? ( thickness + 1 ) / 2 : -static_cast<int>( ( thickness + 1 ) / 2 );
169                 const int x = xOffset * offset;
170                 const int y = yOffset * offset;
171 
172                 fheroes2::DrawLine( surface, fheroes2::Point( first.x + x, first.y + y ), fheroes2::Point( second.x + x, second.y + y ), color, roi );
173             }
174 
175             for ( uint32_t thickness = lightning[i].second.thickness; thickness < lightning[i].first.thickness; ++thickness ) {
176                 const bool isUpper = ( ( thickness % 2 ) == 1 );
177                 const int offset = isUpper ? ( thickness + 1 ) / 2 : -static_cast<int>( ( thickness + 1 ) / 2 );
178 
179                 fheroes2::DrawLine( surface, fheroes2::Point( first.x + xOffset * offset, first.y + yOffset * offset ), second, color, roi );
180             }
181         }
182     }
183 }
184 
185 namespace Battle
186 {
187     int GetIndexIndicator( const Unit & );
188     int GetSwordCursorDirection( int );
189     int GetDirectionFromCursorSword( u32 );
190     int GetCursorFromSpell( int );
191 
192     struct CursorPosition
193     {
CursorPositionBattle::CursorPosition194         CursorPosition()
195             : index( -1 )
196         {}
197 
198         fheroes2::Point coord;
199         int32_t index;
200     };
201 
202     class StatusListBox : public ::Interface::ListBox<std::string>
203     {
204     public:
StatusListBox()205         StatusListBox()
206             : openlog( false )
207         {}
208 
SetPosition(u32 px,u32 py)209         void SetPosition( u32 px, u32 py )
210         {
211             const uint32_t mx = 6;
212             const uint32_t sw = fheroes2::Display::DEFAULT_WIDTH;
213             const uint32_t sh = mx * 20;
214             border.SetPosition( px, py - sh - 2, sw - 32, sh - 30 );
215             const fheroes2::Rect & area = border.GetArea();
216             const int32_t ax = area.x + area.width - 20;
217 
218             SetTopLeft( area.getPosition() );
219             SetAreaMaxItems( mx );
220 
221             SetScrollButtonUp( ICN::DROPLISL, 6, 7, fheroes2::Point( ax + 8, area.y - 10 ) );
222             SetScrollButtonDn( ICN::DROPLISL, 8, 9, fheroes2::Point( ax + 8, area.y + area.height - 11 ) );
223             SetScrollBar( fheroes2::AGG::GetICN( ICN::DROPLISL, 13 ), fheroes2::Rect( ax + 5 + 8, buttonPgUp.area().y + buttonPgUp.area().height + 3, 12,
224                                                                                       buttonPgDn.area().y - ( buttonPgUp.area().y + buttonPgUp.area().height ) - 7 ) );
225             _scrollbar.hide();
226             SetAreaItems( fheroes2::Rect( area.x, area.y, area.width - 10, area.height ) );
227             SetListContent( messages );
228             _scrollbar.show();
229         }
230 
GetArea() const231         const fheroes2::Rect & GetArea() const
232         {
233             return border.GetRect();
234         }
235 
AddMessage(const std::string & str)236         void AddMessage( const std::string & str )
237         {
238             messages.push_back( str );
239             SetListContent( messages );
240             SetCurrent( messages.size() - 1 );
241             if ( !openlog ) {
242                 _scrollbar.hide();
243             }
244         }
245 
RedrawItem(const std::string & str,int32_t px,int32_t py,bool)246         void RedrawItem( const std::string & str, int32_t px, int32_t py, bool ) override
247         {
248             const Text text( str, Font::BIG );
249             text.Blit( px, py );
250         }
251 
RedrawBackground(const fheroes2::Point & pt)252         void RedrawBackground( const fheroes2::Point & pt ) override
253         {
254             (void)pt;
255 
256             fheroes2::Display & display = fheroes2::Display::instance();
257             const fheroes2::Sprite & sp1 = fheroes2::AGG::GetICN( ICN::DROPLISL, 10 );
258             const fheroes2::Sprite & sp2 = fheroes2::AGG::GetICN( ICN::DROPLISL, 12 );
259             const fheroes2::Sprite & sp3 = fheroes2::AGG::GetICN( ICN::DROPLISL, 11 );
260             const int32_t ax = buttonPgUp.area().x;
261             const int32_t ah = buttonPgDn.area().y - ( buttonPgUp.area().y + buttonPgUp.area().height );
262 
263             const fheroes2::Rect & borderRect = border.GetRect();
264             Dialog::FrameBorder::RenderOther( fheroes2::AGG::GetICN( ICN::TEXTBAK2, 0 ), borderRect );
265 
266             for ( int32_t i = 0; i < ( ah / sp3.height() ); ++i )
267                 fheroes2::Blit( sp3, display, ax, buttonPgUp.area().y + buttonPgUp.area().height + ( sp3.height() * i ) );
268 
269             fheroes2::Blit( sp1, display, ax, buttonPgUp.area().y + buttonPgUp.area().height );
270             fheroes2::Blit( sp2, display, ax, buttonPgDn.area().y - sp2.height() );
271         }
272 
ActionCurrentUp()273         void ActionCurrentUp() override
274         {
275             // Do nothing.
276         }
277 
ActionCurrentDn()278         void ActionCurrentDn() override
279         {
280             // Do nothing.
281         }
282 
ActionListDoubleClick(std::string &)283         void ActionListDoubleClick( std::string & ) override
284         {
285             // Do nothing.
286         }
287 
ActionListSingleClick(std::string &)288         void ActionListSingleClick( std::string & ) override
289         {
290             // Do nothing.
291         }
292 
ActionListPressRight(std::string &)293         void ActionListPressRight( std::string & ) override
294         {
295             // Do nothing.
296         }
297 
SetOpenLog(const bool f)298         void SetOpenLog( const bool f )
299         {
300             openlog = f;
301         }
302 
isOpenLog() const303         bool isOpenLog() const
304         {
305             return openlog;
306         }
307 
308     private:
309         Dialog::FrameBorder border;
310         std::vector<std::string> messages;
311         bool openlog;
312     };
313 
matchHeroType(const HeroBase * base)314     int matchHeroType( const HeroBase * base )
315     {
316         if ( base->isCaptain() )
317             return CAPTAIN;
318 
319         switch ( base->GetRace() ) {
320         case Race::BARB:
321             return BARBARIAN;
322         case Race::SORC:
323             return SORCERESS;
324         case Race::WRLK:
325             return WARLOCK;
326         case Race::WZRD:
327             return WIZARD;
328         case Race::NECR:
329             return NECROMANCER;
330         default:
331             break;
332         }
333         return KNIGHT;
334     }
335 
getHeroAnimation(const HeroBase * hero,int animation)336     const std::vector<int> & getHeroAnimation( const HeroBase * hero, int animation )
337     {
338         static std::vector<int> staticAnim;
339         if ( staticAnim.empty() ) {
340             staticAnim.push_back( 1 );
341         }
342 
343         if ( !hero || animation == OP_STATIC )
344             return staticAnim;
345 
346         const int heroType = matchHeroType( hero );
347 
348         if ( animation == OP_SORROW ) {
349             static const std::vector<int> sorrowAnim = { 2, 3, 4, 5, 4, 5, 4, 3, 2 };
350 
351             return ( heroType == CAPTAIN ) ? staticAnim : sorrowAnim;
352         }
353 
354         static std::vector<int> heroTypeAnim[7][9];
355 
356         if ( heroTypeAnim[heroType][animation].empty() ) {
357             const int sourceArray[7][9][9] = {
358                 //   JOY                CAST_MASS             CAST_UP               CAST_DOWN     IDLE
359                 {{6, 7, 8, 9, 8, 9, 8, 7, 6}, {10, 11}, {10}, {6, 12, 13}, {12, 6}, {2, 14}, {2}, {15, 16, 17}, {18, 19}}, // KNIGHT
360                 {{6, 7, 8, 9, 9, 8, 7, 6}, {6, 10, 11}, {10, 6}, {6, 12, 13}, {12, 6}, {6, 14}, {6}, {15, 16, 17}, {18}}, // BARBARIAN
361                 {{6, 7, 8, 7, 6}, {6, 7, 9}, {7, 6}, {6, 10, 11}, {10, 6}, {6, 12}, {6}, {13, 14, 15}, {16}}, // SORCERESS
362                 {{6, 7, 8, 9, 10, 9, 8, 7, 6}, {6, 7, 11, 12}, {11, 6}, {6, 7, 13}, {6}, {6, 14}, {6}, {15, 16}, {6}}, // WARLOCK
363                 {{6, 7, 8, 9, 8, 7, 6}, {6, 10, 11, 12, 13}, {12, 11, 10, 6}, {6, 14}, {6}, {6, 15}, {6}, {16, 17}, {18}}, // WIZARD
364                 {{6, 7, 6, 7, 6, 7}, {7, 8, 9, 10, 11}, {10, 9, 7}, {7, 12, 13, 14, 15}, {7}, {7, 12, 13, 14, 16}, {7}, {17}, {18, 19}}, // NECROMANCER
365                 {{1}, {2, 3, 4}, {3, 2}, {5, 6}, {5}, {5, 7}, {5}, {8, 9}, {10}} // CAPTAIN
366             };
367 
368             for ( int frame = 0; frame < 9; ++frame ) {
369                 if ( sourceArray[heroType][animation][frame] != 0 ) {
370                     heroTypeAnim[heroType][animation].push_back( sourceArray[heroType][animation][frame] );
371                 }
372             }
373         }
374 
375         return heroTypeAnim[heroType][animation];
376     }
377 }
378 
CursorAttack(u32 theme)379 bool CursorAttack( u32 theme )
380 {
381     switch ( theme ) {
382     case Cursor::WAR_ARROW:
383     case Cursor::WAR_BROKENARROW:
384     case Cursor::SWORD_TOPRIGHT:
385     case Cursor::SWORD_RIGHT:
386     case Cursor::SWORD_BOTTOMRIGHT:
387     case Cursor::SWORD_BOTTOMLEFT:
388     case Cursor::SWORD_LEFT:
389     case Cursor::SWORD_TOPLEFT:
390     case Cursor::SWORD_TOP:
391     case Cursor::SWORD_BOTTOM:
392         return true;
393     default:
394         break;
395     }
396 
397     return false;
398 }
399 
DrawHexagon(const uint8_t colorId)400 fheroes2::Image DrawHexagon( const uint8_t colorId )
401 {
402     const int r = 22;
403     const int l = 10;
404     const int w = CELLW;
405     const int h = CELLH;
406 
407     fheroes2::Image sf( w + 1, h + 1 );
408     sf.reset();
409 
410     fheroes2::DrawLine( sf, fheroes2::Point( r, 0 ), fheroes2::Point( 0, l ), colorId );
411     fheroes2::DrawLine( sf, fheroes2::Point( r, 0 ), fheroes2::Point( w, l ), colorId );
412 
413     fheroes2::DrawLine( sf, fheroes2::Point( 0, l + 1 ), fheroes2::Point( 0, h - l ), colorId );
414     fheroes2::DrawLine( sf, fheroes2::Point( w, l + 1 ), fheroes2::Point( w, h - l ), colorId );
415 
416     fheroes2::DrawLine( sf, fheroes2::Point( r, h ), fheroes2::Point( 0, h - l ), colorId );
417     fheroes2::DrawLine( sf, fheroes2::Point( r, h ), fheroes2::Point( w, h - l ), colorId );
418 
419     return sf;
420 }
421 
DrawHexagonShadow(const uint8_t alphaValue)422 fheroes2::Image DrawHexagonShadow( const uint8_t alphaValue )
423 {
424     const int l = 13;
425     const int w = CELLW;
426     const int h = CELLH;
427 
428     fheroes2::Image sf( w, h );
429     sf.reset();
430     fheroes2::Rect rt( 0, l - 1, w + 1, 2 * l + 3 );
431     for ( int i = 0; i < w / 2; i += 2 ) {
432         --rt.y;
433         rt.height += 2;
434         rt.x += 2;
435         rt.width -= 4;
436         for ( int x = 0; x < rt.width; ++x ) {
437             for ( int y = 0; y < rt.height; ++y ) {
438                 fheroes2::SetTransformPixel( sf, rt.x + x, rt.y + y, alphaValue );
439             }
440         }
441     }
442 
443     return sf;
444 }
445 
isFinishAnimFrame(const TargetInfo & info)446 bool Battle::TargetInfo::isFinishAnimFrame( const TargetInfo & info )
447 {
448     return info.defender && info.defender->isFinishAnimFrame();
449 }
450 
GetCursorFromSpell(int spell)451 int Battle::GetCursorFromSpell( int spell )
452 {
453     switch ( spell ) {
454     case Spell::SLOW:
455         return Cursor::SP_SLOW;
456     case Spell::CURSE:
457         return Cursor::SP_CURSE;
458     case Spell::CURE:
459         return Cursor::SP_CURE;
460     case Spell::BLESS:
461         return Cursor::SP_BLESS;
462     case Spell::FIREBALL:
463         return Cursor::SP_FIREBALL;
464     case Spell::FIREBLAST:
465         return Cursor::SP_FIREBLAST;
466     case Spell::TELEPORT:
467         return Cursor::SP_TELEPORT;
468     case Spell::RESURRECT:
469         return Cursor::SP_RESURRECT;
470     case Spell::HASTE:
471         return Cursor::SP_HASTE;
472     case Spell::SHIELD:
473         return Cursor::SP_SHIELD;
474     case Spell::ARMAGEDDON:
475         return Cursor::SP_ARMAGEDDON;
476     case Spell::ANTIMAGIC:
477         return Cursor::SP_ANTIMAGIC;
478     case Spell::BERSERKER:
479         return Cursor::SP_BERSERKER;
480     case Spell::PARALYZE:
481         return Cursor::SP_PARALYZE;
482     case Spell::BLIND:
483         return Cursor::SP_BLIND;
484 
485     case Spell::LIGHTNINGBOLT:
486         return Cursor::SP_LIGHTNINGBOLT;
487     case Spell::CHAINLIGHTNING:
488         return Cursor::SP_CHAINLIGHTNING;
489     case Spell::ELEMENTALSTORM:
490         return Cursor::SP_ELEMENTALSTORM;
491     case Spell::RESURRECTTRUE:
492         return Cursor::SP_RESURRECTTRUE;
493     case Spell::DISPEL:
494         return Cursor::SP_DISPEL;
495     case Spell::HOLYWORD:
496         return Cursor::SP_HOLYWORD;
497     case Spell::HOLYSHOUT:
498         return Cursor::SP_HOLYSHOUT;
499     case Spell::METEORSHOWER:
500         return Cursor::SP_METEORSHOWER;
501 
502     case Spell::ANIMATEDEAD:
503         return Cursor::SP_ANIMATEDEAD;
504     case Spell::MIRRORIMAGE:
505         return Cursor::SP_MIRRORIMAGE;
506     case Spell::BLOODLUST:
507         return Cursor::SP_BLOODLUST;
508     case Spell::DEATHRIPPLE:
509         return Cursor::SP_DEATHRIPPLE;
510     case Spell::DEATHWAVE:
511         return Cursor::SP_DEATHWAVE;
512     case Spell::STEELSKIN:
513         return Cursor::SP_STEELSKIN;
514     case Spell::STONESKIN:
515         return Cursor::SP_STONESKIN;
516     case Spell::DRAGONSLAYER:
517         return Cursor::SP_DRAGONSLAYER;
518     case Spell::EARTHQUAKE:
519         return Cursor::SP_EARTHQUAKE;
520     case Spell::DISRUPTINGRAY:
521         return Cursor::SP_DISRUPTINGRAY;
522     case Spell::COLDRING:
523         return Cursor::SP_COLDRING;
524     case Spell::COLDRAY:
525         return Cursor::SP_COLDRAY;
526     case Spell::HYPNOTIZE:
527         return Cursor::SP_HYPNOTIZE;
528     case Spell::ARROW:
529         return Cursor::SP_ARROW;
530 
531     default:
532         break;
533     }
534     return Cursor::WAR_NONE;
535 }
536 
GetSwordCursorDirection(int dir)537 int Battle::GetSwordCursorDirection( int dir )
538 {
539     switch ( dir ) {
540     case BOTTOM_RIGHT:
541         return Cursor::SWORD_TOPLEFT;
542     case BOTTOM_LEFT:
543         return Cursor::SWORD_TOPRIGHT;
544     case RIGHT:
545         return Cursor::SWORD_LEFT;
546     case TOP_RIGHT:
547         return Cursor::SWORD_BOTTOMLEFT;
548     case TOP_LEFT:
549         return Cursor::SWORD_BOTTOMRIGHT;
550     case LEFT:
551         return Cursor::SWORD_RIGHT;
552     default:
553         break;
554     }
555     return 0;
556 }
557 
GetDirectionFromCursorSword(u32 sword)558 int Battle::GetDirectionFromCursorSword( u32 sword )
559 {
560     switch ( sword ) {
561     case Cursor::SWORD_TOPLEFT:
562         return BOTTOM_RIGHT;
563     case Cursor::SWORD_TOPRIGHT:
564         return BOTTOM_LEFT;
565     case Cursor::SWORD_LEFT:
566         return RIGHT;
567     case Cursor::SWORD_BOTTOMLEFT:
568         return TOP_RIGHT;
569     case Cursor::SWORD_BOTTOMRIGHT:
570         return TOP_LEFT;
571     case Cursor::SWORD_RIGHT:
572         return LEFT;
573     default:
574         break;
575     }
576 
577     return UNKNOWN;
578 }
579 
OpponentSprite(const fheroes2::Rect & area,const HeroBase * b,bool r)580 Battle::OpponentSprite::OpponentSprite( const fheroes2::Rect & area, const HeroBase * b, bool r )
581     : base( b )
582     , _currentAnim( getHeroAnimation( b, OP_STATIC ) )
583     , _animationType( OP_STATIC )
584     , _idleTimer( 8000 )
585     , _heroIcnId( ICN::UNKNOWN )
586     , reflect( r )
587     , _offset( area.x, area.y )
588 {
589     const bool isCaptain = b->isCaptain();
590     switch ( b->GetRace() ) {
591     case Race::KNGT:
592         _heroIcnId = isCaptain ? ICN::CMBTCAPK : ICN::CMBTHROK;
593         break;
594     case Race::BARB:
595         _heroIcnId = isCaptain ? ICN::CMBTCAPB : ICN::CMBTHROB;
596         break;
597     case Race::SORC:
598         _heroIcnId = isCaptain ? ICN::CMBTCAPS : ICN::CMBTHROS;
599         break;
600     case Race::WRLK:
601         _heroIcnId = isCaptain ? ICN::CMBTCAPW : ICN::CMBTHROW;
602         break;
603     case Race::WZRD:
604         _heroIcnId = isCaptain ? ICN::CMBTCAPZ : ICN::CMBTHROZ;
605         break;
606     case Race::NECR:
607         _heroIcnId = isCaptain ? ICN::CMBTCAPN : ICN::CMBTHRON;
608         break;
609     default:
610         // Did you add a new faction? Add the logic here.
611         assert( 0 );
612         break;
613     }
614 
615     const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( _heroIcnId, _currentAnim.getFrame() );
616 
617     if ( reflect ) {
618         pos.x = _offset.x + fheroes2::Display::DEFAULT_WIDTH - HERO_X_OFFSET - ( sprite.x() + sprite.width() );
619         pos.y = _offset.y + RIGHT_HERO_Y_OFFSET + sprite.y();
620     }
621     else {
622         pos.x = _offset.x + HERO_X_OFFSET + sprite.x();
623         pos.y = _offset.y + LEFT_HERO_Y_OFFSET + sprite.y();
624     }
625 
626     if ( isCaptain ) {
627         if ( reflect )
628             pos.x += CAPTAIN_X_OFFSET;
629         else
630             pos.x -= CAPTAIN_X_OFFSET;
631         pos.y += CAPTAIN_Y_OFFSET;
632     }
633 
634     pos.width = sprite.width();
635     pos.height = sprite.height();
636 }
637 
GetHero(void) const638 const HeroBase * Battle::OpponentSprite::GetHero( void ) const
639 {
640     return base;
641 }
642 
Offset() const643 fheroes2::Point Battle::OpponentSprite::Offset() const
644 {
645     return _offset;
646 }
647 
IncreaseAnimFrame(bool loop)648 void Battle::OpponentSprite::IncreaseAnimFrame( bool loop )
649 {
650     _currentAnim.playAnimation( loop );
651 }
652 
SetAnimation(int rule)653 void Battle::OpponentSprite::SetAnimation( int rule )
654 {
655     _animationType = rule;
656     _currentAnim = getHeroAnimation( base, rule );
657 }
658 
isFinishFrame(void) const659 bool Battle::OpponentSprite::isFinishFrame( void ) const
660 {
661     return _currentAnim.isLastFrame();
662 }
663 
GetArea(void) const664 const fheroes2::Rect & Battle::OpponentSprite::GetArea( void ) const
665 {
666     return pos;
667 }
668 
GetCastPosition(void) const669 fheroes2::Point Battle::OpponentSprite::GetCastPosition( void ) const
670 {
671     const bool isCaptain = base->isCaptain();
672     fheroes2::Point offset;
673     switch ( base->GetRace() ) {
674     case Race::KNGT:
675         offset.x = isCaptain ? 0 : 13;
676         offset.y = isCaptain ? 3 : -7;
677         break;
678     case Race::BARB:
679         offset.x = isCaptain ? 0 : 16;
680         offset.y = isCaptain ? 3 : -15;
681         break;
682     case Race::SORC:
683         offset.x = isCaptain ? 0 : 11;
684         offset.y = isCaptain ? 3 : -8;
685         break;
686     case Race::WRLK:
687         offset.x = isCaptain ? 2 : 9;
688         offset.y = isCaptain ? 5 : -11;
689         break;
690     case Race::WZRD:
691         offset.x = isCaptain ? 5 : 1;
692         offset.y = isCaptain ? 8 : -9;
693         break;
694     case Race::NECR:
695         offset.x = isCaptain ? 5 : 13;
696         offset.y = isCaptain ? 6 : -7;
697         break;
698     default:
699         break;
700     }
701 
702     return fheroes2::Point( pos.x + ( reflect ? offset.x : pos.width - offset.x ), pos.y + pos.height / 2 + offset.y );
703 }
704 
Redraw(fheroes2::Image & dst) const705 void Battle::OpponentSprite::Redraw( fheroes2::Image & dst ) const
706 {
707     const fheroes2::Sprite & hero = fheroes2::AGG::GetICN( _heroIcnId, _currentAnim.getFrame() );
708 
709     fheroes2::Point offset( _offset );
710     if ( base->isCaptain() ) {
711         if ( reflect )
712             offset.x += CAPTAIN_X_OFFSET;
713         else
714             offset.x -= CAPTAIN_X_OFFSET;
715         offset.y += CAPTAIN_Y_OFFSET;
716     }
717 
718     if ( reflect )
719         fheroes2::Blit( hero, dst, offset.x + fheroes2::Display::DEFAULT_WIDTH - HERO_X_OFFSET - ( hero.x() + hero.width() ), offset.y + RIGHT_HERO_Y_OFFSET + hero.y(),
720                         reflect );
721     else
722         fheroes2::Blit( hero, dst, offset.x + HERO_X_OFFSET + hero.x(), offset.y + LEFT_HERO_Y_OFFSET + hero.y() );
723 }
724 
Update()725 void Battle::OpponentSprite::Update()
726 {
727     if ( _currentAnim.isLastFrame() ) {
728         if ( _animationType != OP_STATIC ) {
729             if ( _animationType != OP_CAST_MASS && _animationType != OP_CAST_UP && _animationType != OP_CAST_DOWN )
730                 SetAnimation( OP_STATIC );
731         }
732         else if ( _idleTimer.checkDelay() ) {
733             SetAnimation( ( Rand::Get( 1, 3 ) < 2 ) ? OP_IDLE2 : OP_IDLE );
734         }
735     }
736     else {
737         _currentAnim.playAnimation();
738     }
739 }
740 
Status()741 Battle::Status::Status()
742     : back1( fheroes2::AGG::GetICN( ICN::TEXTBAR, 8 ) )
743     , back2( fheroes2::AGG::GetICN( ICN::TEXTBAR, 9 ) )
744     , listlog( nullptr )
745 {
746     width = back1.width();
747     height = back1.height() + back2.height();
748 
749     bar1.Set( Font::BIG );
750     bar2.Set( Font::BIG );
751 }
752 
SetPosition(s32 cx,s32 cy)753 void Battle::Status::SetPosition( s32 cx, s32 cy )
754 {
755     fheroes2::Rect::x = cx;
756     fheroes2::Rect::y = cy;
757 }
758 
SetMessage(const std::string & str,bool top)759 void Battle::Status::SetMessage( const std::string & str, bool top )
760 {
761     if ( top ) {
762         bar1.Set( str );
763         if ( listlog )
764             listlog->AddMessage( str );
765     }
766     else if ( str != message ) {
767         bar2.Set( str );
768         message = str;
769     }
770 }
771 
Redraw(void) const772 void Battle::Status::Redraw( void ) const
773 {
774     fheroes2::Display & display = fheroes2::Display::instance();
775     fheroes2::Blit( back1, display, x, y );
776     fheroes2::Blit( back2, display, x, y + back1.height() );
777 
778     if ( bar1.Size() )
779         bar1.Blit( x + ( back1.width() - bar1.w() ) / 2, y + 2 );
780     if ( bar2.Size() )
781         bar2.Blit( x + ( back2.width() - bar2.w() ) / 2, y + back1.height() - 2 );
782 }
783 
GetMessage(void) const784 const std::string & Battle::Status::GetMessage( void ) const
785 {
786     return message;
787 }
788 
clear()789 void Battle::Status::clear()
790 {
791     bar1.Clear();
792     bar2.Clear();
793 }
794 
ArmiesOrder()795 Battle::ArmiesOrder::ArmiesOrder()
796     : orders( nullptr )
797     , army_color2( 0 )
798 {
799     const fheroes2::Size sz( ARMYORDERW, ARMYORDERW );
800 
801     sf_color[0].resize( sz.width, sz.height );
802     sf_color[0].reset();
803     fheroes2::DrawBorder( sf_color[0], fheroes2::GetColorId( 0xe0, 0xe0, 0 ) );
804 
805     sf_color[1].resize( sz.width, sz.height );
806     sf_color[1].reset();
807     fheroes2::DrawBorder( sf_color[1], fheroes2::GetColorId( 0xe0, 0x48, 0 ) );
808 
809     sf_color[2].resize( sz.width, sz.height );
810     sf_color[2].reset();
811     fheroes2::DrawBorder( sf_color[2], fheroes2::GetColorId( 0x90, 0xc0, 0 ) );
812 }
813 
Set(const fheroes2::Rect & rt,const Units * units,int color)814 void Battle::ArmiesOrder::Set( const fheroes2::Rect & rt, const Units * units, int color )
815 {
816     area = rt;
817     orders = units;
818     army_color2 = color;
819     if ( units )
820         rects.reserve( units->size() );
821 }
822 
QueueEventProcessing(std::string & msg,const fheroes2::Point & offset)823 void Battle::ArmiesOrder::QueueEventProcessing( std::string & msg, const fheroes2::Point & offset )
824 {
825     LocalEvent & le = LocalEvent::Get();
826 
827     for ( std::vector<UnitPos>::const_iterator it = rects.begin(); it != rects.end(); ++it )
828         if ( ( *it ).first ) {
829             const fheroes2::Rect unitRoi = ( *it ).second + offset;
830             if ( le.MouseCursor( unitRoi ) ) {
831                 msg = _( "View %{monster} info" );
832                 StringReplace( msg, "%{monster}", ( *it ).first->GetName() );
833             }
834 
835             const Unit & unit = *( *it ).first;
836 
837             if ( le.MouseClickLeft( unitRoi ) )
838                 Dialog::ArmyInfo( unit, Dialog::READONLY | Dialog::BUTTONS, unit.isReflect() );
839             else if ( le.MousePressRight( unitRoi ) )
840                 Dialog::ArmyInfo( unit, Dialog::READONLY, unit.isReflect() );
841         }
842 }
843 
RedrawUnit(const fheroes2::Rect & pos,const Battle::Unit & unit,bool revert,bool current,fheroes2::Image & output) const844 void Battle::ArmiesOrder::RedrawUnit( const fheroes2::Rect & pos, const Battle::Unit & unit, bool revert, bool current, fheroes2::Image & output ) const
845 {
846     const fheroes2::Sprite & mons32 = fheroes2::AGG::GetICN( ICN::MONS32, unit.GetSpriteIndex() );
847 
848     // background
849     fheroes2::Fill( output, pos.x, pos.y, pos.width, pos.height, fheroes2::GetColorId( 0x33, 0x33, 0x33 ) );
850     // mons32 sprite
851     fheroes2::Blit( mons32, output, pos.x + ( pos.width - mons32.width() ) / 2, pos.y + pos.height - mons32.height() - ( mons32.height() + 3 < pos.height ? 3 : 0 ),
852                     revert );
853 
854     // window
855     if ( current )
856         fheroes2::Blit( sf_color[0], output, pos.x + 1, pos.y + 1 );
857     else if ( unit.Modes( Battle::TR_MOVED ) )
858         fheroes2::Blit( sf_color[1], output, pos.x + 1, pos.y + 1 );
859     else
860         fheroes2::Blit( sf_color[2], output, pos.x + 1, pos.y + 1 );
861 
862     // number
863     Text number( std::to_string( unit.GetCount() ), Font::SMALL );
864     number.Blit( pos.x + 2, pos.y + 2, output );
865 }
866 
Redraw(const Unit * current,fheroes2::Image & output)867 void Battle::ArmiesOrder::Redraw( const Unit * current, fheroes2::Image & output )
868 {
869     if ( orders ) {
870         const int32_t ow = ARMYORDERW + 2;
871 
872         const int32_t validUnitCount = static_cast<int32_t>( std::count_if( orders->begin(), orders->end(), []( const Unit * unit ) { return unit->isValid(); } ) );
873 
874         int32_t ox = area.x + ( area.width - ow * validUnitCount ) / 2;
875         int32_t oy = area.y;
876 
877         fheroes2::Rect::x = ox;
878         fheroes2::Rect::y = oy;
879         fheroes2::Rect::height = ow;
880 
881         rects.clear();
882 
883         for ( Units::const_iterator it = orders->begin(); it != orders->end(); ++it )
884             if ( *it && ( *it )->isValid() ) {
885                 rects.emplace_back( *it, fheroes2::Rect( ox, oy, ow, ow ) );
886                 RedrawUnit( rects.back().second, **it, ( **it ).GetColor() == army_color2, current == *it, output );
887                 ox += ow;
888                 fheroes2::Rect::width += ow;
889             }
890     }
891 }
892 
Interface(Arena & a,s32 center)893 Battle::Interface::Interface( Arena & a, s32 center )
894     : arena( a )
895     , _surfaceInnerArea( 0, 0, fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT )
896     , _mainSurface( fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT )
897     , icn_cbkg( ICN::UNKNOWN )
898     , icn_frng( ICN::UNKNOWN )
899     , humanturn_spell( Spell::NONE )
900     , humanturn_exit( true )
901     , humanturn_redraw( true )
902     , animation_flags_frame( 0 )
903     , catapult_frame( 0 )
904     , _contourColor( 110 )
905     , _brightLandType( false )
906     , _currentUnit( nullptr )
907     , _movingUnit( nullptr )
908     , _flyingUnit( nullptr )
909     , b_current_sprite( nullptr )
910     , index_pos( -1 )
911     , teleport_src( -1 )
912     , listlog( nullptr )
913     , _cursorRestorer( true, Cursor::WAR_POINTER )
914     , _bridgeAnimation( { false, BridgeMovementAnimation::UP_POSITION } )
915 {
916     const Settings & conf = Settings::Get();
917 
918     // border
919     const fheroes2::Display & display = fheroes2::Display::instance();
920 
921     _interfacePosition = fheroes2::Rect( ( display.width() - fheroes2::Display::DEFAULT_WIDTH ) / 2, ( display.height() - fheroes2::Display::DEFAULT_HEIGHT ) / 2,
922                                          _surfaceInnerArea.width, _surfaceInnerArea.height );
923     border.SetPosition( _interfacePosition.x - BORDERWIDTH, _interfacePosition.y - BORDERWIDTH, fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT );
924 
925     // cover
926     const bool trees = !Maps::ScanAroundObject( center, MP2::OBJ_TREES ).empty();
927     const Maps::Tiles & tile = world.GetTiles( center );
928 
929     const int groundType = tile.GetGround();
930     _brightLandType
931         = ( groundType == Maps::Ground::SNOW || groundType == Maps::Ground::DESERT || groundType == Maps::Ground::WASTELAND || groundType == Maps::Ground::BEACH );
932     if ( _brightLandType ) {
933         _contourColor = 108;
934     }
935 
936     switch ( groundType ) {
937     case Maps::Ground::DESERT:
938         icn_cbkg = ICN::CBKGDSRT;
939         icn_frng = ICN::FRNG0004;
940         break;
941     case Maps::Ground::SNOW:
942         icn_cbkg = trees ? ICN::CBKGSNTR : ICN::CBKGSNMT;
943         icn_frng = trees ? ICN::FRNG0006 : ICN::FRNG0007;
944         break;
945     case Maps::Ground::SWAMP:
946         icn_cbkg = ICN::CBKGSWMP;
947         icn_frng = ICN::FRNG0008;
948         break;
949     case Maps::Ground::WASTELAND:
950         icn_cbkg = ICN::CBKGCRCK;
951         icn_frng = ICN::FRNG0003;
952         break;
953     case Maps::Ground::BEACH:
954         icn_cbkg = ICN::CBKGBEAC;
955         icn_frng = ICN::FRNG0002;
956         break;
957     case Maps::Ground::LAVA:
958         icn_cbkg = ICN::CBKGLAVA;
959         icn_frng = ICN::FRNG0005;
960         break;
961     case Maps::Ground::DIRT:
962         icn_cbkg = trees ? ICN::CBKGDITR : ICN::CBKGDIMT;
963         icn_frng = trees ? ICN::FRNG0010 : ICN::FRNG0009;
964         break;
965     case Maps::Ground::GRASS:
966         icn_cbkg = trees ? ICN::CBKGGRTR : ICN::CBKGGRMT;
967         icn_frng = trees ? ICN::FRNG0011 : ICN::FRNG0012;
968         break;
969     case Maps::Ground::WATER:
970         icn_cbkg = ICN::CBKGWATR;
971         icn_frng = ICN::FRNG0013;
972         break;
973     default:
974         break;
975     }
976 
977     // hexagon
978     sf_hexagon = DrawHexagon( fheroes2::GetColorId( 0x68, 0x8C, 0x04 ) );
979     sf_cursor = DrawHexagonShadow( 2 );
980     sf_shadow = DrawHexagonShadow( 4 );
981 
982     btn_auto.setICNInfo( ICN::TEXTBAR, 4, 5 );
983     btn_settings.setICNInfo( ICN::TEXTBAR, 6, 7 );
984 
985     // opponents
986     opponent1 = arena.GetCommander1() ? new OpponentSprite( _surfaceInnerArea, arena.GetCommander1(), false ) : nullptr;
987     opponent2 = arena.GetCommander2() ? new OpponentSprite( _surfaceInnerArea, arena.GetCommander2(), true ) : nullptr;
988 
989     if ( Arena::GetCastle() )
990         main_tower = fheroes2::Rect( 570, 145, 70, 70 );
991 
992     const fheroes2::Rect & area = border.GetArea();
993 
994     const fheroes2::Rect autoRect = btn_auto.area();
995     const fheroes2::Rect settingsRect = btn_settings.area();
996     btn_auto.setPosition( area.x, area.y + area.height - settingsRect.height - autoRect.height );
997     btn_settings.setPosition( area.x, area.y + area.height - settingsRect.height );
998 
999     if ( conf.ExtBattleSoftWait() ) {
1000         btn_wait.setICNInfo( ICN::BATTLEWAIT, 0, 1 );
1001         btn_skip.setICNInfo( ICN::BATTLESKIP, 0, 1 );
1002 
1003         const fheroes2::Rect waitRect = btn_wait.area();
1004         const fheroes2::Rect skipRect = btn_skip.area();
1005         btn_wait.setPosition( area.x + area.width - waitRect.width, area.y + area.height - skipRect.height - waitRect.height );
1006         btn_skip.setPosition( area.x + area.width - skipRect.width, area.y + area.height - skipRect.height );
1007     }
1008     else {
1009         btn_skip.setICNInfo( ICN::TEXTBAR, 0, 1 );
1010         btn_skip.setPosition( area.x + area.width - btn_skip.area().width, area.y + area.height - btn_skip.area().height );
1011     }
1012 
1013     status.SetPosition( area.x + settingsRect.width, btn_auto.area().y );
1014 
1015     listlog = new StatusListBox();
1016 
1017     if ( listlog )
1018         listlog->SetPosition( area.x, area.y + area.height - status.height );
1019     status.SetLogs( listlog );
1020 
1021     AGG::ResetMixer();
1022     AGG::PlaySound( M82::PREBATTL );
1023 }
1024 
~Interface()1025 Battle::Interface::~Interface()
1026 {
1027     AGG::ResetMixer();
1028 
1029     if ( listlog )
1030         delete listlog;
1031     if ( opponent1 )
1032         delete opponent1;
1033     if ( opponent2 )
1034         delete opponent2;
1035 }
1036 
SetArmiesOrder(const Units * units)1037 void Battle::Interface::SetArmiesOrder( const Units * units )
1038 {
1039     armies_order.Set( GetArea(), units, arena.GetArmyColor2() );
1040 }
1041 
GetArea(void) const1042 const fheroes2::Rect & Battle::Interface::GetArea( void ) const
1043 {
1044     return _surfaceInnerArea;
1045 }
1046 
GetMouseCursor() const1047 fheroes2::Point Battle::Interface::GetMouseCursor() const
1048 {
1049     return LocalEvent::Get().GetMouseCursor() - _interfacePosition.getPosition();
1050 }
1051 
SetStatus(const std::string & msg,bool top)1052 void Battle::Interface::SetStatus( const std::string & msg, bool top )
1053 {
1054     if ( top ) {
1055         status.SetMessage( msg, true );
1056         status.SetMessage( "", false );
1057     }
1058     else {
1059         status.SetMessage( msg );
1060     }
1061     humanturn_redraw = true;
1062 }
1063 
UpdateContourColor()1064 void Battle::Interface::UpdateContourColor()
1065 {
1066     ++_contourCycle;
1067 
1068     if ( _brightLandType ) {
1069         static const uint8_t contourColorTable[] = {108, 115, 122, 129, 122, 115};
1070         _contourColor = contourColorTable[_contourCycle % sizeof( contourColorTable )];
1071     }
1072     else {
1073         static const uint8_t contourColorTable[] = {110, 114, 118, 122, 126, 122, 118, 114};
1074         _contourColor = contourColorTable[_contourCycle % sizeof( contourColorTable )];
1075     }
1076 }
1077 
fullRedraw()1078 void Battle::Interface::fullRedraw()
1079 {
1080     if ( !_background ) {
1081         _background.reset( new fheroes2::StandardWindow( fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT ) );
1082     }
1083 
1084     Redraw();
1085 }
1086 
Redraw(void)1087 void Battle::Interface::Redraw( void )
1088 {
1089     RedrawPartialStart();
1090     RedrawPartialFinish();
1091 }
1092 
RedrawPartialStart()1093 void Battle::Interface::RedrawPartialStart()
1094 {
1095     RedrawCover();
1096     RedrawArmies();
1097 }
1098 
RedrawPartialFinish()1099 void Battle::Interface::RedrawPartialFinish()
1100 {
1101     fheroes2::Display & display = fheroes2::Display::instance();
1102 
1103     if ( Settings::Get().ExtBattleShowBattleOrder() )
1104         armies_order.Redraw( _currentUnit, _mainSurface );
1105 
1106 #ifdef WITH_DEBUG
1107     if ( IS_DEVEL() ) {
1108         const Board & board = *Arena::GetBoard();
1109         for ( Board::const_iterator it = board.begin(); it != board.end(); ++it ) {
1110             uint32_t distance = arena.CalculateMoveDistance( it->GetIndex() );
1111             if ( distance != MAX_MOVE_COST ) {
1112                 Text text( std::to_string( distance ), Font::SMALL );
1113                 text.Blit( ( *it ).GetPos().x + 20, ( *it ).GetPos().y + 22, _mainSurface );
1114             }
1115         }
1116     }
1117 #endif
1118 
1119     fheroes2::Blit( _mainSurface, display, _interfacePosition.x, _interfacePosition.y );
1120     RedrawInterface();
1121 
1122     display.render();
1123 }
1124 
RedrawInterface(void)1125 void Battle::Interface::RedrawInterface( void )
1126 {
1127     const Settings & conf = Settings::Get();
1128 
1129     status.Redraw();
1130 
1131     btn_auto.draw();
1132     btn_settings.draw();
1133 
1134     if ( conf.ExtBattleSoftWait() )
1135         btn_wait.draw();
1136     btn_skip.draw();
1137 
1138     popup.Redraw( _interfacePosition.x + _interfacePosition.width + 60, _interfacePosition.y + _interfacePosition.height );
1139 
1140     if ( listlog && listlog->isOpenLog() ) {
1141         listlog->Redraw();
1142     }
1143 }
1144 
RedrawArmies()1145 void Battle::Interface::RedrawArmies()
1146 {
1147     const Castle * castle = Arena::GetCastle();
1148 
1149     const int32_t wallCellIds[ARENAH]
1150         = { Arena::CASTLE_FIRST_TOP_WALL_POS, Arena::CASTLE_TOP_ARCHER_TOWER_POS,  Arena::CASTLE_SECOND_TOP_WALL_POS, Arena::CASTLE_TOP_GATE_TOWER_POS,
1151             Arena::CASTLE_GATE_POS,           Arena::CASTLE_BOTTOM_GATE_TOWER_POS, Arena::CASTLE_THIRD_TOP_WALL_POS,  Arena::CASTLE_BOTTOM_ARCHER_TOWER_POS,
1152             Arena::CASTLE_FOURTH_TOP_WALL_POS };
1153 
1154     if ( castle == nullptr ) {
1155         RedrawKilled();
1156     }
1157 
1158     for ( int32_t cellRowId = 0; cellRowId < ARENAH; ++cellRowId ) {
1159         // Redraw objects.
1160         for ( int32_t cellColumnId = 0; cellColumnId < ARENAW; ++cellColumnId ) {
1161             const int32_t cellId = cellRowId * ARENAW + cellColumnId;
1162             RedrawHighObjects( cellId );
1163         }
1164 
1165         if ( castle != nullptr ) {
1166             // Redraw main tower.
1167             if ( cellRowId == 5 ) {
1168                 RedrawCastleMainTower( *castle );
1169             }
1170             else if ( cellRowId == 7 ) { // Redraw catapult.
1171                 RedrawCastle2( *castle, Arena::CATAPULT_POS );
1172             }
1173 
1174             std::vector<const Unit *> deadTroopBeforeWall;
1175             std::vector<const Unit *> deadTroopAfterWall;
1176 
1177             std::vector<const Unit *> troopCounterBeforeWall;
1178             std::vector<const Unit *> troopCounterAfterWall;
1179 
1180             std::vector<const Unit *> troopBeforeWall;
1181             std::vector<const Unit *> troopAfterWall;
1182 
1183             std::vector<const Unit *> movingTroopBeforeWall;
1184             std::vector<const Unit *> movingTroopAfterWall;
1185 
1186             const int32_t wallCellId = wallCellIds[cellRowId];
1187 
1188             for ( int32_t cellColumnId = 0; cellColumnId < ARENAW; ++cellColumnId ) {
1189                 const int32_t cellId = cellRowId * ARENAW + cellColumnId;
1190 
1191                 bool isCellBefore = true;
1192                 if ( cellRowId < 5 ) {
1193                     isCellBefore = cellId < wallCellId;
1194                 }
1195                 else {
1196                     isCellBefore = cellId > wallCellId;
1197                     if ( ( wallCellId == Arena::CASTLE_THIRD_TOP_WALL_POS || wallCellId == Arena::CASTLE_FOURTH_TOP_WALL_POS )
1198                          && Board::GetCell( wallCellId )->GetObject() == 0 ) {
1199                         isCellBefore = false;
1200                     }
1201                 }
1202 
1203                 const std::vector<const Unit *> & deadUnits = arena.GetGraveyardTroops( cellId );
1204                 for ( size_t i = 0; i < deadUnits.size(); ++i ) {
1205                     if ( deadUnits[i] && cellId != deadUnits[i]->GetTailIndex() ) {
1206                         if ( isCellBefore ) {
1207                             deadTroopBeforeWall.emplace_back( deadUnits[i] );
1208                         }
1209                         else {
1210                             deadTroopAfterWall.emplace_back( deadUnits[i] );
1211                         }
1212                     }
1213                 }
1214 
1215                 const Unit * unitOnCell = Board::GetCell( cellId )->GetUnit();
1216                 if ( unitOnCell == nullptr || _flyingUnit == unitOnCell || cellId == unitOnCell->GetTailIndex() ) {
1217                     continue;
1218                 }
1219 
1220                 if ( _movingUnit != unitOnCell && unitOnCell->isValid() ) {
1221                     const int unitAnimState = unitOnCell->GetAnimationState();
1222                     const bool isStaticUnit = unitAnimState == Monster_Info::STATIC || unitAnimState == Monster_Info::IDLE;
1223 
1224                     if ( isCellBefore ) {
1225                         if ( isStaticUnit ) {
1226                             troopCounterBeforeWall.emplace_back( unitOnCell );
1227                         }
1228                         troopBeforeWall.emplace_back( unitOnCell );
1229                     }
1230                     else {
1231                         if ( isStaticUnit ) {
1232                             troopCounterAfterWall.emplace_back( unitOnCell );
1233                         }
1234                         troopAfterWall.emplace_back( unitOnCell );
1235                     }
1236                 }
1237                 else {
1238                     if ( isCellBefore ) {
1239                         movingTroopBeforeWall.emplace_back( unitOnCell );
1240                     }
1241                     else {
1242                         movingTroopAfterWall.emplace_back( unitOnCell );
1243                     }
1244                 }
1245             }
1246 
1247             for ( size_t i = 0; i < deadTroopBeforeWall.size(); ++i ) {
1248                 RedrawTroopSprite( *deadTroopBeforeWall[i] );
1249             }
1250 
1251             for ( size_t i = 0; i < troopBeforeWall.size(); ++i ) {
1252                 RedrawTroopSprite( *troopBeforeWall[i] );
1253             }
1254 
1255             for ( size_t i = 0; i < troopCounterBeforeWall.size(); ++i ) {
1256                 RedrawTroopCount( *troopCounterBeforeWall[i] );
1257             }
1258 
1259             for ( size_t i = 0; i < movingTroopBeforeWall.size(); ++i ) {
1260                 RedrawTroopSprite( *movingTroopBeforeWall[i] );
1261             }
1262 
1263             RedrawCastle2( *castle, wallCellId );
1264 
1265             for ( size_t i = 0; i < deadTroopAfterWall.size(); ++i ) {
1266                 RedrawTroopSprite( *deadTroopAfterWall[i] );
1267             }
1268 
1269             for ( size_t i = 0; i < troopAfterWall.size(); ++i ) {
1270                 RedrawTroopSprite( *troopAfterWall[i] );
1271             }
1272 
1273             for ( size_t i = 0; i < troopCounterAfterWall.size(); ++i ) {
1274                 RedrawTroopCount( *troopCounterAfterWall[i] );
1275             }
1276 
1277             for ( size_t i = 0; i < movingTroopAfterWall.size(); ++i ) {
1278                 RedrawTroopSprite( *movingTroopAfterWall[i] );
1279             }
1280         }
1281         else {
1282             std::vector<const Unit *> troopCounter;
1283             std::vector<const Unit *> troop;
1284             std::vector<const Unit *> movingTroop;
1285 
1286             // Redraw monsters.
1287             for ( int32_t cellColumnId = 0; cellColumnId < ARENAW; ++cellColumnId ) {
1288                 const int32_t cellId = cellRowId * ARENAW + cellColumnId;
1289 
1290                 const Unit * unitOnCell = Board::GetCell( cellId )->GetUnit();
1291                 if ( unitOnCell == nullptr || _flyingUnit == unitOnCell || cellId == unitOnCell->GetTailIndex() ) {
1292                     continue;
1293                 }
1294 
1295                 if ( _movingUnit != unitOnCell && unitOnCell->isValid() ) {
1296                     const int unitAnimState = unitOnCell->GetAnimationState();
1297                     const bool isStaticUnit = unitAnimState == Monster_Info::STATIC || unitAnimState == Monster_Info::IDLE;
1298                     if ( isStaticUnit ) {
1299                         troopCounter.emplace_back( unitOnCell );
1300                     }
1301 
1302                     troop.emplace_back( unitOnCell );
1303                 }
1304                 else {
1305                     movingTroop.emplace_back( unitOnCell );
1306                 }
1307             }
1308 
1309             // Redraw monster counters.
1310             for ( size_t i = 0; i < troop.size(); ++i ) {
1311                 RedrawTroopSprite( *troop[i] );
1312             }
1313 
1314             for ( size_t i = 0; i < troopCounter.size(); ++i ) {
1315                 RedrawTroopCount( *troopCounter[i] );
1316             }
1317 
1318             for ( size_t i = 0; i < movingTroop.size(); ++i ) {
1319                 RedrawTroopSprite( *movingTroop[i] );
1320             }
1321         }
1322 
1323         // Redraw heroes.
1324         if ( cellRowId == 2 ) {
1325             RedrawOpponents();
1326         }
1327     }
1328 
1329     if ( _flyingUnit ) {
1330         RedrawTroopSprite( *_flyingUnit );
1331     }
1332 }
1333 
RedrawOpponents(void)1334 void Battle::Interface::RedrawOpponents( void )
1335 {
1336     if ( opponent1 )
1337         opponent1->Redraw( _mainSurface );
1338     if ( opponent2 )
1339         opponent2->Redraw( _mainSurface );
1340 
1341     RedrawOpponentsFlags();
1342 }
1343 
RedrawOpponentsFlags(void)1344 void Battle::Interface::RedrawOpponentsFlags( void )
1345 {
1346     if ( opponent1 ) {
1347         int icn = ICN::UNKNOWN;
1348 
1349         switch ( arena.GetArmyColor1() ) {
1350         case Color::BLUE:
1351             icn = ICN::HEROFL00;
1352             break;
1353         case Color::GREEN:
1354             icn = ICN::HEROFL01;
1355             break;
1356         case Color::RED:
1357             icn = ICN::HEROFL02;
1358             break;
1359         case Color::YELLOW:
1360             icn = ICN::HEROFL03;
1361             break;
1362         case Color::ORANGE:
1363             icn = ICN::HEROFL04;
1364             break;
1365         case Color::PURPLE:
1366             icn = ICN::HEROFL05;
1367             break;
1368         default:
1369             icn = ICN::HEROFL06;
1370             break;
1371         }
1372 
1373         const fheroes2::Sprite & flag = fheroes2::AGG::GetICN( icn, ICN::AnimationFrame( icn, 0, animation_flags_frame ) );
1374         fheroes2::Blit( flag, _mainSurface, opponent1->Offset().x + OpponentSprite::HERO_X_OFFSET + flag.x(),
1375                         opponent1->Offset().y + OpponentSprite::LEFT_HERO_Y_OFFSET + flag.y() );
1376     }
1377 
1378     if ( opponent2 ) {
1379         int icn = ICN::UNKNOWN;
1380 
1381         switch ( arena.GetForce2().GetColor() ) {
1382         case Color::BLUE:
1383             icn = ICN::HEROFL00;
1384             break;
1385         case Color::GREEN:
1386             icn = ICN::HEROFL01;
1387             break;
1388         case Color::RED:
1389             icn = ICN::HEROFL02;
1390             break;
1391         case Color::YELLOW:
1392             icn = ICN::HEROFL03;
1393             break;
1394         case Color::ORANGE:
1395             icn = ICN::HEROFL04;
1396             break;
1397         case Color::PURPLE:
1398             icn = ICN::HEROFL05;
1399             break;
1400         default:
1401             icn = ICN::HEROFL06;
1402             break;
1403         }
1404 
1405         const fheroes2::Sprite & flag = fheroes2::AGG::GetICN( icn, ICN::AnimationFrame( icn, 0, animation_flags_frame ) );
1406         const fheroes2::Point offset = opponent2->Offset();
1407         fheroes2::Blit( flag, _mainSurface, offset.x + fheroes2::Display::DEFAULT_WIDTH - OpponentSprite::HERO_X_OFFSET - ( flag.x() + flag.width() ),
1408                         offset.y + OpponentSprite::RIGHT_HERO_Y_OFFSET + flag.y(), true );
1409     }
1410 }
1411 
GetTroopPosition(const Battle::Unit & unit,const fheroes2::Sprite & sprite)1412 fheroes2::Point GetTroopPosition( const Battle::Unit & unit, const fheroes2::Sprite & sprite )
1413 {
1414     const fheroes2::Rect & rt = unit.GetRectPosition();
1415 
1416     int32_t offsetX = 0;
1417     if ( unit.isReflect() ) {
1418         if ( unit.isWide() ) {
1419             offsetX = rt.x + ( rt.width / 2 + rt.width / 4 ) - sprite.width() - sprite.x() + 1;
1420         }
1421         else {
1422             offsetX = rt.x + ( rt.width / 2 ) - sprite.width() - sprite.x() + 1;
1423         }
1424     }
1425     else {
1426         if ( unit.isWide() ) {
1427             offsetX = rt.x + ( rt.width / 4 ) + sprite.x();
1428         }
1429         else {
1430             offsetX = rt.x + ( rt.width / 2 ) + sprite.x();
1431         }
1432     }
1433 
1434     const int32_t offsetY = rt.y + rt.height + sprite.y() + cellYOffset;
1435 
1436     return fheroes2::Point( offsetX, offsetY );
1437 }
1438 
RedrawTroopSprite(const Unit & unit)1439 void Battle::Interface::RedrawTroopSprite( const Unit & unit )
1440 {
1441     if ( b_current_sprite && _currentUnit == &unit ) {
1442         drawTroopSprite( unit, *b_current_sprite );
1443     }
1444     else if ( unit.Modes( SP_STONE ) ) {
1445         // Current monster can't be active if it's under Stunning effect.
1446         const int monsterIcnId = unit.GetMonsterSprite();
1447         fheroes2::Sprite monsterSprite = fheroes2::AGG::GetICN( monsterIcnId, unit.GetFrame() );
1448         fheroes2::ApplyPalette( monsterSprite, PAL::GetPalette( PAL::PaletteType::GRAY ) );
1449         drawTroopSprite( unit, monsterSprite );
1450     }
1451     else if ( unit.Modes( CAP_MIRRORIMAGE ) ) {
1452         fheroes2::Sprite monsterSprite;
1453 
1454         if ( _currentUnit == &unit && b_current_sprite != nullptr ) {
1455             monsterSprite = *b_current_sprite;
1456         }
1457         else {
1458             const int monsterIcnId = unit.GetMonsterSprite();
1459             monsterSprite = fheroes2::AGG::GetICN( monsterIcnId, unit.GetFrame() );
1460         }
1461 
1462         fheroes2::ApplyPalette( monsterSprite, PAL::GetPalette( PAL::PaletteType::MIRROR_IMAGE ) );
1463 
1464         const fheroes2::Point drawnPosition = drawTroopSprite( unit, monsterSprite );
1465 
1466         if ( _currentUnit == &unit && b_current_sprite == nullptr ) {
1467             // Current unit's turn which is idling.
1468             const fheroes2::Sprite & monsterContour = fheroes2::CreateContour( monsterSprite, _contourColor );
1469             fheroes2::Blit( monsterContour, _mainSurface, drawnPosition.x, drawnPosition.y, unit.isReflect() );
1470         }
1471     }
1472     else {
1473         const int monsterIcnId = unit.GetMonsterSprite();
1474         const bool isCurrentMonsterAction = ( _currentUnit == &unit && b_current_sprite != nullptr );
1475 
1476         const fheroes2::Sprite & monsterSprite = isCurrentMonsterAction ? *b_current_sprite : fheroes2::AGG::GetICN( monsterIcnId, unit.GetFrame() );
1477 
1478         const fheroes2::Point drawnPosition = drawTroopSprite( unit, monsterSprite );
1479 
1480         if ( _currentUnit == &unit && b_current_sprite == nullptr ) {
1481             // Current unit's turn which is idling.
1482             const fheroes2::Sprite & monsterContour = fheroes2::CreateContour( monsterSprite, _contourColor );
1483             fheroes2::Blit( monsterContour, _mainSurface, drawnPosition.x, drawnPosition.y, unit.isReflect() );
1484         }
1485     }
1486 }
1487 
drawTroopSprite(const Unit & unit,const fheroes2::Sprite & troopSprite)1488 fheroes2::Point Battle::Interface::drawTroopSprite( const Unit & unit, const fheroes2::Sprite & troopSprite )
1489 {
1490     const fheroes2::Rect & rt = unit.GetRectPosition();
1491     fheroes2::Point sp = GetTroopPosition( unit, troopSprite );
1492 
1493     if ( _movingUnit == &unit ) {
1494         // Monster is moving.
1495         // Here we're getting the first frame and then based on the offset from the first frame we calculate the position of the current frame.
1496         // TODO: verify if it's the correct way as we have issues for monster movement animation.
1497         const int monsterIcnId = unit.GetMonsterSprite();
1498         const fheroes2::Sprite & firstMonsterFrame = fheroes2::AGG::GetICN( monsterIcnId, _movingUnit->animation.firstFrame() );
1499         const s32 ox = troopSprite.x() - firstMonsterFrame.x();
1500 
1501         if ( _movingUnit->animation.animationLength() ) {
1502             const int32_t cx = _movingPos.x - rt.x;
1503             const int32_t cy = _movingPos.y - rt.y;
1504 
1505             // TODO: use offset X from bin file for ground movement
1506             // cx/cy is sprite size
1507             // Frame count: one tile of movement goes through all stages of animation
1508             // sp is sprite drawing offset
1509             sp.y += static_cast<int32_t>( _movingUnit->animation.movementProgress() * cy );
1510             if ( 0 != Sign( cy ) )
1511                 sp.x -= Sign( cx ) * ox / 2;
1512         }
1513     }
1514     else if ( _flyingUnit == &unit ) {
1515         // Monster is flying.
1516         const int32_t cx = _flyingPos.x - rt.x;
1517         const int32_t cy = _flyingPos.y - rt.y;
1518 
1519         const double movementProgress = _flyingUnit->animation.movementProgress();
1520 
1521         sp.x += cx + static_cast<int32_t>( ( _movingPos.x - _flyingPos.x ) * movementProgress );
1522         sp.y += cy + static_cast<int32_t>( ( _movingPos.y - _flyingPos.y ) * movementProgress );
1523     }
1524 
1525     fheroes2::AlphaBlit( troopSprite, _mainSurface, sp.x, sp.y, unit.GetCustomAlpha(), unit.isReflect() );
1526 
1527     return sp;
1528 }
1529 
RedrawTroopCount(const Unit & unit)1530 void Battle::Interface::RedrawTroopCount( const Unit & unit )
1531 {
1532     const fheroes2::Rect & rt = unit.GetRectPosition();
1533     const fheroes2::Sprite & bar = fheroes2::AGG::GetICN( ICN::TEXTBAR, GetIndexIndicator( unit ) );
1534     const bool isReflected = unit.isReflect();
1535 
1536     const int32_t monsterIndex = unit.GetHeadIndex();
1537     const int tileInFront = Board::GetIndexDirection( monsterIndex, isReflected ? Battle::LEFT : Battle::RIGHT );
1538     const bool isValidFrontMonster = ( monsterIndex / ARENAW ) == ( tileInFront == ARENAW );
1539 
1540     s32 sx = rt.x + ( isReflected ? -7 : rt.width - 13 );
1541     const s32 sy = rt.y + rt.height - bar.height() - ( isReflected ? 21 : 9 );
1542 
1543     int xOffset = unit.animation.getTroopCountOffset( isReflected );
1544     // check if has unit standing in front
1545     if ( xOffset > 0 && isValidFrontMonster && Board::isValidIndex( tileInFront ) && Board::GetCell( tileInFront )->GetUnit() != nullptr )
1546         xOffset = 0;
1547 
1548     sx += isReflected ? -xOffset : xOffset;
1549 
1550     fheroes2::Blit( bar, _mainSurface, sx, sy );
1551 
1552     Text text( GetStringShort( unit.GetCount() ), Font::SMALL );
1553     text.Blit( sx + ( bar.width() - text.w() ) / 2, sy, _mainSurface );
1554 }
1555 
RedrawCover()1556 void Battle::Interface::RedrawCover()
1557 {
1558     const Settings & conf = Settings::Get();
1559     const Board & board = *Arena::GetBoard();
1560 
1561     RedrawCoverStatic( conf, board );
1562 
1563     const Bridge * bridge = Arena::GetBridge();
1564     if ( bridge && ( bridge->isDown() || _bridgeAnimation.animationIsRequired ) ) {
1565         uint32_t spriteIndex = bridge->isDestroy() ? BridgeMovementAnimation::DESTROYED : BridgeMovementAnimation::DOWN_POSITION;
1566 
1567         if ( _bridgeAnimation.animationIsRequired ) {
1568             spriteIndex = _bridgeAnimation.currentFrameId;
1569         }
1570 
1571         const fheroes2::Sprite & bridgeImage = fheroes2::AGG::GetICN( ICN::Get4Castle( Arena::GetCastle()->GetRace() ), spriteIndex );
1572         fheroes2::Blit( bridgeImage, _mainSurface, bridgeImage.x(), bridgeImage.y() );
1573     }
1574 
1575     // cursor
1576     const Cell * cell = Board::GetCell( index_pos );
1577     const int cursorType = Cursor::Get().Themes();
1578 
1579     if ( cell && _currentUnit && conf.BattleShowMouseShadow() ) {
1580         std::set<const Cell *> highlightCells;
1581 
1582         if ( humanturn_spell.isValid() ) {
1583             switch ( humanturn_spell.GetID() ) {
1584             case Spell::COLDRING: {
1585                 const Indexes around = Board::GetAroundIndexes( index_pos );
1586                 for ( size_t i = 0; i < around.size(); ++i ) {
1587                     const Cell * aroundCell = Board::GetCell( around[i] );
1588                     if ( aroundCell != nullptr ) {
1589                         highlightCells.emplace( aroundCell );
1590                     }
1591                 }
1592                 break;
1593             }
1594             case Spell::FIREBALL:
1595             case Spell::METEORSHOWER: {
1596                 highlightCells.emplace( cell );
1597                 const Indexes around = Board::GetAroundIndexes( index_pos );
1598                 for ( size_t i = 0; i < around.size(); ++i ) {
1599                     const Cell * aroundCell = Board::GetCell( around[i] );
1600                     if ( aroundCell != nullptr ) {
1601                         highlightCells.emplace( aroundCell );
1602                     }
1603                 }
1604                 break;
1605             }
1606             case Spell::FIREBLAST: {
1607                 highlightCells.emplace( cell );
1608                 const Indexes around = Board::GetAroundIndexes( index_pos );
1609                 for ( size_t i = 0; i < around.size(); ++i ) {
1610                     const Cell * aroundCell = Board::GetCell( around[i] );
1611                     if ( aroundCell != nullptr ) {
1612                         highlightCells.emplace( aroundCell );
1613                     }
1614 
1615                     const Indexes aroundTwice = Board::GetAroundIndexes( around[i] );
1616                     for ( size_t j = 0; j < aroundTwice.size(); ++j ) {
1617                         const Cell * aroundCellTwice = Board::GetCell( aroundTwice[j] );
1618                         if ( aroundCellTwice != nullptr ) {
1619                             highlightCells.emplace( aroundCellTwice );
1620                         }
1621                     }
1622                 }
1623                 break;
1624             }
1625             default:
1626                 highlightCells.emplace( cell );
1627             }
1628         }
1629         else if ( _currentUnit->isAbilityPresent( fheroes2::MonsterAbilityType::AREA_SHOT )
1630                   && ( cursorType == Cursor::WAR_ARROW || cursorType == Cursor::WAR_BROKENARROW ) ) {
1631             highlightCells.emplace( cell );
1632             const Indexes around = Board::GetAroundIndexes( index_pos );
1633             for ( size_t i = 0; i < around.size(); ++i ) {
1634                 const Cell * aroundCell = Board::GetCell( around[i] );
1635                 if ( aroundCell != nullptr ) {
1636                     highlightCells.emplace( aroundCell );
1637                 }
1638             }
1639         }
1640         else if ( _currentUnit->isWide() && ( cursorType == Cursor::WAR_MOVE || cursorType == Cursor::WAR_FLY ) ) {
1641             const Position pos = Position::GetReachable( *_currentUnit, index_pos );
1642 
1643             assert( pos.GetHead() != nullptr );
1644             assert( pos.GetTail() != nullptr );
1645 
1646             highlightCells.emplace( pos.GetHead() );
1647             highlightCells.emplace( pos.GetTail() );
1648         }
1649         else if ( cursorType == Cursor::SWORD_TOPLEFT || cursorType == Cursor::SWORD_TOPRIGHT || cursorType == Cursor::SWORD_BOTTOMLEFT
1650                   || cursorType == Cursor::SWORD_BOTTOMRIGHT || cursorType == Cursor::SWORD_LEFT || cursorType == Cursor::SWORD_RIGHT ) {
1651             highlightCells.emplace( cell );
1652 
1653             int direction = 0;
1654             if ( cursorType == Cursor::SWORD_TOPLEFT ) {
1655                 direction = BOTTOM_RIGHT;
1656             }
1657             else if ( cursorType == Cursor::SWORD_TOPRIGHT ) {
1658                 direction = BOTTOM_LEFT;
1659             }
1660             else if ( cursorType == Cursor::SWORD_BOTTOMLEFT ) {
1661                 direction = TOP_RIGHT;
1662             }
1663             else if ( cursorType == Cursor::SWORD_BOTTOMRIGHT ) {
1664                 direction = TOP_LEFT;
1665             }
1666             else if ( cursorType == Cursor::SWORD_LEFT ) {
1667                 direction = RIGHT;
1668             }
1669             else if ( cursorType == Cursor::SWORD_RIGHT ) {
1670                 direction = LEFT;
1671             }
1672             else {
1673                 assert( 0 );
1674             }
1675 
1676             const Position pos = Position::GetReachable( *_currentUnit, Board::GetIndexDirection( index_pos, direction ) );
1677 
1678             assert( pos.GetHead() != nullptr );
1679 
1680             highlightCells.emplace( pos.GetHead() );
1681 
1682             if ( _currentUnit->isWide() ) {
1683                 assert( pos.GetTail() != nullptr );
1684 
1685                 highlightCells.emplace( pos.GetTail() );
1686             }
1687 
1688             if ( _currentUnit->isDoubleCellAttack() ) {
1689                 // We have to invert the direction.
1690                 int attackDirection = Board::GetDirection( pos.GetHead()->GetIndex(), index_pos );
1691                 if ( attackDirection == 0 ) {
1692                     // It happens when a creature needs to swap tail and head for an attack move.
1693                     attackDirection = Board::GetDirection( pos.GetTail()->GetIndex(), index_pos );
1694                 }
1695 
1696                 assert( attackDirection != 0 );
1697 
1698                 const Cell * secondAttackedCell = Board::GetCell( index_pos, attackDirection );
1699                 if ( secondAttackedCell != nullptr ) {
1700                     highlightCells.emplace( secondAttackedCell );
1701                 }
1702             }
1703         }
1704         else {
1705             highlightCells.emplace( cell );
1706         }
1707 
1708         assert( !highlightCells.empty() );
1709 
1710         const HeroBase * currentCommander = arena.GetCurrentCommander();
1711         const int spellPower = ( currentCommander == nullptr ) ? 0 : currentCommander->GetPower();
1712 
1713         for ( const Cell * highlightCell : highlightCells ) {
1714             bool isApplicable = highlightCell->isPassable1( false );
1715 
1716             if ( isApplicable ) {
1717                 const Unit * highlightedUnit = highlightCell->GetUnit();
1718 
1719                 isApplicable = highlightedUnit == nullptr || !humanturn_spell.isValid() || !highlightedUnit->isMagicResist( humanturn_spell, spellPower );
1720             }
1721 
1722             if ( isApplicable ) {
1723                 fheroes2::Blit( sf_cursor, _mainSurface, highlightCell->GetPos().x, highlightCell->GetPos().y );
1724             }
1725         }
1726     }
1727 }
1728 
RedrawCoverStatic(const Settings & conf,const Board & board)1729 void Battle::Interface::RedrawCoverStatic( const Settings & conf, const Board & board )
1730 {
1731     if ( icn_cbkg != ICN::UNKNOWN ) {
1732         const fheroes2::Sprite & cbkg = fheroes2::AGG::GetICN( icn_cbkg, 0 );
1733         fheroes2::Copy( cbkg, _mainSurface );
1734     }
1735 
1736     if ( icn_frng != ICN::UNKNOWN ) {
1737         const fheroes2::Sprite & frng = fheroes2::AGG::GetICN( icn_frng, 0 );
1738         fheroes2::Blit( frng, _mainSurface, frng.x(), frng.y() );
1739     }
1740 
1741     if ( arena.GetICNCovr() != ICN::UNKNOWN ) {
1742         const fheroes2::Sprite & cover = fheroes2::AGG::GetICN( arena.GetICNCovr(), 0 );
1743         fheroes2::Blit( cover, _mainSurface, cover.x(), cover.y() );
1744     }
1745 
1746     if ( conf.BattleShowGrid() ) { // grid
1747         for ( const Cell & cell : board ) {
1748             fheroes2::Blit( sf_hexagon, _mainSurface, cell.GetPos().x, cell.GetPos().y );
1749         }
1750     }
1751 
1752     // ground obstacles
1753     for ( int32_t cellId = 0; cellId < ARENASIZE; ++cellId ) {
1754         RedrawLowObjects( cellId );
1755     }
1756 
1757     const Castle * castle = Arena::GetCastle();
1758     if ( castle )
1759         RedrawCastle1( *castle );
1760 
1761     if ( !_movingUnit && conf.BattleShowMoveShadow() && _currentUnit && !( _currentUnit->GetCurrentControl() & CONTROL_AI ) ) { // shadow
1762         for ( const Cell & cell : board ) {
1763             if ( cell.isReachableForHead() || cell.isReachableForTail() ) {
1764                 fheroes2::Blit( sf_shadow, _mainSurface, cell.GetPos().x, cell.GetPos().y );
1765             }
1766         }
1767     }
1768 }
1769 
RedrawCastle1(const Castle & castle)1770 void Battle::Interface::RedrawCastle1( const Castle & castle )
1771 {
1772     int icn_castbkg = ICN::UNKNOWN;
1773 
1774     switch ( castle.GetRace() ) {
1775     default:
1776     case Race::BARB:
1777         icn_castbkg = ICN::CASTBKGB;
1778         break;
1779     case Race::KNGT:
1780         icn_castbkg = ICN::CASTBKGK;
1781         break;
1782     case Race::NECR:
1783         icn_castbkg = ICN::CASTBKGN;
1784         break;
1785     case Race::SORC:
1786         icn_castbkg = ICN::CASTBKGS;
1787         break;
1788     case Race::WRLK:
1789         icn_castbkg = ICN::CASTBKGW;
1790         break;
1791     case Race::WZRD:
1792         icn_castbkg = ICN::CASTBKGZ;
1793         break;
1794     }
1795 
1796     // castle cover
1797     const fheroes2::Sprite & sprite1 = fheroes2::AGG::GetICN( icn_castbkg, 1 );
1798     fheroes2::Blit( sprite1, _mainSurface, sprite1.x(), sprite1.y() );
1799 
1800     // moat
1801     if ( castle.isBuild( BUILD_MOAT ) ) {
1802         const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::MOATWHOL, 0 );
1803         fheroes2::Blit( sprite, _mainSurface, sprite.x(), sprite.y() );
1804     }
1805 
1806     // top wall
1807     const fheroes2::Sprite & sprite2 = fheroes2::AGG::GetICN( icn_castbkg, castle.isFortificationBuild() ? 4 : 3 );
1808     fheroes2::Blit( sprite2, _mainSurface, sprite2.x(), sprite2.y() );
1809 }
1810 
RedrawCastle2(const Castle & castle,int32_t cellId)1811 void Battle::Interface::RedrawCastle2( const Castle & castle, int32_t cellId )
1812 {
1813     const int castleIcnId = ICN::Get4Castle( castle.GetRace() );
1814 
1815     if ( Arena::CATAPULT_POS == cellId ) {
1816         const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::CATAPULT, catapult_frame );
1817         fheroes2::Blit( sprite, _mainSurface, 22 + sprite.x(), 390 + sprite.y() );
1818     }
1819     else if ( Arena::CASTLE_GATE_POS == cellId ) {
1820         const Bridge * bridge = Arena::GetBridge();
1821         assert( bridge != nullptr );
1822         if ( bridge != nullptr && !bridge->isDestroy() ) {
1823             const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( castleIcnId, 4 );
1824             fheroes2::Blit( sprite, _mainSurface, sprite.x(), sprite.y() );
1825         }
1826     }
1827     else if ( Arena::CASTLE_FIRST_TOP_WALL_POS == cellId || Arena::CASTLE_SECOND_TOP_WALL_POS == cellId || Arena::CASTLE_THIRD_TOP_WALL_POS == cellId
1828               || Arena::CASTLE_FOURTH_TOP_WALL_POS == cellId ) {
1829         uint32_t index = 0;
1830 
1831         switch ( cellId ) {
1832         case Arena::CASTLE_FIRST_TOP_WALL_POS:
1833             index = 5;
1834             break;
1835         case Arena::CASTLE_SECOND_TOP_WALL_POS:
1836             index = 6;
1837             break;
1838         case Arena::CASTLE_THIRD_TOP_WALL_POS:
1839             index = 7;
1840             break;
1841         case Arena::CASTLE_FOURTH_TOP_WALL_POS:
1842             index = 8;
1843             break;
1844         default:
1845             break;
1846         }
1847 
1848         if ( castle.isFortificationBuild() ) {
1849             switch ( Board::GetCell( cellId )->GetObject() ) {
1850             case 0:
1851                 index += 31;
1852                 break;
1853             case 1:
1854                 index += 35;
1855                 break;
1856             case 2:
1857                 index += 27;
1858                 break;
1859             case 3:
1860                 index += 23;
1861                 break;
1862             default:
1863                 break;
1864             }
1865         }
1866         else {
1867             switch ( Board::GetCell( cellId )->GetObject() ) {
1868             case 0:
1869                 index += 8;
1870                 break;
1871             case 1:
1872                 index += 4;
1873                 break;
1874             case 2:
1875                 index += 0;
1876                 break;
1877             default:
1878                 break;
1879             }
1880         }
1881 
1882         const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( castleIcnId, index );
1883         fheroes2::Blit( sprite, _mainSurface, sprite.x(), sprite.y() );
1884     }
1885     else if ( Arena::CASTLE_TOP_ARCHER_TOWER_POS == cellId ) {
1886         const Tower * ltower = Arena::GetTower( TWR_LEFT );
1887         uint32_t index = 17;
1888 
1889         if ( castle.isBuild( BUILD_LEFTTURRET ) && ltower )
1890             index = ltower->isValid() ? 18 : 19;
1891 
1892         const fheroes2::Sprite & towerSprite = fheroes2::AGG::GetICN( castleIcnId, index );
1893         fheroes2::Blit( towerSprite, _mainSurface, 443 + towerSprite.x(), 153 + towerSprite.y() );
1894     }
1895     else if ( Arena::CASTLE_BOTTOM_ARCHER_TOWER_POS == cellId ) {
1896         const Tower * rtower = Arena::GetTower( TWR_RIGHT );
1897         uint32_t index = 17;
1898 
1899         if ( castle.isBuild( BUILD_RIGHTTURRET ) && rtower )
1900             index = rtower->isValid() ? 18 : 19;
1901 
1902         const fheroes2::Sprite & towerSprite = fheroes2::AGG::GetICN( castleIcnId, index );
1903         fheroes2::Blit( towerSprite, _mainSurface, 443 + towerSprite.x(), 405 + towerSprite.y() );
1904     }
1905     else if ( Arena::CASTLE_TOP_GATE_TOWER_POS == cellId ) {
1906         const fheroes2::Sprite & towerSprite = fheroes2::AGG::GetICN( castleIcnId, 17 );
1907         fheroes2::Blit( towerSprite, _mainSurface, 399 + towerSprite.x(), 237 + towerSprite.y() );
1908     }
1909     else if ( Arena::CASTLE_BOTTOM_GATE_TOWER_POS == cellId ) {
1910         const fheroes2::Sprite & towerSprite = fheroes2::AGG::GetICN( castleIcnId, 17 );
1911         fheroes2::Blit( towerSprite, _mainSurface, 399 + towerSprite.x(), 321 + towerSprite.y() );
1912     }
1913 }
1914 
RedrawCastleMainTower(const Castle & castle)1915 void Battle::Interface::RedrawCastleMainTower( const Castle & castle )
1916 {
1917     const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::Get4Castle( castle.GetRace() ), ( Arena::GetTower( TWR_CENTER )->isValid() ? 20 : 26 ) );
1918 
1919     fheroes2::Blit( sprite, _mainSurface, sprite.x(), sprite.y() );
1920 }
1921 
RedrawLowObjects(s32 cell_index)1922 void Battle::Interface::RedrawLowObjects( s32 cell_index )
1923 {
1924     const Cell * cell = Board::GetCell( cell_index );
1925     if ( cell == nullptr )
1926         return;
1927 
1928     const int cellObjectId = cell->GetObject();
1929     if ( cellObjectId == 0 ) {
1930         // No object exists.
1931         return;
1932     }
1933 
1934     int objectIcnId = 0;
1935 
1936     switch ( cellObjectId ) {
1937     case 0x84:
1938         objectIcnId = ICN::COBJ0004;
1939         break;
1940     case 0x87:
1941         objectIcnId = ICN::COBJ0007;
1942         break;
1943     case 0x90:
1944         objectIcnId = ICN::COBJ0016;
1945         break;
1946     case 0x9E:
1947         objectIcnId = ICN::COBJ0030;
1948         break;
1949     case 0x9F:
1950         objectIcnId = ICN::COBJ0031;
1951         break;
1952     default:
1953         return;
1954     }
1955 
1956     const fheroes2::Sprite & objectSprite = fheroes2::AGG::GetICN( objectIcnId, 0 );
1957     const fheroes2::Rect & pt = cell->GetPos();
1958     fheroes2::Blit( objectSprite, _mainSurface, pt.x + pt.width / 2 + objectSprite.x(), pt.y + pt.height + objectSprite.y() + cellYOffset );
1959 }
1960 
RedrawHighObjects(s32 cell_index)1961 void Battle::Interface::RedrawHighObjects( s32 cell_index )
1962 {
1963     const Cell * cell = Board::GetCell( cell_index );
1964     if ( cell == nullptr )
1965         return;
1966 
1967     const int cellObjectId = cell->GetObject();
1968     if ( cellObjectId == 0 ) {
1969         // No object exists.
1970         return;
1971     }
1972 
1973     int objectIcnId = 0;
1974 
1975     switch ( cellObjectId ) {
1976     case 0x80:
1977         objectIcnId = ICN::COBJ0000;
1978         break;
1979     case 0x81:
1980         objectIcnId = ICN::COBJ0001;
1981         break;
1982     case 0x82:
1983         objectIcnId = ICN::COBJ0002;
1984         break;
1985     case 0x83:
1986         objectIcnId = ICN::COBJ0003;
1987         break;
1988     case 0x85:
1989         objectIcnId = ICN::COBJ0005;
1990         break;
1991     case 0x86:
1992         objectIcnId = ICN::COBJ0006;
1993         break;
1994     case 0x88:
1995         objectIcnId = ICN::COBJ0008;
1996         break;
1997     case 0x89:
1998         objectIcnId = ICN::COBJ0009;
1999         break;
2000     case 0x8A:
2001         objectIcnId = ICN::COBJ0010;
2002         break;
2003     case 0x8B:
2004         objectIcnId = ICN::COBJ0011;
2005         break;
2006     case 0x8C:
2007         objectIcnId = ICN::COBJ0012;
2008         break;
2009     case 0x8D:
2010         objectIcnId = ICN::COBJ0013;
2011         break;
2012     case 0x8E:
2013         objectIcnId = ICN::COBJ0014;
2014         break;
2015     case 0x8F:
2016         objectIcnId = ICN::COBJ0015;
2017         break;
2018     case 0x91:
2019         objectIcnId = ICN::COBJ0017;
2020         break;
2021     case 0x92:
2022         objectIcnId = ICN::COBJ0018;
2023         break;
2024     case 0x93:
2025         objectIcnId = ICN::COBJ0019;
2026         break;
2027     case 0x94:
2028         objectIcnId = ICN::COBJ0020;
2029         break;
2030     case 0x95:
2031         objectIcnId = ICN::COBJ0021;
2032         break;
2033     case 0x96:
2034         objectIcnId = ICN::COBJ0022;
2035         break;
2036     case 0x97:
2037         objectIcnId = ICN::COBJ0023;
2038         break;
2039     case 0x98:
2040         objectIcnId = ICN::COBJ0024;
2041         break;
2042     case 0x99:
2043         objectIcnId = ICN::COBJ0025;
2044         break;
2045     case 0x9A:
2046         objectIcnId = ICN::COBJ0026;
2047         break;
2048     case 0x9B:
2049         objectIcnId = ICN::COBJ0027;
2050         break;
2051     case 0x9C:
2052         objectIcnId = ICN::COBJ0028;
2053         break;
2054     case 0x9D:
2055         objectIcnId = ICN::COBJ0029;
2056         break;
2057     default:
2058         return;
2059     }
2060 
2061     const fheroes2::Sprite & objectSprite = fheroes2::AGG::GetICN( objectIcnId, 0 );
2062     const fheroes2::Rect & pt = cell->GetPos();
2063     fheroes2::Blit( objectSprite, _mainSurface, pt.x + pt.width / 2 + objectSprite.x(), pt.y + pt.height + objectSprite.y() + cellYOffset );
2064 }
2065 
RedrawKilled()2066 void Battle::Interface::RedrawKilled()
2067 {
2068     // redraw killed troop
2069     const Indexes cells = arena.GraveyardClosedCells();
2070 
2071     for ( Indexes::const_iterator it = cells.begin(); it != cells.end(); ++it ) {
2072         const std::vector<const Unit *> & units = arena.GetGraveyardTroops( *it );
2073         for ( size_t i = 0; i < units.size(); ++i ) {
2074             if ( units[i] && *it != units[i]->GetTailIndex() ) {
2075                 RedrawTroopSprite( *units[i] );
2076             }
2077         }
2078     }
2079 }
2080 
GetBattleCursor(std::string & statusMsg) const2081 int Battle::Interface::GetBattleCursor( std::string & statusMsg ) const
2082 {
2083     statusMsg.clear();
2084 
2085     const Cell * cell = Board::GetCell( index_pos );
2086 
2087     if ( cell && _currentUnit ) {
2088         const Unit * b_enemy = cell->GetUnit();
2089 
2090         if ( b_enemy ) {
2091             if ( _currentUnit->GetCurrentColor() == b_enemy->GetColor() || ( _currentUnit == b_enemy ) ) {
2092                 statusMsg = _( "View %{monster} info" );
2093                 StringReplace( statusMsg, "%{monster}", b_enemy->GetMultiName() );
2094                 return Cursor::WAR_INFO;
2095             }
2096             else {
2097                 if ( _currentUnit->isArchers() && !_currentUnit->isHandFighting() ) {
2098                     statusMsg = _( "Shoot %{monster}" );
2099                     statusMsg.append( " " );
2100                     statusMsg.append( _n( "(1 shot left)", "(%{count} shots left)", _currentUnit->GetShots() ) );
2101                     StringReplace( statusMsg, "%{monster}", b_enemy->GetMultiName() );
2102                     StringReplace( statusMsg, "%{count}", _currentUnit->GetShots() );
2103 
2104                     return arena.IsShootingPenalty( *_currentUnit, *b_enemy ) ? Cursor::WAR_BROKENARROW : Cursor::WAR_ARROW;
2105                 }
2106                 else {
2107                     const int dir = cell->GetTriangleDirection( GetMouseCursor() );
2108                     const int cursor = GetSwordCursorDirection( dir );
2109 
2110                     if ( cursor && Board::isValidDirection( index_pos, dir ) ) {
2111                         const s32 from = Board::GetIndexDirection( index_pos, dir );
2112 
2113                         if ( Board::CanAttackUnitFromCell( *_currentUnit, from ) ) {
2114                             statusMsg = _( "Attack %{monster}" );
2115                             StringReplace( statusMsg, "%{monster}", b_enemy->GetName() );
2116 
2117                             return cursor;
2118                         }
2119                     }
2120                 }
2121             }
2122         }
2123         else if ( cell->isReachableForHead() || cell->isReachableForTail() ) {
2124             statusMsg = _currentUnit->isFlying() ? _( "Fly %{monster} here" ) : _( "Move %{monster} here" );
2125             StringReplace( statusMsg, "%{monster}", _currentUnit->GetName() );
2126             return _currentUnit->isFlying() ? Cursor::WAR_FLY : Cursor::WAR_MOVE;
2127         }
2128     }
2129 
2130     statusMsg = _( "Turn %{turn}" );
2131     StringReplace( statusMsg, "%{turn}", arena.GetCurrentTurn() );
2132 
2133     return Cursor::WAR_NONE;
2134 }
2135 
GetBattleSpellCursor(std::string & statusMsg) const2136 int Battle::Interface::GetBattleSpellCursor( std::string & statusMsg ) const
2137 {
2138     statusMsg.clear();
2139 
2140     const Cell * cell = Board::GetCell( index_pos );
2141     const Spell & spell = humanturn_spell;
2142 
2143     if ( cell && _currentUnit && spell.isValid() ) {
2144         const Unit * b_stats = cell->GetUnit();
2145 
2146         // over graveyard
2147         if ( !b_stats && arena.GraveyardAllowResurrect( index_pos, spell ) ) {
2148             b_stats = arena.GraveyardLastTroop( index_pos );
2149             if ( b_stats->isWide() ) { // we need to check tail and head positions
2150                 const Cell * tailCell = Board::GetCell( b_stats->GetTailIndex() );
2151                 const Cell * headCell = Board::GetCell( b_stats->GetHeadIndex() );
2152                 if ( !tailCell || tailCell->GetUnit() || !headCell || headCell->GetUnit() )
2153                     b_stats = nullptr;
2154             }
2155         }
2156 
2157         // teleport check first
2158         if ( Board::isValidIndex( teleport_src ) ) {
2159             const Unit * unitToTeleport = arena.GetTroopBoard( teleport_src );
2160 
2161             assert( unitToTeleport != nullptr );
2162 
2163             if ( !b_stats && cell->isPassable3( *unitToTeleport, false ) ) {
2164                 statusMsg = _( "Teleport here" );
2165                 return Cursor::SP_TELEPORT;
2166             }
2167 
2168             statusMsg = _( "Invalid teleport destination" );
2169             return Cursor::WAR_NONE;
2170         }
2171         else if ( b_stats && b_stats->AllowApplySpell( spell, _currentUnit->GetCurrentOrArmyCommander() ) ) {
2172             statusMsg = _( "Cast %{spell} on %{monster}" );
2173             StringReplace( statusMsg, "%{spell}", spell.GetName() );
2174             StringReplace( statusMsg, "%{monster}", b_stats->GetName() );
2175             return GetCursorFromSpell( spell.GetID() );
2176         }
2177         else if ( !spell.isApplyToFriends() && !spell.isApplyToEnemies() && !spell.isApplyToAnyTroops() ) {
2178             statusMsg = _( "Cast %{spell}" );
2179             StringReplace( statusMsg, "%{spell}", spell.GetName() );
2180             return GetCursorFromSpell( spell.GetID() );
2181         }
2182     }
2183 
2184     statusMsg = _( "Select spell target" );
2185 
2186     return Cursor::WAR_NONE;
2187 }
2188 
HumanTurn(const Unit & b,Actions & a)2189 void Battle::Interface::HumanTurn( const Unit & b, Actions & a )
2190 {
2191     Cursor & cursor = Cursor::Get();
2192     LocalEvent & le = LocalEvent::Get();
2193 
2194     cursor.SetThemes( Cursor::WAR_POINTER );
2195     _currentUnit = &b;
2196     humanturn_redraw = false;
2197     humanturn_exit = false;
2198     catapult_frame = 0;
2199 
2200     // in case we moved the window
2201     _interfacePosition = border.GetArea();
2202 
2203     Board & board = *Arena::GetBoard();
2204     board.Reset();
2205     board.SetScanPassability( b );
2206 
2207     popup.Reset();
2208 
2209     // safe position coord
2210     CursorPosition cursorPosition;
2211 
2212     Redraw();
2213 
2214     std::string msg;
2215     animation_flags_frame = 0;
2216 
2217     ResetIdleTroopAnimation();
2218 
2219     while ( !humanturn_exit && le.HandleEvents() ) {
2220         // move cursor
2221         int32_t indexNew = -1;
2222         if ( le.MouseCursor( fheroes2::Rect( _interfacePosition.x, _interfacePosition.y, _interfacePosition.width, _interfacePosition.height - status.height ) ) ) {
2223             indexNew = board.GetIndexAbsPosition( GetMouseCursor() );
2224         }
2225         if ( index_pos != indexNew ) {
2226             index_pos = indexNew;
2227             humanturn_redraw = true;
2228         }
2229 
2230         if ( humanturn_spell.isValid() )
2231             HumanCastSpellTurn( b, a, msg );
2232         else
2233             HumanBattleTurn( b, a, msg );
2234 
2235         if ( humanturn_exit )
2236             cursor.SetThemes( Cursor::WAIT );
2237 
2238         // update status
2239         if ( msg != status.GetMessage() ) {
2240             status.SetMessage( msg );
2241             humanturn_redraw = true;
2242         }
2243 
2244         // animation troops
2245         if ( IdleTroopsAnimation() )
2246             humanturn_redraw = true;
2247 
2248         CheckGlobalEvents( le );
2249 
2250         // redraw arena
2251         if ( humanturn_redraw ) {
2252             Redraw();
2253             humanturn_redraw = false;
2254         }
2255     }
2256 
2257     popup.Reset();
2258     _currentUnit = nullptr;
2259 }
2260 
HumanBattleTurn(const Unit & b,Actions & a,std::string & msg)2261 void Battle::Interface::HumanBattleTurn( const Unit & b, Actions & a, std::string & msg )
2262 {
2263     Cursor & cursor = Cursor::Get();
2264     LocalEvent & le = LocalEvent::Get();
2265     const Settings & conf = Settings::Get();
2266 
2267     if ( le.KeyPress() ) {
2268         // skip
2269         if ( Game::HotKeyPressEvent( Game::EVENT_BATTLE_HARDSKIP ) ) {
2270             a.push_back( Command( CommandType::MSG_BATTLE_SKIP, b.GetUID(), true ) );
2271             humanturn_exit = true;
2272         }
2273         else
2274             // soft skip
2275             if ( Game::HotKeyPressEvent( Game::EVENT_BATTLE_SOFTSKIP ) ) {
2276             a.push_back( Command( CommandType::MSG_BATTLE_SKIP, b.GetUID(), !conf.ExtBattleSoftWait() ) );
2277             humanturn_exit = true;
2278         }
2279         else
2280             // options
2281             if ( Game::HotKeyPressEvent( Game::EVENT_BATTLE_OPTIONS ) )
2282             EventShowOptions();
2283         else
2284             // auto switch
2285             if ( Game::HotKeyPressEvent( Game::EVENT_BATTLE_AUTOSWITCH ) )
2286             EventAutoSwitch( b, a );
2287         else
2288             // cast
2289             if ( Game::HotKeyPressEvent( Game::EVENT_BATTLE_CASTSPELL ) )
2290             ProcessingHeroDialogResult( 1, a );
2291         else
2292             // retreat
2293             if ( Game::HotKeyPressEvent( Game::EVENT_BATTLE_RETREAT ) )
2294             ProcessingHeroDialogResult( 2, a );
2295         else
2296             // surrender
2297             if ( Game::HotKeyPressEvent( Game::EVENT_BATTLE_SURRENDER ) )
2298             ProcessingHeroDialogResult( 3, a );
2299 
2300             // debug only
2301 #ifdef WITH_DEBUG
2302         if ( IS_DEVEL() )
2303             switch ( le.KeyValue() ) {
2304             case KEY_w:
2305                 // fast wins game
2306                 arena.GetResult().army1 = RESULT_WINS;
2307                 humanturn_exit = true;
2308                 a.push_back( Command( CommandType::MSG_BATTLE_END_TURN, b.GetUID() ) );
2309                 break;
2310 
2311             case KEY_l:
2312                 // fast loss game
2313                 arena.GetResult().army1 = RESULT_LOSS;
2314                 humanturn_exit = true;
2315                 a.push_back( Command( CommandType::MSG_BATTLE_END_TURN, b.GetUID() ) );
2316                 break;
2317 
2318             default:
2319                 break;
2320             }
2321 #endif
2322     }
2323 
2324     // Add offsets to inner objects
2325     const fheroes2::Rect mainTowerRect = main_tower + _interfacePosition.getPosition();
2326     const fheroes2::Rect armiesOrderRect = armies_order + _interfacePosition.getPosition();
2327     if ( Arena::GetTower( TWR_CENTER ) && le.MouseCursor( mainTowerRect ) ) {
2328         cursor.SetThemes( Cursor::WAR_INFO );
2329         msg = _( "View Ballista info" );
2330 
2331         if ( le.MouseClickLeft( mainTowerRect ) || le.MousePressRight( mainTowerRect ) ) {
2332             const Castle * cstl = Arena::GetCastle();
2333             std::string ballistaMessage = Tower::GetInfo( *cstl );
2334 
2335             if ( cstl->isBuild( BUILD_MOAT ) ) {
2336                 ballistaMessage.append( "\n \n" );
2337                 ballistaMessage.append( Battle::Board::GetMoatInfo() );
2338             }
2339 
2340             Dialog::Message( _( "Ballista" ), ballistaMessage, Font::BIG, le.MousePressRight() ? 0 : Dialog::OK );
2341         }
2342     }
2343     else if ( conf.ExtBattleShowBattleOrder() && le.MouseCursor( armiesOrderRect ) ) {
2344         cursor.SetThemes( Cursor::POINTER );
2345         armies_order.QueueEventProcessing( msg, _interfacePosition.getPosition() );
2346     }
2347     else if ( le.MouseCursor( btn_auto.area() ) ) {
2348         cursor.SetThemes( Cursor::WAR_POINTER );
2349         msg = _( "Enable auto combat" );
2350         ButtonAutoAction( b, a );
2351 
2352         if ( le.MousePressRight() ) {
2353             Dialog::Message( _( "Auto Combat" ), _( "Allows the computer to fight out the battle for you." ), Font::BIG );
2354         }
2355     }
2356     else if ( le.MouseCursor( btn_settings.area() ) ) {
2357         cursor.SetThemes( Cursor::WAR_POINTER );
2358         msg = _( "Customize system options" );
2359         ButtonSettingsAction();
2360 
2361         if ( le.MousePressRight() ) {
2362             Dialog::Message( _( "System Options" ), _( "Allows you to customize the combat screen." ), Font::BIG );
2363         }
2364     }
2365     else if ( conf.ExtBattleSoftWait() && le.MouseCursor( btn_wait.area() ) ) {
2366         cursor.SetThemes( Cursor::WAR_POINTER );
2367         msg = _( "Wait this unit" );
2368         ButtonWaitAction( a );
2369 
2370         if ( le.MousePressRight() ) {
2371             Dialog::Message( _( "Wait" ), _( "Waits the current creature. The current creature delays its turn until after all other creatures have had their turn." ),
2372                              Font::BIG );
2373         }
2374     }
2375     else if ( le.MouseCursor( btn_skip.area() ) ) {
2376         cursor.SetThemes( Cursor::WAR_POINTER );
2377         msg = _( "Skip this unit" );
2378         ButtonSkipAction( a );
2379 
2380         if ( le.MousePressRight() ) {
2381             Dialog::Message( _( "Skip" ), _( "Skips the current creature. The current creature ends its turn and does not get to go again until the next round." ),
2382                              Font::BIG );
2383         }
2384     }
2385     else if ( opponent1 && le.MouseCursor( opponent1->GetArea() + _interfacePosition.getPosition() ) ) {
2386         const fheroes2::Rect opponent1Area = opponent1->GetArea() + _interfacePosition.getPosition();
2387         if ( arena.GetCurrentColor() == arena.GetArmyColor1() ) {
2388             msg = _( "View Hero's options" );
2389             cursor.SetThemes( Cursor::WAR_HERO );
2390 
2391             if ( le.MouseClickLeft( opponent1Area ) ) {
2392                 ProcessingHeroDialogResult( arena.DialogBattleHero( *opponent1->GetHero(), true, status ), a );
2393                 humanturn_redraw = true;
2394             }
2395         }
2396         else {
2397             msg = _( "View opposing Hero" );
2398             cursor.SetThemes( Cursor::WAR_INFO );
2399 
2400             if ( le.MouseClickLeft( opponent1Area ) ) {
2401                 arena.DialogBattleHero( *opponent1->GetHero(), true, status );
2402                 humanturn_redraw = true;
2403             }
2404         }
2405 
2406         if ( le.MousePressRight( opponent1Area ) ) {
2407             arena.DialogBattleHero( *opponent1->GetHero(), false, status );
2408             humanturn_redraw = true;
2409         }
2410     }
2411     else if ( opponent2 && le.MouseCursor( opponent2->GetArea() + _interfacePosition.getPosition() ) ) {
2412         const fheroes2::Rect opponent2Area = opponent2->GetArea() + _interfacePosition.getPosition();
2413         if ( arena.GetCurrentColor() == arena.GetForce2().GetColor() ) {
2414             msg = _( "View Hero's options" );
2415             cursor.SetThemes( Cursor::WAR_HERO );
2416 
2417             if ( le.MouseClickLeft( opponent2Area ) ) {
2418                 ProcessingHeroDialogResult( arena.DialogBattleHero( *opponent2->GetHero(), true, status ), a );
2419                 humanturn_redraw = true;
2420             }
2421         }
2422         else {
2423             msg = _( "View opposing Hero" );
2424             cursor.SetThemes( Cursor::WAR_INFO );
2425 
2426             if ( le.MouseClickLeft( opponent2Area ) ) {
2427                 arena.DialogBattleHero( *opponent2->GetHero(), true, status );
2428                 humanturn_redraw = true;
2429             }
2430         }
2431 
2432         if ( le.MousePressRight( opponent2Area ) ) {
2433             arena.DialogBattleHero( *opponent2->GetHero(), false, status );
2434             humanturn_redraw = true;
2435         }
2436     }
2437     else if ( listlog && listlog->isOpenLog() && le.MouseCursor( listlog->GetArea() ) ) {
2438         cursor.SetThemes( Cursor::WAR_POINTER );
2439 
2440         listlog->QueueEventProcessing();
2441     }
2442     else if ( le.MouseCursor( fheroes2::Rect( _interfacePosition.x, _interfacePosition.y, _interfacePosition.width, _interfacePosition.height - status.height ) ) ) {
2443         const int themes = GetBattleCursor( msg );
2444 
2445         if ( cursor.Themes() != themes )
2446             cursor.SetThemes( themes );
2447 
2448         const Cell * cell = Board::GetCell( index_pos );
2449 
2450         if ( cell ) {
2451             if ( CursorAttack( themes ) ) {
2452                 const Unit * b_enemy = cell->GetUnit();
2453                 popup.SetInfo( cell, _currentUnit, b_enemy, _interfacePosition.getPosition() );
2454             }
2455             else
2456                 popup.Reset();
2457 
2458             if ( le.MouseClickLeft() )
2459                 MouseLeftClickBoardAction( themes, *cell, a );
2460             else if ( le.MousePressRight() )
2461                 MousePressRightBoardAction( themes, *cell );
2462         }
2463         else {
2464             le.MouseClickLeft();
2465             le.MousePressRight();
2466         }
2467     }
2468     else if ( le.MouseCursor( status ) ) {
2469         if ( listlog ) {
2470             msg = ( listlog->isOpenLog() ? _( "Hide logs" ) : _( "Show logs" ) );
2471             if ( le.MouseClickLeft( status ) )
2472                 listlog->SetOpenLog( !listlog->isOpenLog() );
2473         }
2474         cursor.SetThemes( Cursor::WAR_POINTER );
2475     }
2476     else {
2477         cursor.SetThemes( Cursor::WAR_NONE );
2478         le.MouseClickLeft();
2479         le.MousePressRight();
2480     }
2481 }
2482 
HumanCastSpellTurn(const Unit &,Actions & a,std::string & msg)2483 void Battle::Interface::HumanCastSpellTurn( const Unit & /*b*/, Actions & a, std::string & msg )
2484 {
2485     Cursor & cursor = Cursor::Get();
2486     LocalEvent & le = LocalEvent::Get();
2487 
2488     // reset cast
2489     if ( le.MousePressRight() || Game::HotKeyPressEvent( Game::EVENT_DEFAULT_EXIT ) ) {
2490         humanturn_spell = Spell::NONE;
2491         teleport_src = -1;
2492     }
2493     else if ( le.MouseCursor( _interfacePosition ) && humanturn_spell.isValid() ) {
2494         const int themes = GetBattleSpellCursor( msg );
2495 
2496         if ( cursor.Themes() != themes )
2497             cursor.SetThemes( themes );
2498 
2499         if ( le.MouseClickLeft() && Cursor::WAR_NONE != cursor.Themes() ) {
2500             if ( !Board::isValidIndex( index_pos ) ) {
2501                 DEBUG_LOG( DBG_BATTLE, DBG_WARN,
2502                            "dst: "
2503                                << "out of range" );
2504                 return;
2505             }
2506 
2507             if ( listlog ) {
2508                 std::string str = _( "%{color} casts a spell: %{spell}" );
2509                 const HeroBase * current_commander = arena.GetCurrentCommander();
2510                 if ( current_commander )
2511                     StringReplace( str, "%{color}", Color::String( current_commander->GetColor() ) );
2512                 StringReplace( str, "%{spell}", humanturn_spell.GetName() );
2513                 listlog->AddMessage( str );
2514             }
2515 
2516             DEBUG_LOG( DBG_BATTLE, DBG_TRACE, humanturn_spell.GetName() << ", dst: " << index_pos );
2517 
2518             if ( Cursor::SP_TELEPORT == cursor.Themes() ) {
2519                 if ( 0 > teleport_src )
2520                     teleport_src = index_pos;
2521                 else {
2522                     a.push_back( Command( CommandType::MSG_BATTLE_CAST, Spell::TELEPORT, teleport_src, index_pos ) );
2523                     humanturn_spell = Spell::NONE;
2524                     humanturn_exit = true;
2525                     teleport_src = -1;
2526                 }
2527             }
2528             else if ( Cursor::SP_MIRRORIMAGE == cursor.Themes() ) {
2529                 a.push_back( Command( CommandType::MSG_BATTLE_CAST, Spell::MIRRORIMAGE, index_pos ) );
2530                 humanturn_spell = Spell::NONE;
2531                 humanturn_exit = true;
2532             }
2533             else {
2534                 a.push_back( Command( CommandType::MSG_BATTLE_CAST, humanturn_spell.GetID(), index_pos ) );
2535                 humanturn_spell = Spell::NONE;
2536                 humanturn_exit = true;
2537             }
2538         }
2539     }
2540     else {
2541         cursor.SetThemes( Cursor::WAR_NONE );
2542     }
2543 }
2544 
FadeArena(bool clearMessageLog)2545 void Battle::Interface::FadeArena( bool clearMessageLog )
2546 {
2547     AGG::ResetMixer();
2548 
2549     if ( clearMessageLog ) {
2550         status.clear();
2551         status.Redraw();
2552     }
2553 
2554     Redraw();
2555 
2556     const fheroes2::Rect srt = border.GetArea();
2557     fheroes2::Image top( srt.width, srt.height );
2558     fheroes2::Display & display = fheroes2::Display::instance();
2559 
2560     fheroes2::Copy( display, srt.x, srt.y, top, 0, 0, srt.width, srt.height );
2561     fheroes2::FadeDisplayWithPalette( top, srt.getPosition(), 5, 300, 5 );
2562 
2563     display.render();
2564 }
2565 
GetIndexIndicator(const Unit & b)2566 int Battle::GetIndexIndicator( const Unit & b )
2567 {
2568     // yellow
2569     if ( b.Modes( IS_GREEN_STATUS ) && b.Modes( IS_RED_STATUS ) )
2570         return 13;
2571     else
2572         // green
2573         if ( b.Modes( IS_GREEN_STATUS ) )
2574         return 12;
2575     else
2576         // red
2577         if ( b.Modes( IS_RED_STATUS ) )
2578         return 14;
2579 
2580     return 10;
2581 }
2582 
EventShowOptions(void)2583 void Battle::Interface::EventShowOptions( void )
2584 {
2585     btn_settings.drawOnPress();
2586     DialogBattleSettings();
2587     btn_settings.drawOnRelease();
2588     humanturn_redraw = true;
2589 }
2590 
EventAutoSwitch(const Unit & b,Actions & a)2591 void Battle::Interface::EventAutoSwitch( const Unit & b, Actions & a )
2592 {
2593     btn_auto.drawOnPress();
2594 
2595     a.push_back( Command( CommandType::MSG_BATTLE_AUTO, b.GetColor() ) );
2596 
2597     Cursor::Get().SetThemes( Cursor::WAIT );
2598     humanturn_redraw = true;
2599     humanturn_exit = true;
2600 
2601     btn_auto.drawOnRelease();
2602 }
2603 
ButtonAutoAction(const Unit & b,Actions & a)2604 void Battle::Interface::ButtonAutoAction( const Unit & b, Actions & a )
2605 {
2606     LocalEvent & le = LocalEvent::Get();
2607 
2608     le.MousePressLeft( btn_auto.area() ) ? btn_auto.drawOnPress() : btn_auto.drawOnRelease();
2609 
2610     if ( le.MouseClickLeft( btn_auto.area() ) )
2611         EventAutoSwitch( b, a );
2612 }
2613 
ButtonSettingsAction(void)2614 void Battle::Interface::ButtonSettingsAction( void )
2615 {
2616     LocalEvent & le = LocalEvent::Get();
2617 
2618     le.MousePressLeft( btn_settings.area() ) ? btn_settings.drawOnPress() : btn_settings.drawOnRelease();
2619 
2620     if ( le.MouseClickLeft( btn_settings.area() ) ) {
2621         DialogBattleSettings();
2622         humanturn_redraw = true;
2623     }
2624 }
2625 
ButtonWaitAction(Actions & a)2626 void Battle::Interface::ButtonWaitAction( Actions & a )
2627 {
2628     LocalEvent & le = LocalEvent::Get();
2629 
2630     le.MousePressLeft( btn_wait.area() ) ? btn_wait.drawOnPress() : btn_wait.drawOnRelease();
2631 
2632     if ( le.MouseClickLeft( btn_wait.area() ) && _currentUnit ) {
2633         a.push_back( Command( CommandType::MSG_BATTLE_SKIP, _currentUnit->GetUID(), false ) );
2634         humanturn_exit = true;
2635     }
2636 }
2637 
ButtonSkipAction(Actions & a)2638 void Battle::Interface::ButtonSkipAction( Actions & a )
2639 {
2640     LocalEvent & le = LocalEvent::Get();
2641 
2642     le.MousePressLeft( btn_skip.area() ) ? btn_skip.drawOnPress() : btn_skip.drawOnRelease();
2643 
2644     if ( le.MouseClickLeft( btn_skip.area() ) && _currentUnit ) {
2645         a.push_back( Command( CommandType::MSG_BATTLE_SKIP, _currentUnit->GetUID(), true ) );
2646         humanturn_exit = true;
2647     }
2648 }
2649 
MousePressRightBoardAction(u32,const Cell & cell) const2650 void Battle::Interface::MousePressRightBoardAction( u32 /*themes*/, const Cell & cell ) const
2651 {
2652     const Unit * b = cell.GetUnit();
2653 
2654     if ( b ) {
2655         Dialog::ArmyInfo( *b, Dialog::READONLY, b->isReflect() );
2656     }
2657 }
2658 
MouseLeftClickBoardAction(u32 themes,const Cell & cell,Actions & a)2659 void Battle::Interface::MouseLeftClickBoardAction( u32 themes, const Cell & cell, Actions & a )
2660 {
2661     const int32_t index = cell.GetIndex();
2662     const Unit * b = cell.GetUnit();
2663 
2664     if ( _currentUnit ) {
2665         switch ( themes ) {
2666         case Cursor::WAR_FLY:
2667         case Cursor::WAR_MOVE:
2668             a.push_back( Command( CommandType::MSG_BATTLE_MOVE, _currentUnit->GetUID(), Board::FixupDestinationCell( *_currentUnit, index ) ) );
2669             a.push_back( Command( CommandType::MSG_BATTLE_END_TURN, _currentUnit->GetUID() ) );
2670             humanturn_exit = true;
2671             break;
2672 
2673         case Cursor::SWORD_TOPLEFT:
2674         case Cursor::SWORD_TOPRIGHT:
2675         case Cursor::SWORD_RIGHT:
2676         case Cursor::SWORD_BOTTOMRIGHT:
2677         case Cursor::SWORD_BOTTOMLEFT:
2678         case Cursor::SWORD_LEFT: {
2679             const Unit * enemy = b;
2680             const int dir = GetDirectionFromCursorSword( themes );
2681 
2682             if ( enemy && Board::isValidDirection( index, dir ) ) {
2683                 const int32_t move = Board::FixupDestinationCell( *_currentUnit, Board::GetIndexDirection( index, dir ) );
2684 
2685                 if ( _currentUnit->GetHeadIndex() != move ) {
2686                     a.push_back( Command( CommandType::MSG_BATTLE_MOVE, _currentUnit->GetUID(), move ) );
2687                 }
2688                 a.push_back( Command( CommandType::MSG_BATTLE_ATTACK, _currentUnit->GetUID(), enemy->GetUID(), index, Board::GetReflectDirection( dir ) ) );
2689                 a.push_back( Command( CommandType::MSG_BATTLE_END_TURN, _currentUnit->GetUID() ) );
2690                 humanturn_exit = true;
2691             }
2692             break;
2693         }
2694 
2695         case Cursor::WAR_BROKENARROW:
2696         case Cursor::WAR_ARROW: {
2697             const Unit * enemy = b;
2698 
2699             if ( enemy ) {
2700                 a.push_back( Command( CommandType::MSG_BATTLE_ATTACK, _currentUnit->GetUID(), enemy->GetUID(), index, 0 ) );
2701                 a.push_back( Command( CommandType::MSG_BATTLE_END_TURN, _currentUnit->GetUID() ) );
2702                 humanturn_exit = true;
2703             }
2704             break;
2705         }
2706 
2707         case Cursor::WAR_INFO: {
2708             if ( b ) {
2709                 Dialog::ArmyInfo( *b, Dialog::BUTTONS | Dialog::READONLY, b->isReflect() );
2710                 humanturn_redraw = true;
2711             }
2712             break;
2713         }
2714 
2715         default:
2716             break;
2717         }
2718     }
2719 }
2720 
AnimateUnitWithDelay(Unit & unit,uint32_t delay)2721 void Battle::Interface::AnimateUnitWithDelay( Unit & unit, uint32_t delay )
2722 {
2723     if ( unit.isFinishAnimFrame() ) // nothing to animate
2724         return;
2725 
2726     LocalEvent & le = LocalEvent::Get();
2727     const uint64_t frameDelay = ( unit.animation.animationLength() > 0 ) ? delay / unit.animation.animationLength() : 0;
2728 
2729     while ( le.HandleEvents( false ) ) {
2730         CheckGlobalEvents( le );
2731 
2732         if ( Game::validateCustomAnimationDelay( frameDelay ) ) {
2733             Redraw();
2734             if ( unit.isFinishAnimFrame() )
2735                 break;
2736             unit.IncreaseAnimFrame();
2737         }
2738     }
2739 }
2740 
AnimateOpponents(OpponentSprite * target)2741 void Battle::Interface::AnimateOpponents( OpponentSprite * target )
2742 {
2743     if ( target == nullptr ) // nothing to animate
2744         return;
2745 
2746     LocalEvent & le = LocalEvent::Get();
2747     while ( le.HandleEvents() && !target->isFinishFrame() ) {
2748         if ( Game::validateAnimationDelay( Game::BATTLE_OPPONENTS_DELAY ) ) {
2749             target->IncreaseAnimFrame();
2750             Redraw();
2751         }
2752     }
2753 }
2754 
RedrawTroopDefaultDelay(Unit & unit)2755 void Battle::Interface::RedrawTroopDefaultDelay( Unit & unit )
2756 {
2757     if ( unit.isFinishAnimFrame() ) // nothing to animate
2758         return;
2759 
2760     LocalEvent & le = LocalEvent::Get();
2761 
2762     while ( le.HandleEvents( false ) ) {
2763         CheckGlobalEvents( le );
2764 
2765         if ( Game::validateAnimationDelay( Game::BATTLE_FRAME_DELAY ) ) {
2766             Redraw();
2767             if ( unit.isFinishAnimFrame() )
2768                 break;
2769             unit.IncreaseAnimFrame();
2770         }
2771     }
2772 }
2773 
RedrawActionSkipStatus(const Unit & attacker)2774 void Battle::Interface::RedrawActionSkipStatus( const Unit & attacker )
2775 {
2776     std::string msg;
2777     if ( attacker.Modes( TR_HARDSKIP ) ) {
2778         msg = _( "%{name} skip their turn." );
2779     }
2780     else {
2781         msg = _( "%{name} wait their turn." );
2782     }
2783 
2784     StringReplace( msg, "%{name}", attacker.GetName() );
2785     status.SetMessage( msg, true );
2786 }
2787 
RedrawMissileAnimation(const fheroes2::Point & startPos,const fheroes2::Point & endPos,double angle,uint32_t monsterID)2788 void Battle::Interface::RedrawMissileAnimation( const fheroes2::Point & startPos, const fheroes2::Point & endPos, double angle, uint32_t monsterID )
2789 {
2790     LocalEvent & le = LocalEvent::Get();
2791     fheroes2::Sprite missile;
2792 
2793     const bool reverse = startPos.x > endPos.x;
2794     const bool isMage = ( monsterID == Monster::MAGE || monsterID == Monster::ARCHMAGE );
2795 
2796     // Mage is channeling the bolt; doesn't have missile sprite
2797     if ( isMage )
2798         fheroes2::delayforMs( Game::ApplyBattleSpeed( 115 ) );
2799     else
2800         missile = fheroes2::AGG::GetICN( Monster::GetMissileICN( monsterID ), static_cast<uint32_t>( Bin_Info::GetMonsterInfo( monsterID ).getProjectileID( angle ) ) );
2801 
2802     // Lich/Power lich has projectile speed of 25
2803     const std::vector<fheroes2::Point> points = GetEuclideanLine( startPos, endPos, isMage ? 50 : std::max( missile.width(), 25 ) );
2804     std::vector<fheroes2::Point>::const_iterator pnt = points.begin();
2805 
2806     // convert the following code into a function/event service
2807     while ( le.HandleEvents( false ) && pnt != points.end() ) {
2808         CheckGlobalEvents( le );
2809 
2810         if ( Game::validateAnimationDelay( Game::BATTLE_MISSILE_DELAY ) ) {
2811             RedrawPartialStart();
2812             if ( isMage ) {
2813                 fheroes2::DrawLine( _mainSurface, fheroes2::Point( startPos.x, startPos.y - 2 ), fheroes2::Point( pnt->x, pnt->y - 2 ), 0x77 );
2814                 fheroes2::DrawLine( _mainSurface, fheroes2::Point( startPos.x, startPos.y - 1 ), fheroes2::Point( pnt->x, pnt->y - 1 ), 0xB5 );
2815                 fheroes2::DrawLine( _mainSurface, startPos, *pnt, 0xBC );
2816                 fheroes2::DrawLine( _mainSurface, fheroes2::Point( startPos.x, startPos.y + 1 ), fheroes2::Point( pnt->x, pnt->y + 1 ), 0xB5 );
2817                 fheroes2::DrawLine( _mainSurface, fheroes2::Point( startPos.x, startPos.y + 2 ), fheroes2::Point( pnt->x, pnt->y + 2 ), 0x77 );
2818             }
2819             else {
2820                 fheroes2::Blit( missile, _mainSurface, reverse ? pnt->x - missile.width() : pnt->x, ( angle > 0 ) ? pnt->y - missile.height() : pnt->y, reverse );
2821             }
2822             RedrawPartialFinish();
2823             ++pnt;
2824         }
2825     }
2826 }
2827 
RedrawActionNewTurn() const2828 void Battle::Interface::RedrawActionNewTurn() const
2829 {
2830     if ( !Music::isPlaying() ) {
2831         AGG::PlayMusic( MUS::GetBattleRandom(), true, true );
2832     }
2833 
2834     if ( listlog == nullptr ) {
2835         return;
2836     }
2837 
2838     std::string msg = _( "Turn %{turn}" );
2839     StringReplace( msg, "%{turn}", arena.GetCurrentTurn() );
2840 
2841     listlog->AddMessage( msg );
2842 }
2843 
RedrawActionAttackPart1(Unit & attacker,Unit & defender,const TargetsInfo & targets)2844 void Battle::Interface::RedrawActionAttackPart1( Unit & attacker, Unit & defender, const TargetsInfo & targets )
2845 {
2846     Cursor::Get().SetThemes( Cursor::WAR_POINTER );
2847 
2848     _currentUnit = nullptr;
2849     _movingUnit = &attacker;
2850     _movingPos = attacker.GetRectPosition().getPosition();
2851 
2852     // Unit 'Position' is position of the tile he's standing at
2853     const fheroes2::Rect & pos1 = attacker.GetRectPosition();
2854     const fheroes2::Rect & pos2 = defender.GetRectPosition();
2855 
2856     const bool archer = attacker.isArchers() && !attacker.isHandFighting();
2857     const bool isDoubleCell = attacker.isDoubleCellAttack() && 2 == targets.size();
2858 
2859     // redraw luck animation
2860     if ( attacker.Modes( LUCK_GOOD | LUCK_BAD ) )
2861         RedrawActionLuck( attacker );
2862 
2863     AGG::PlaySound( attacker.M82Attk() );
2864 
2865     // long distance attack animation
2866     if ( archer ) {
2867         const fheroes2::Sprite & attackerSprite = fheroes2::AGG::GetICN( attacker.GetMonsterSprite(), attacker.GetFrame() );
2868         const fheroes2::Point attackerPos = GetTroopPosition( attacker, attackerSprite );
2869 
2870         // For shooter position we need bottom center position of rear tile
2871         // Use cell coordinates for X because sprite width is very inconsistent (e.g. halfling)
2872         const int rearCenterX = ( attacker.isWide() && attacker.isReflect() ) ? pos1.width * 3 / 4 : CELLW / 2;
2873         const fheroes2::Point shooterPos( pos1.x + rearCenterX, attackerPos.y - attackerSprite.y() );
2874 
2875         // Use the front one to calculate the angle, then overwrite
2876         fheroes2::Point offset = attacker.GetStartMissileOffset( Monster_Info::FRONT );
2877 
2878         const fheroes2::Point targetPos = defender.GetCenterPoint();
2879 
2880         double angle = GetAngle( fheroes2::Point( shooterPos.x + offset.x, shooterPos.y + offset.y ), targetPos );
2881         // This check is made only for situations when a target stands on the same row as shooter.
2882         if ( attacker.GetHeadIndex() / ARENAW == defender.GetHeadIndex() / ARENAW ) {
2883             angle = 0;
2884         }
2885 
2886         // Angles are used in Heroes2 as 90 (TOP) -> 0 (FRONT) -> -90 (BOT) degrees
2887         const int direction = angle >= 25.0 ? Monster_Info::TOP : ( angle <= -25.0 ) ? Monster_Info::BOTTOM : Monster_Info::FRONT;
2888 
2889         if ( direction != Monster_Info::FRONT )
2890             offset = attacker.GetStartMissileOffset( direction );
2891 
2892         // redraw archer attack animation
2893         if ( attacker.SwitchAnimation( Monster_Info::RANG_TOP + direction * 2 ) ) {
2894             AnimateUnitWithDelay( attacker, Game::ApplyBattleSpeed( attacker.animation.getShootingSpeed() ) );
2895         }
2896 
2897         const fheroes2::Point missileStart = fheroes2::Point( shooterPos.x + ( attacker.isReflect() ? -offset.x : offset.x ), shooterPos.y + offset.y );
2898 
2899         // draw missile animation
2900         RedrawMissileAnimation( missileStart, targetPos, angle, attacker.GetID() );
2901     }
2902     else {
2903         int attackAnim = isDoubleCell ? Monster_Info::RANG_FRONT : Monster_Info::MELEE_FRONT;
2904         if ( pos2.y < pos1.y ) {
2905             attackAnim -= 2;
2906         }
2907         else if ( pos2.y > pos1.y ) {
2908             attackAnim += 2;
2909         }
2910 
2911         // redraw melee attack animation
2912         if ( attacker.SwitchAnimation( attackAnim ) ) {
2913             RedrawTroopDefaultDelay( attacker );
2914         }
2915     }
2916 
2917     if ( attacker.isAbilityPresent( fheroes2::MonsterAbilityType::AREA_SHOT ) && archer ) {
2918         // Lich cloud animation.
2919         RedrawTroopWithFrameAnimation( defender, ICN::LICHCLOD, attacker.M82Expl(), NONE );
2920     }
2921 }
2922 
RedrawActionAttackPart2(Unit & attacker,TargetsInfo & targets)2923 void Battle::Interface::RedrawActionAttackPart2( Unit & attacker, TargetsInfo & targets )
2924 {
2925     // post attack animation
2926     int attackStart = attacker.animation.getCurrentState();
2927     if ( attackStart >= Monster_Info::MELEE_TOP && attackStart <= Monster_Info::RANG_BOT ) {
2928         ++attackStart;
2929         attacker.SwitchAnimation( attackStart );
2930     }
2931 
2932     // targets damage animation
2933     RedrawActionWincesKills( targets, &attacker );
2934     RedrawTroopDefaultDelay( attacker );
2935 
2936     attacker.SwitchAnimation( Monster_Info::STATIC );
2937 
2938     const bool isMirror = targets.size() == 1 && targets.front().defender->isModes( CAP_MIRRORIMAGE );
2939     // draw status for first defender
2940     if ( !isMirror && !targets.empty() ) {
2941         std::string msg = _( "%{attacker} do %{damage} damage." );
2942         StringReplace( msg, "%{attacker}", attacker.GetName() );
2943 
2944         if ( 1 < targets.size() ) {
2945             u32 killed = 0;
2946             u32 damage = 0;
2947 
2948             for ( TargetsInfo::const_iterator it = targets.begin(); it != targets.end(); ++it ) {
2949                 if ( !it->defender->isModes( CAP_MIRRORIMAGE ) ) {
2950                     killed += ( *it ).killed;
2951                     damage += ( *it ).damage;
2952                 }
2953             }
2954 
2955             StringReplace( msg, "%{damage}", damage );
2956 
2957             if ( killed ) {
2958                 msg.append( " " );
2959                 msg.append( _n( "1 creature perishes.", "%{count} creatures perish.", killed ) );
2960                 StringReplace( msg, "%{count}", killed );
2961             }
2962         }
2963         else {
2964             const TargetInfo & target = targets.front();
2965             StringReplace( msg, "%{damage}", target.damage );
2966 
2967             if ( target.killed ) {
2968                 msg.append( " " );
2969                 msg.append( _n( "1 %{defender} perishes.", "%{count} %{defender} perish.", target.killed ) );
2970                 StringReplace( msg, "%{count}", target.killed );
2971                 StringReplace( msg, "%{defender}", target.defender->GetPluralName( target.killed ) );
2972             }
2973         }
2974 
2975         status.SetMessage( msg, true );
2976         status.SetMessage( "", false );
2977     }
2978 
2979     _movingUnit = nullptr;
2980 }
2981 
RedrawActionWincesKills(TargetsInfo & targets,Unit * attacker)2982 void Battle::Interface::RedrawActionWincesKills( TargetsInfo & targets, Unit * attacker )
2983 {
2984     LocalEvent & le = LocalEvent::Get();
2985 
2986     // targets damage animation
2987     int finish = 0;
2988     int deathColor = Color::UNUSED;
2989 
2990     std::vector<Unit *> mirrorImages;
2991     std::set<Unit *> resistantTarget;
2992 
2993     for ( TargetsInfo::iterator it = targets.begin(); it != targets.end(); ++it ) {
2994         Unit * defender = it->defender;
2995         if ( defender == nullptr ) {
2996             continue;
2997         }
2998 
2999         if ( defender->isModes( CAP_MIRRORIMAGE ) )
3000             mirrorImages.push_back( defender );
3001 
3002         // kill animation
3003         if ( !defender->isValid() ) {
3004             // destroy linked mirror
3005             if ( defender->isModes( CAP_MIRROROWNER ) )
3006                 mirrorImages.push_back( defender->GetMirror() );
3007 
3008             defender->SwitchAnimation( Monster_Info::KILL );
3009             AGG::PlaySound( defender->M82Kill() );
3010             ++finish;
3011 
3012             deathColor = defender->GetArmyColor();
3013         }
3014         else if ( it->damage ) {
3015             // wince animation
3016             defender->SwitchAnimation( Monster_Info::WNCE );
3017             AGG::PlaySound( defender->M82Wnce() );
3018             ++finish;
3019         }
3020         else {
3021             // have immunity
3022             resistantTarget.insert( it->defender );
3023             AGG::PlaySound( M82::RSBRYFZL );
3024         }
3025     }
3026 
3027     if ( deathColor != Color::UNUSED ) {
3028         const bool attackersTurn = deathColor == arena.GetArmyColor2();
3029         OpponentSprite * attackingHero = attackersTurn ? opponent1 : opponent2;
3030         OpponentSprite * defendingHero = attackersTurn ? opponent2 : opponent1;
3031         // 60% of joyful animation
3032         if ( attackingHero && Rand::Get( 1, 5 ) < 4 ) {
3033             attackingHero->SetAnimation( OP_JOY );
3034         }
3035         // 80% of sorrow animation otherwise
3036         else if ( defendingHero && Rand::Get( 1, 5 ) < 5 ) {
3037             defendingHero->SetAnimation( OP_SORROW );
3038         }
3039     }
3040 
3041     // targets damage animation loop
3042     bool finishedAnimation = false;
3043     while ( le.HandleEvents() && !finishedAnimation ) {
3044         CheckGlobalEvents( le );
3045 
3046         if ( Game::validateAnimationDelay( Game::BATTLE_FRAME_DELAY ) ) {
3047             bool redrawBattleField = false;
3048 
3049             if ( attacker != nullptr ) {
3050                 if ( attacker->isFinishAnimFrame() ) {
3051                     attacker->SwitchAnimation( Monster_Info::STATIC );
3052                 }
3053                 else {
3054                     attacker->IncreaseAnimFrame();
3055                 }
3056 
3057                 redrawBattleField = true;
3058             }
3059             else {
3060                 for ( TargetsInfo::iterator it = targets.begin(); it != targets.end(); ++it ) {
3061                     if ( ( *it ).defender ) {
3062                         redrawBattleField = true;
3063                         break;
3064                     }
3065                 }
3066             }
3067 
3068             if ( redrawBattleField ) {
3069                 RedrawPartialStart();
3070                 RedrawPartialFinish();
3071             }
3072 
3073             const int finishedAnimationCount = std::count_if( targets.begin(), targets.end(), [&resistantTarget]( const TargetInfo & info ) {
3074                 if ( info.defender == nullptr ) {
3075                     return false;
3076                 }
3077 
3078                 if ( resistantTarget.count( info.defender ) > 0 ) {
3079                     return false;
3080                 }
3081 
3082                 const int animationState = info.defender->GetAnimationState();
3083                 if ( animationState != Monster_Info::WNCE && animationState != Monster_Info::KILL ) {
3084                     return true;
3085                 }
3086 
3087                 return TargetInfo::isFinishAnimFrame( info );
3088             } );
3089 
3090             finishedAnimation = ( finish == finishedAnimationCount );
3091 
3092             for ( TargetsInfo::iterator it = targets.begin(); it != targets.end(); ++it ) {
3093                 if ( ( *it ).defender ) {
3094                     if ( it->defender->isFinishAnimFrame() && it->defender->GetAnimationState() == Monster_Info::WNCE ) {
3095                         it->defender->SwitchAnimation( Monster_Info::STATIC );
3096                     }
3097                     else {
3098                         it->defender->IncreaseAnimFrame();
3099                     }
3100                 }
3101             }
3102         }
3103     }
3104 
3105     // Fade away animation for destroyed mirror images
3106     if ( !mirrorImages.empty() )
3107         RedrawActionRemoveMirrorImage( mirrorImages );
3108 }
3109 
RedrawActionMove(Unit & unit,const Indexes & path)3110 void Battle::Interface::RedrawActionMove( Unit & unit, const Indexes & path )
3111 {
3112     Indexes::const_iterator dst = path.begin();
3113     Bridge * bridge = Arena::GetBridge();
3114 
3115     uint32_t frameDelay = Game::ApplyBattleSpeed( unit.animation.getMoveSpeed() );
3116     if ( unit.Modes( SP_HASTE ) ) {
3117         frameDelay = frameDelay * 65 / 100; // by 35% faster
3118     }
3119     else if ( unit.Modes( SP_SLOW ) ) {
3120         frameDelay = frameDelay * 150 / 100; // by 50% slower
3121     }
3122 
3123     Cursor::Get().SetThemes( Cursor::WAR_POINTER );
3124 
3125 #ifdef DEBUG_LOG
3126     std::string msg = _( "Moved %{monster}: %{src}, %{dst}" );
3127     StringReplace( msg, "%{monster}", unit.GetName() );
3128     StringReplace( msg, "%{src}", unit.GetHeadIndex() );
3129 #else
3130     std::string msg = _( "Moved %{monster}" );
3131     StringReplace( msg, "%{monster}", unit.GetName() );
3132 #endif
3133 
3134     _currentUnit = nullptr;
3135     _movingUnit = &unit;
3136 
3137     while ( dst != path.end() ) {
3138         const Cell * cell = Board::GetCell( *dst );
3139         _movingPos = cell->GetPos().getPosition();
3140         bool show_anim = false;
3141 
3142         if ( bridge && bridge->NeedDown( unit, *dst ) ) {
3143             _movingUnit = nullptr;
3144             unit.SwitchAnimation( Monster_Info::STATIC );
3145             bridge->Action( unit, *dst );
3146             _movingUnit = &unit;
3147         }
3148 
3149         if ( unit.isWide() ) {
3150             if ( unit.GetTailIndex() == *dst )
3151                 unit.SetReflection( !unit.isReflect() );
3152             else
3153                 show_anim = true;
3154         }
3155         else {
3156             unit.UpdateDirection( cell->GetPos() );
3157             show_anim = true;
3158         }
3159 
3160         if ( show_anim ) {
3161             AGG::PlaySound( unit.M82Move() );
3162             unit.SwitchAnimation( Monster_Info::MOVING );
3163             AnimateUnitWithDelay( unit, frameDelay );
3164             unit.SetPosition( *dst );
3165         }
3166 
3167         // check for possible bridge close action, after unit's end of movement
3168         if ( bridge && bridge->AllowUp() ) {
3169             _movingUnit = nullptr;
3170             unit.SwitchAnimation( Monster_Info::STATIC );
3171             bridge->Action( unit, *dst );
3172             _movingUnit = &unit;
3173         }
3174 
3175         ++dst;
3176     }
3177 
3178     // restore
3179     _flyingUnit = nullptr;
3180     _movingUnit = nullptr;
3181     _currentUnit = nullptr;
3182     unit.SwitchAnimation( Monster_Info::STATIC );
3183 
3184 #ifdef DEBUG_LOG
3185     StringReplace( msg, "%{dst}", unit.GetHeadIndex() );
3186 #endif
3187     status.SetMessage( msg, true );
3188 }
3189 
RedrawActionFly(Unit & unit,const Position & pos)3190 void Battle::Interface::RedrawActionFly( Unit & unit, const Position & pos )
3191 {
3192     const int32_t destIndex = pos.GetHead()->GetIndex();
3193     const int32_t destTailIndex = unit.isWide() ? pos.GetTail()->GetIndex() : -1;
3194 
3195     // check if we're already there
3196     if ( unit.GetPosition().contains( destIndex ) )
3197         return;
3198 
3199     const fheroes2::Rect & pos1 = unit.GetRectPosition();
3200     const fheroes2::Rect & pos2 = Board::GetCell( destIndex )->GetPos();
3201 
3202     fheroes2::Point destPos( pos1.x, pos1.y );
3203     fheroes2::Point targetPos( pos2.x, pos2.y );
3204 
3205     if ( unit.isWide() && targetPos.x > destPos.x ) {
3206         targetPos.x -= CELLW; // this is needed to avoid extra cell shifting upon landing when we move to right side
3207     }
3208 
3209     std::string msg = _( "Moved %{monster}: %{src}, %{dst}" );
3210     StringReplace( msg, "%{monster}", unit.GetName() );
3211     StringReplace( msg, "%{src}", unit.GetHeadIndex() );
3212 
3213     Cursor::Get().SetThemes( Cursor::WAR_POINTER );
3214 
3215     const uint32_t step = unit.animation.getFlightSpeed();
3216     uint32_t frameDelay = Game::ApplyBattleSpeed( unit.animation.getMoveSpeed() );
3217     if ( unit.Modes( SP_HASTE ) ) {
3218         frameDelay = frameDelay * 8 / 10; // 20% faster
3219     }
3220     else if ( unit.Modes( SP_SLOW ) ) {
3221         frameDelay = frameDelay * 12 / 10; // 20% slower
3222     }
3223 
3224     const std::vector<fheroes2::Point> points = GetEuclideanLine( destPos, targetPos, step );
3225     std::vector<fheroes2::Point>::const_iterator currentPoint = points.begin();
3226 
3227     // cleanup
3228     _currentUnit = nullptr;
3229     _movingUnit = nullptr;
3230     _flyingUnit = nullptr;
3231 
3232     Bridge * bridge = Arena::GetBridge();
3233 
3234     // open the bridge if the unit should land on it
3235     if ( bridge ) {
3236         if ( bridge->NeedDown( unit, destIndex ) ) {
3237             bridge->Action( unit, destIndex );
3238         }
3239         else if ( unit.isWide() && bridge->NeedDown( unit, destTailIndex ) ) {
3240             bridge->Action( unit, destTailIndex );
3241         }
3242     }
3243 
3244     // jump up
3245     _flyingUnit = nullptr;
3246     _movingUnit = &unit;
3247     _movingPos = currentPoint != points.end() ? *currentPoint : destPos;
3248     _flyingPos = destPos;
3249 
3250     unit.SwitchAnimation( Monster_Info::FLY_UP );
3251     // Take off animation is 30% length on average (original value)
3252     AnimateUnitWithDelay( unit, frameDelay * 3 / 10 );
3253 
3254     _movingUnit = nullptr;
3255     _flyingUnit = &unit;
3256     _flyingPos = _movingPos;
3257 
3258     if ( currentPoint != points.end() )
3259         ++currentPoint;
3260 
3261     unit.SwitchAnimation( Monster_Info::MOVING );
3262     while ( currentPoint != points.end() ) {
3263         _movingPos = *currentPoint;
3264 
3265         AGG::PlaySound( unit.M82Move() );
3266         unit.animation.restartAnimation();
3267         AnimateUnitWithDelay( unit, frameDelay );
3268 
3269         _flyingPos = _movingPos;
3270         ++currentPoint;
3271     }
3272 
3273     unit.SetPosition( destIndex );
3274 
3275     // landing
3276     _flyingUnit = nullptr;
3277     _movingUnit = &unit;
3278     _movingPos = targetPos;
3279 
3280     std::vector<int> landAnim;
3281     landAnim.push_back( Monster_Info::FLY_LAND );
3282     landAnim.push_back( Monster_Info::STATIC );
3283     unit.SwitchAnimation( landAnim );
3284     AnimateUnitWithDelay( unit, frameDelay );
3285 
3286     // restore
3287     _movingUnit = nullptr;
3288 
3289     // check for possible bridge close action, after unit's end of movement
3290     if ( bridge && bridge->AllowUp() ) {
3291         bridge->Action( unit, destIndex );
3292     }
3293 
3294     StringReplace( msg, "%{dst}", unit.GetHeadIndex() );
3295     status.SetMessage( msg, true );
3296 }
3297 
RedrawActionResistSpell(const Unit & target,bool playSound)3298 void Battle::Interface::RedrawActionResistSpell( const Unit & target, bool playSound )
3299 {
3300     if ( playSound ) {
3301         AGG::PlaySound( M82::RSBRYFZL );
3302     }
3303     std::string str( _( "The %{name} resist the spell!" ) );
3304     StringReplace( str, "%{name}", target.GetName() );
3305     status.SetMessage( str, true );
3306     status.SetMessage( "", false );
3307 }
3308 
RedrawActionSpellCastStatus(const Spell & spell,int32_t dst,const std::string & name,const TargetsInfo & targets)3309 void Battle::Interface::RedrawActionSpellCastStatus( const Spell & spell, int32_t dst, const std::string & name, const TargetsInfo & targets )
3310 {
3311     const Unit * target = !targets.empty() ? targets.front().defender : nullptr;
3312 
3313     std::string msg;
3314 
3315     if ( target && target->GetHeadIndex() == dst ) {
3316         msg = _( "%{name} casts %{spell} on the %{troop}." );
3317         StringReplace( msg, "%{troop}", target->GetName() );
3318     }
3319     else if ( spell.isApplyWithoutFocusObject() ) {
3320         msg = _( "%{name} casts %{spell}." );
3321     }
3322 
3323     if ( !msg.empty() ) {
3324         StringReplace( msg, "%{name}", name );
3325         StringReplace( msg, "%{spell}", spell.GetName() );
3326 
3327         status.SetMessage( msg, true );
3328         status.SetMessage( "", false );
3329     }
3330 }
3331 
RedrawActionSpellCastPart1(const Spell & spell,s32 dst,const HeroBase * caster,const TargetsInfo & targets)3332 void Battle::Interface::RedrawActionSpellCastPart1( const Spell & spell, s32 dst, const HeroBase * caster, const TargetsInfo & targets )
3333 {
3334     Unit * target = !targets.empty() ? targets.front().defender : nullptr;
3335 
3336     // set spell cast animation
3337     if ( caster ) {
3338         OpponentSprite * opponent = caster->GetColor() == arena.GetArmyColor1() ? opponent1 : opponent2;
3339         if ( opponent ) {
3340             opponent->SetAnimation( spell.isApplyWithoutFocusObject() ? OP_CAST_MASS : OP_CAST_UP );
3341             AnimateOpponents( opponent );
3342         }
3343     }
3344 
3345     // without object
3346     switch ( spell.GetID() ) {
3347     case Spell::FIREBALL:
3348         RedrawTargetsWithFrameAnimation( dst, targets, ICN::FIREBALL, M82::FromSpell( spell.GetID() ) );
3349         break;
3350     case Spell::FIREBLAST:
3351         RedrawTargetsWithFrameAnimation( dst, targets, ICN::FIREBAL2, M82::FromSpell( spell.GetID() ) );
3352         break;
3353     case Spell::METEORSHOWER:
3354         RedrawTargetsWithFrameAnimation( dst, targets, ICN::METEOR, M82::FromSpell( spell.GetID() ), 1 );
3355         break;
3356     case Spell::COLDRING:
3357         RedrawActionColdRingSpell( dst, targets );
3358         break;
3359 
3360     case Spell::MASSSHIELD:
3361         RedrawTargetsWithFrameAnimation( targets, ICN::SHIELD, M82::FromSpell( spell.GetID() ), false );
3362         break;
3363     case Spell::MASSCURE:
3364         RedrawTargetsWithFrameAnimation( targets, ICN::MAGIC01, M82::FromSpell( spell.GetID() ), false );
3365         break;
3366     case Spell::MASSHASTE:
3367         RedrawTargetsWithFrameAnimation( targets, ICN::HASTE, M82::FromSpell( spell.GetID() ), false );
3368         break;
3369     case Spell::MASSSLOW:
3370         RedrawTargetsWithFrameAnimation( targets, ICN::MAGIC02, M82::FromSpell( spell.GetID() ), false );
3371         break;
3372     case Spell::MASSBLESS:
3373         RedrawTargetsWithFrameAnimation( targets, ICN::BLESS, M82::FromSpell( spell.GetID() ), false );
3374         break;
3375     case Spell::MASSCURSE:
3376         RedrawTargetsWithFrameAnimation( targets, ICN::CURSE, M82::FromSpell( spell.GetID() ), false );
3377         break;
3378     case Spell::MASSDISPEL:
3379         RedrawTargetsWithFrameAnimation( targets, ICN::MAGIC07, M82::FromSpell( spell.GetID() ), false );
3380         break;
3381 
3382     case Spell::DEATHRIPPLE:
3383         RedrawActionDeathWaveSpell( targets, 10 );
3384         break;
3385     case Spell::DEATHWAVE:
3386         RedrawActionDeathWaveSpell( targets, 15 );
3387         break;
3388 
3389     case Spell::HOLYWORD:
3390         RedrawActionHolyShoutSpell( targets, 2 );
3391         break;
3392     case Spell::HOLYSHOUT:
3393         RedrawActionHolyShoutSpell( targets, 4 );
3394         break;
3395 
3396     case Spell::ELEMENTALSTORM:
3397         RedrawActionElementalStormSpell( targets );
3398         break;
3399     case Spell::ARMAGEDDON:
3400         RedrawActionArmageddonSpell(); // hit everything
3401         break;
3402 
3403     default:
3404         break;
3405     }
3406 
3407     // with object
3408     if ( target ) {
3409         if ( spell.isResurrect() )
3410             RedrawActionResurrectSpell( *target, spell );
3411         else
3412             switch ( spell.GetID() ) {
3413             // simple spell animation
3414             case Spell::BLESS:
3415                 RedrawTroopWithFrameAnimation( *target, ICN::BLESS, M82::FromSpell( spell.GetID() ), NONE );
3416                 break;
3417             case Spell::BLIND:
3418                 RedrawTroopWithFrameAnimation( *target, ICN::BLIND, M82::FromSpell( spell.GetID() ), NONE );
3419                 break;
3420             case Spell::CURE:
3421                 RedrawTroopWithFrameAnimation( *target, ICN::MAGIC01, M82::FromSpell( spell.GetID() ), NONE );
3422                 break;
3423             case Spell::SLOW:
3424                 RedrawTroopWithFrameAnimation( *target, ICN::MAGIC02, M82::FromSpell( spell.GetID() ), NONE );
3425                 break;
3426             case Spell::SHIELD:
3427                 RedrawTroopWithFrameAnimation( *target, ICN::SHIELD, M82::FromSpell( spell.GetID() ), NONE );
3428                 break;
3429             case Spell::HASTE:
3430                 RedrawTroopWithFrameAnimation( *target, ICN::HASTE, M82::FromSpell( spell.GetID() ), NONE );
3431                 break;
3432             case Spell::CURSE:
3433                 RedrawTroopWithFrameAnimation( *target, ICN::CURSE, M82::FromSpell( spell.GetID() ), NONE );
3434                 break;
3435             case Spell::ANTIMAGIC:
3436                 RedrawTroopWithFrameAnimation( *target, ICN::MAGIC06, M82::FromSpell( spell.GetID() ), NONE );
3437                 break;
3438             case Spell::DISPEL:
3439                 RedrawTroopWithFrameAnimation( *target, ICN::MAGIC07, M82::FromSpell( spell.GetID() ), NONE );
3440                 break;
3441             case Spell::STONESKIN:
3442                 RedrawTroopWithFrameAnimation( *target, ICN::STONSKIN, M82::FromSpell( spell.GetID() ), NONE );
3443                 break;
3444             case Spell::STEELSKIN:
3445                 RedrawTroopWithFrameAnimation( *target, ICN::STELSKIN, M82::FromSpell( spell.GetID() ), NONE );
3446                 break;
3447             case Spell::PARALYZE:
3448                 RedrawTroopWithFrameAnimation( *target, ICN::PARALYZE, M82::FromSpell( spell.GetID() ), NONE );
3449                 break;
3450             case Spell::HYPNOTIZE:
3451                 RedrawTroopWithFrameAnimation( *target, ICN::HYPNOTIZ, M82::FromSpell( spell.GetID() ), NONE );
3452                 break;
3453             case Spell::DRAGONSLAYER:
3454                 RedrawTroopWithFrameAnimation( *target, ICN::DRAGSLAY, M82::FromSpell( spell.GetID() ), NONE );
3455                 break;
3456             case Spell::BERSERKER:
3457                 RedrawTroopWithFrameAnimation( *target, ICN::BERZERK, M82::FromSpell( spell.GetID() ), NONE );
3458                 break;
3459 
3460             // uniq spell animation
3461             case Spell::LIGHTNINGBOLT:
3462                 RedrawActionLightningBoltSpell( *target );
3463                 break;
3464             case Spell::CHAINLIGHTNING:
3465                 RedrawActionChainLightningSpell( targets );
3466                 break;
3467             case Spell::ARROW:
3468                 RedrawActionArrowSpell( *target );
3469                 break;
3470             case Spell::COLDRAY:
3471                 RedrawActionColdRaySpell( *target );
3472                 break;
3473             case Spell::DISRUPTINGRAY:
3474                 RedrawActionDisruptingRaySpell( *target );
3475                 break;
3476             case Spell::BLOODLUST:
3477                 RedrawActionBloodLustSpell( *target );
3478                 break;
3479             case Spell::STONE:
3480                 RedrawActionStoneSpell( *target );
3481                 break;
3482             default:
3483                 break;
3484             }
3485     }
3486 
3487     if ( caster ) {
3488         OpponentSprite * opponent = caster->GetColor() == arena.GetArmyColor1() ? opponent1 : opponent2;
3489         if ( opponent ) {
3490             opponent->SetAnimation( ( target ) ? OP_CAST_UP_RETURN : OP_CAST_MASS_RETURN );
3491             AnimateOpponents( opponent );
3492         }
3493     }
3494 }
3495 
RedrawActionSpellCastPart2(const Spell & spell,TargetsInfo & targets)3496 void Battle::Interface::RedrawActionSpellCastPart2( const Spell & spell, TargetsInfo & targets )
3497 {
3498     if ( spell.isDamage() ) {
3499         uint32_t killed = 0;
3500         uint32_t totalDamage = 0;
3501         uint32_t maximumDamage = 0;
3502         uint32_t damagedMonsters = 0;
3503 
3504         for ( TargetsInfo::const_iterator it = targets.begin(); it != targets.end(); ++it ) {
3505             if ( !it->defender->isModes( CAP_MIRRORIMAGE ) ) {
3506                 killed += ( *it ).killed;
3507 
3508                 ++damagedMonsters;
3509                 totalDamage += it->damage;
3510                 if ( maximumDamage < it->damage )
3511                     maximumDamage = it->damage;
3512             }
3513         }
3514 
3515         // targets damage animation
3516         RedrawActionWincesKills( targets );
3517 
3518         if ( totalDamage > 0 ) {
3519             assert( damagedMonsters > 0 );
3520             std::string msg;
3521             if ( spell.isUndeadOnly() ) {
3522                 if ( damagedMonsters == 1 ) {
3523                     msg = _( "The %{spell} does %{damage} damage to one undead creature." );
3524                     StringReplace( msg, "%{spell}", spell.GetName() );
3525                     StringReplace( msg, "%{damage}", totalDamage );
3526                     status.SetMessage( msg, true );
3527 
3528                     if ( killed > 0 ) {
3529                         msg = _n( "1 creature perishes.", "%{count} creatures perish.", killed );
3530                         StringReplace( msg, "%{count}", killed );
3531                         status.SetMessage( msg, true );
3532                     }
3533                 }
3534                 else {
3535                     msg = _( "The %{spell} does %{damage} damage to all undead creatures." );
3536                     StringReplace( msg, "%{spell}", spell.GetName() );
3537                     StringReplace( msg, "%{damage}", maximumDamage );
3538                     status.SetMessage( msg, true );
3539 
3540                     if ( killed > 0 ) {
3541                         msg = _( "The %{spell} does %{damage} damage, %{count} creatures perish." );
3542                         StringReplace( msg, "%{count}", killed );
3543                     }
3544                     else {
3545                         msg = _( "The %{spell} does %{damage} damage." );
3546                     }
3547 
3548                     StringReplace( msg, "%{spell}", spell.GetName() );
3549                     StringReplace( msg, "%{damage}", totalDamage );
3550                     status.SetMessage( msg, true );
3551                 }
3552             }
3553             else if ( spell.isALiveOnly() ) {
3554                 if ( damagedMonsters == 1 ) {
3555                     msg = _( "The %{spell} does %{damage} damage to one living creature." );
3556                     StringReplace( msg, "%{spell}", spell.GetName() );
3557                     StringReplace( msg, "%{damage}", totalDamage );
3558                     status.SetMessage( msg, true );
3559 
3560                     if ( killed > 0 ) {
3561                         msg = _n( "1 creature perishes.", "%{count} creatures perish.", killed );
3562                         StringReplace( msg, "%{count}", killed );
3563                         status.SetMessage( msg, true );
3564                     }
3565                 }
3566                 else {
3567                     msg = _( "The %{spell} does %{damage} damage to all living creatures." );
3568                     StringReplace( msg, "%{spell}", spell.GetName() );
3569                     StringReplace( msg, "%{damage}", maximumDamage );
3570                     status.SetMessage( msg, true );
3571 
3572                     if ( killed > 0 ) {
3573                         msg = _( "The %{spell} does %{damage} damage, %{count} creatures perish." );
3574                         StringReplace( msg, "%{count}", killed );
3575                     }
3576                     else {
3577                         msg = _( "The %{spell} does %{damage} damage." );
3578                     }
3579 
3580                     StringReplace( msg, "%{spell}", spell.GetName() );
3581                     StringReplace( msg, "%{damage}", totalDamage );
3582                     status.SetMessage( msg, true );
3583                 }
3584             }
3585             else {
3586                 msg = _( "The %{spell} does %{damage} damage." );
3587                 StringReplace( msg, "%{spell}", spell.GetName() );
3588                 StringReplace( msg, "%{damage}", totalDamage );
3589                 status.SetMessage( msg, true );
3590 
3591                 if ( killed > 0 ) {
3592                     msg = _n( "1 creature perishes.", "%{count} creatures perish.", killed );
3593                     StringReplace( msg, "%{count}", killed );
3594                     status.SetMessage( msg, true );
3595                 }
3596             }
3597         }
3598     }
3599 
3600     status.SetMessage( " ", false );
3601     _movingUnit = nullptr;
3602 }
3603 
RedrawActionMonsterSpellCastStatus(const Unit & attacker,const TargetInfo & target)3604 void Battle::Interface::RedrawActionMonsterSpellCastStatus( const Unit & attacker, const TargetInfo & target )
3605 {
3606     const char * msg = nullptr;
3607 
3608     switch ( attacker.GetID() ) {
3609     case Monster::UNICORN:
3610         msg = _( "The Unicorns' attack blinds the %{name}!" );
3611         break;
3612     case Monster::MEDUSA:
3613         msg = _( "The Medusas' gaze turns the %{name} to stone!" );
3614         break;
3615     case Monster::ROYAL_MUMMY:
3616     case Monster::MUMMY:
3617         msg = _( "The Mummies' curse falls upon the %{name}!" );
3618         break;
3619     case Monster::CYCLOPS:
3620         msg = _( "The %{name} are paralyzed by the Cyclopes!" );
3621         break;
3622     case Monster::ARCHMAGE:
3623         msg = _( "The Archmagi dispel all good spells on your %{name}!" );
3624         break;
3625     default:
3626         break;
3627     }
3628 
3629     if ( msg ) {
3630         std::string str( msg );
3631         StringReplace( str, "%{name}", target.defender->GetName() );
3632 
3633         status.SetMessage( str, true );
3634         status.SetMessage( "", false );
3635     }
3636 }
3637 
RedrawActionLuck(const Unit & unit)3638 void Battle::Interface::RedrawActionLuck( const Unit & unit )
3639 {
3640     LocalEvent & le = LocalEvent::Get();
3641 
3642     const bool isGoodLuck = unit.Modes( LUCK_GOOD );
3643     const fheroes2::Rect & pos = unit.GetRectPosition();
3644 
3645     std::string msg = isGoodLuck ? _( "Good luck shines on the %{attacker}." ) : _( "Bad luck descends on the %{attacker}." );
3646     StringReplace( msg, "%{attacker}", unit.GetName() );
3647     status.SetMessage( msg, true );
3648 
3649     Cursor::Get().SetThemes( Cursor::WAR_POINTER );
3650     if ( isGoodLuck ) {
3651         const fheroes2::Sprite & luckSprite = fheroes2::AGG::GetICN( ICN::EXPMRL, 0 );
3652         const fheroes2::Sprite & unitSprite = fheroes2::AGG::GetICN( unit.GetMonsterSprite(), unit.GetFrame() );
3653 
3654         int width = 2;
3655         fheroes2::Rect src( 0, 0, width, luckSprite.height() );
3656         src.x = ( luckSprite.width() - src.width ) / 2;
3657         int y = pos.y + pos.height - unitSprite.height() - src.height;
3658         if ( y < 0 )
3659             y = 0;
3660 
3661         AGG::PlaySound( M82::GOODLUCK );
3662 
3663         while ( le.HandleEvents() && Mixer::isPlaying( -1 ) ) {
3664             CheckGlobalEvents( le );
3665 
3666             if ( width < luckSprite.width() && Game::validateAnimationDelay( Game::BATTLE_MISSILE_DELAY ) ) {
3667                 RedrawPartialStart();
3668 
3669                 fheroes2::Blit( luckSprite, src.x, src.y, _mainSurface, pos.x + ( pos.width - src.width ) / 2, y, src.width, src.height );
3670 
3671                 RedrawPartialFinish();
3672 
3673                 src.width = width;
3674                 src.x = ( luckSprite.width() - src.width ) / 2;
3675 
3676                 width += 3;
3677             }
3678         }
3679     }
3680     else {
3681         const int maxHeight = fheroes2::AGG::GetAbsoluteICNHeight( ICN::CLOUDLUK );
3682         int y = pos.y + pos.height + cellYOffset;
3683 
3684         // move drawing position if it will clip outside of the battle window
3685         if ( y - maxHeight < 0 )
3686             y = maxHeight;
3687 
3688         AGG::PlaySound( M82::BADLUCK );
3689 
3690         int frameId = 0;
3691         while ( le.HandleEvents() && Mixer::isPlaying( -1 ) ) {
3692             CheckGlobalEvents( le );
3693 
3694             if ( frameId < 8 && Game::validateAnimationDelay( Game::BATTLE_MISSILE_DELAY ) ) {
3695                 RedrawPartialStart();
3696 
3697                 const fheroes2::Sprite & luckSprite = fheroes2::AGG::GetICN( ICN::CLOUDLUK, frameId );
3698                 fheroes2::Blit( luckSprite, _mainSurface, pos.x + pos.width / 2 + luckSprite.x(), y + luckSprite.y() );
3699 
3700                 RedrawPartialFinish();
3701 
3702                 ++frameId;
3703             }
3704         }
3705     }
3706 }
3707 
RedrawActionMorale(Unit & b,bool good)3708 void Battle::Interface::RedrawActionMorale( Unit & b, bool good )
3709 {
3710     std::string msg;
3711 
3712     if ( good ) {
3713         msg = _( "High morale enables the %{monster} to attack again." );
3714         StringReplace( msg, "%{monster}", b.GetName() );
3715         status.SetMessage( msg, true );
3716         RedrawTroopWithFrameAnimation( b, ICN::MORALEG, M82::GOODMRLE, NONE );
3717     }
3718     else {
3719         msg = _( "Low morale causes the %{monster} to freeze in panic." );
3720         StringReplace( msg, "%{monster}", b.GetName() );
3721         status.SetMessage( msg, true );
3722         RedrawTroopWithFrameAnimation( b, ICN::MORALEB, M82::BADMRLE, WINCE );
3723     }
3724 }
3725 
RedrawActionTowerPart1(const Tower & tower,const Unit & defender)3726 void Battle::Interface::RedrawActionTowerPart1( const Tower & tower, const Unit & defender )
3727 {
3728     Cursor::Get().SetThemes( Cursor::WAR_POINTER );
3729     _currentUnit = nullptr;
3730 
3731     const fheroes2::Point missileStart = tower.GetPortPosition();
3732     const fheroes2::Point targetPos = defender.GetCenterPoint();
3733     const double angle = GetAngle( missileStart, targetPos );
3734 
3735     AGG::PlaySound( M82::KEEPSHOT );
3736 
3737     // Keep missile == Orc missile
3738     RedrawMissileAnimation( missileStart, targetPos, angle, Monster::ORC );
3739 }
3740 
RedrawActionTowerPart2(const Tower & tower,const TargetInfo & target)3741 void Battle::Interface::RedrawActionTowerPart2( const Tower & tower, const TargetInfo & target )
3742 {
3743     TargetsInfo targets;
3744     targets.push_back( target );
3745     const bool isMirror = target.defender->isModes( CAP_MIRRORIMAGE );
3746 
3747     // targets damage animation
3748     RedrawActionWincesKills( targets );
3749 
3750     // draw status for first defender
3751     std::string msg = _( "%{tower} does %{damage} damage." );
3752     StringReplace( msg, "%{tower}", tower.GetName() );
3753     StringReplace( msg, "%{damage}", target.damage );
3754     if ( target.killed ) {
3755         msg += ' ';
3756         msg.append( _n( "1 %{defender} perishes.", "%{count} %{defender} perish.", target.killed ) );
3757         StringReplace( msg, "%{count}", target.killed );
3758         StringReplace( msg, "%{defender}", target.defender->GetPluralName( target.killed ) );
3759     }
3760 
3761     if ( !isMirror ) {
3762         status.SetMessage( msg, true );
3763         status.SetMessage( "", false );
3764     }
3765 
3766     _movingUnit = nullptr;
3767 }
3768 
RedrawActionCatapult(int target,bool hit)3769 void Battle::Interface::RedrawActionCatapult( int target, bool hit )
3770 {
3771     LocalEvent & le = LocalEvent::Get();
3772 
3773     const fheroes2::Sprite & missile = fheroes2::AGG::GetICN( ICN::BOULDER, 0 );
3774     const fheroes2::Rect & area = GetArea();
3775 
3776     AGG::PlaySound( M82::CATSND00 );
3777 
3778     // catapult animation
3779     while ( le.HandleEvents( false ) && catapult_frame < 6 ) {
3780         CheckGlobalEvents( le );
3781 
3782         if ( Game::validateAnimationDelay( Game::BATTLE_CATAPULT_DELAY ) ) {
3783             Redraw();
3784             ++catapult_frame;
3785         }
3786     }
3787 
3788     // boulder animation
3789     fheroes2::Point pt1( 90, 220 );
3790     fheroes2::Point pt2 = Catapult::GetTargetPosition( target, hit );
3791     fheroes2::Point max( 300, 20 );
3792 
3793     pt1.x += area.x;
3794     pt2.x += area.x;
3795     max.x += area.x;
3796     pt1.y += area.y;
3797     pt2.y += area.y;
3798     max.y += area.y;
3799 
3800     const std::vector<fheroes2::Point> points = GetArcPoints( pt1, pt2, max, missile.width() );
3801     std::vector<fheroes2::Point>::const_iterator pnt = points.begin();
3802 
3803     while ( le.HandleEvents( false ) && pnt != points.end() ) {
3804         CheckGlobalEvents( le );
3805 
3806         if ( Game::validateAnimationDelay( Game::BATTLE_CATAPULT_BOULDER_DELAY ) ) {
3807             if ( catapult_frame < 9 )
3808                 ++catapult_frame;
3809 
3810             RedrawPartialStart();
3811             fheroes2::Blit( missile, _mainSurface, pnt->x, pnt->y );
3812             RedrawPartialFinish();
3813             ++pnt;
3814         }
3815     }
3816 
3817     // draw cloud
3818     const int icn = hit ? ICN::LICHCLOD : ICN::SMALCLOD;
3819     uint32_t frame = 0;
3820 
3821     AGG::PlaySound( M82::CATSND02 );
3822 
3823     while ( le.HandleEvents() && frame < fheroes2::AGG::GetICNCount( icn ) ) {
3824         CheckGlobalEvents( le );
3825 
3826         if ( Game::validateAnimationDelay( Game::BATTLE_CATAPULT_CLOUD_DELAY ) ) {
3827             if ( catapult_frame < 9 )
3828                 ++catapult_frame;
3829 
3830             RedrawPartialStart();
3831             const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( icn, frame );
3832             fheroes2::Blit( sprite, _mainSurface, pt2.x + sprite.x(), pt2.y + sprite.y() );
3833             RedrawPartialFinish();
3834 
3835             ++frame;
3836         }
3837     }
3838 
3839     catapult_frame = 0;
3840 }
3841 
RedrawActionArrowSpell(const Unit & target)3842 void Battle::Interface::RedrawActionArrowSpell( const Unit & target )
3843 {
3844     const HeroBase * caster = arena.GetCurrentCommander();
3845 
3846     if ( caster ) {
3847         const fheroes2::Point missileStart = caster == opponent1->GetHero() ? opponent1->GetCastPosition() : opponent2->GetCastPosition();
3848 
3849         const fheroes2::Point targetPos = target.GetCenterPoint();
3850         const double angle = GetAngle( missileStart, targetPos );
3851 
3852         Cursor::Get().SetThemes( Cursor::WAR_POINTER );
3853         AGG::PlaySound( M82::MAGCAROW );
3854 
3855         // Magic arrow == Archer missile
3856         RedrawMissileAnimation( missileStart, targetPos, angle, Monster::ARCHER );
3857     }
3858 }
3859 
RedrawActionTeleportSpell(Unit & target,s32 dst)3860 void Battle::Interface::RedrawActionTeleportSpell( Unit & target, s32 dst )
3861 {
3862     LocalEvent & le = LocalEvent::Get();
3863 
3864     Cursor::Get().SetThemes( Cursor::WAR_POINTER );
3865 
3866     uint32_t currentAlpha = target.GetCustomAlpha();
3867 
3868     AGG::PlaySound( M82::TELPTOUT );
3869 
3870     Game::passAnimationDelay( Game::BATTLE_SPELL_DELAY );
3871 
3872     while ( le.HandleEvents() && Mixer::isPlaying( -1 ) ) {
3873         CheckGlobalEvents( le );
3874 
3875         if ( currentAlpha > 0 && Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) {
3876             currentAlpha -= 15;
3877             target.SetCustomAlpha( currentAlpha );
3878             Redraw();
3879         }
3880     }
3881 
3882     currentAlpha = 0;
3883     Redraw();
3884 
3885     target.SetPosition( dst );
3886     AGG::PlaySound( M82::TELPTIN );
3887 
3888     while ( le.HandleEvents() && Mixer::isPlaying( -1 ) ) {
3889         CheckGlobalEvents( le );
3890 
3891         if ( currentAlpha <= 240 && Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) {
3892             currentAlpha += 15;
3893             target.SetCustomAlpha( currentAlpha );
3894             Redraw();
3895         }
3896     }
3897 
3898     target.SetCustomAlpha( 255 );
3899 }
3900 
RedrawActionSummonElementalSpell(Unit & target)3901 void Battle::Interface::RedrawActionSummonElementalSpell( Unit & target )
3902 {
3903     LocalEvent & le = LocalEvent::Get();
3904 
3905     Cursor::Get().SetThemes( Cursor::WAR_POINTER );
3906 
3907     uint32_t currentAlpha = 0;
3908 
3909     AGG::PlaySound( M82::SUMNELM );
3910 
3911     Game::passAnimationDelay( Game::BATTLE_SPELL_DELAY );
3912 
3913     while ( le.HandleEvents() && currentAlpha < 220 ) {
3914         CheckGlobalEvents( le );
3915 
3916         if ( Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) {
3917             currentAlpha += 20;
3918             target.SetCustomAlpha( currentAlpha );
3919             Redraw();
3920         }
3921     }
3922 
3923     target.SetCustomAlpha( 255 );
3924 }
3925 
RedrawActionMirrorImageSpell(const Unit & target,const Position & pos)3926 void Battle::Interface::RedrawActionMirrorImageSpell( const Unit & target, const Position & pos )
3927 {
3928     LocalEvent & le = LocalEvent::Get();
3929 
3930     fheroes2::Sprite sprite = fheroes2::AGG::GetICN( target.GetMonsterSprite(), target.GetFrame() );
3931     fheroes2::ApplyPalette( sprite, PAL::GetPalette( PAL::PaletteType::MIRROR_IMAGE ) );
3932 
3933     const fheroes2::Rect & rt1 = target.GetRectPosition();
3934     const fheroes2::Rect & rt2 = pos.GetRect();
3935 
3936     const std::vector<fheroes2::Point> points = GetLinePoints( rt1.getPosition(), rt2.getPosition(), 5 );
3937     std::vector<fheroes2::Point>::const_iterator pnt = points.begin();
3938 
3939     Cursor::Get().SetThemes( Cursor::WAR_POINTER );
3940     AGG::PlaySound( M82::MIRRORIM );
3941 
3942     Game::passAnimationDelay( Game::BATTLE_SPELL_DELAY );
3943 
3944     while ( le.HandleEvents() && pnt != points.end() ) {
3945         CheckGlobalEvents( le );
3946 
3947         if ( Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) {
3948             const fheroes2::Point & sp = GetTroopPosition( target, sprite );
3949 
3950             RedrawPartialStart();
3951             fheroes2::Blit( sprite, _mainSurface, sp.x - rt1.x + ( *pnt ).x, sp.y - rt1.y + ( *pnt ).y, target.isReflect() );
3952             RedrawPartialFinish();
3953 
3954             ++pnt;
3955         }
3956     }
3957 
3958     status.SetMessage( _( "The mirror image is created." ), true );
3959 }
3960 
RedrawLightningOnTargets(const std::vector<fheroes2::Point> & points,const fheroes2::Rect & drawRoi)3961 void Battle::Interface::RedrawLightningOnTargets( const std::vector<fheroes2::Point> & points, const fheroes2::Rect & drawRoi )
3962 {
3963     if ( points.size() < 2 )
3964         return;
3965 
3966     const fheroes2::Point roiOffset( drawRoi.x, drawRoi.y );
3967 
3968     LocalEvent & le = LocalEvent::Get();
3969     Cursor & cursor = Cursor::Get();
3970     cursor.SetThemes( Cursor::WAR_POINTER );
3971 
3972     AGG::PlaySound( points.size() > 2 ? M82::CHAINLTE : M82::LIGHTBLT );
3973 
3974     for ( size_t i = 1; i < points.size(); ++i ) {
3975         const fheroes2::Point & startingPos = points[i - 1];
3976         const fheroes2::Point & endPos = points[i];
3977 
3978         const std::vector<std::pair<LightningPoint, LightningPoint> > & lightningBolt = GenerateLightning( startingPos + roiOffset, endPos + roiOffset );
3979         fheroes2::Rect roi;
3980         const bool isHorizontalBolt = std::abs( startingPos.x - endPos.x ) > std::abs( startingPos.y - endPos.y );
3981         const bool isForwardDirection = isHorizontalBolt ? ( endPos.x > startingPos.x ) : ( endPos.y > startingPos.y );
3982         const int animationStep = 100;
3983 
3984         if ( isHorizontalBolt ) {
3985             roi.height = drawRoi.height;
3986             if ( isForwardDirection ) {
3987                 roi.x = 0;
3988                 roi.width = startingPos.x;
3989             }
3990             else {
3991                 roi.x = startingPos.x;
3992                 roi.width = drawRoi.width - startingPos.x;
3993             }
3994         }
3995         else {
3996             roi.width = drawRoi.width;
3997             if ( isForwardDirection ) {
3998                 roi.y = 0;
3999                 roi.height = startingPos.y;
4000             }
4001             else {
4002                 roi.y = startingPos.y;
4003                 roi.height = drawRoi.height - startingPos.y;
4004             }
4005         }
4006 
4007         while ( le.HandleEvents() && ( ( isHorizontalBolt && roi.width < drawRoi.width ) || ( !isHorizontalBolt && roi.height < drawRoi.height ) ) ) {
4008             if ( Game::validateAnimationDelay( Game::BATTLE_DISRUPTING_DELAY ) ) {
4009                 if ( isHorizontalBolt ) {
4010                     if ( isForwardDirection ) {
4011                         roi.width += animationStep;
4012                     }
4013                     else {
4014                         roi.width += animationStep;
4015                         roi.x -= animationStep;
4016                     }
4017 
4018                     if ( roi.x < 0 )
4019                         roi.x = 0;
4020                     if ( roi.width > drawRoi.width )
4021                         roi.width = drawRoi.width;
4022                 }
4023                 else {
4024                     if ( isForwardDirection ) {
4025                         roi.height += animationStep;
4026                     }
4027                     else {
4028                         roi.height += animationStep;
4029                         roi.y -= animationStep;
4030                     }
4031 
4032                     if ( roi.y < 0 )
4033                         roi.y = 0;
4034                     if ( roi.height > drawRoi.height )
4035                         roi.height = drawRoi.height;
4036                 }
4037 
4038                 RedrawPartialStart();
4039 
4040                 RedrawLightning( lightningBolt, fheroes2::GetColorId( 0xff, 0xff, 0 ), _mainSurface,
4041                                  fheroes2::Rect( roi.x + roiOffset.x, roi.y + roiOffset.y, roi.width, roi.height ) );
4042                 fheroes2::ApplyPalette( _mainSurface, 7 );
4043 
4044                 RedrawPartialFinish();
4045             }
4046         }
4047     }
4048 
4049     // small delay to display fully drawn lightning
4050     fheroes2::delayforMs( 100 );
4051 
4052     uint32_t frame = 0;
4053     while ( le.HandleEvents() && frame < fheroes2::AGG::GetICNCount( ICN::SPARKS ) ) {
4054         CheckGlobalEvents( le );
4055 
4056         if ( ( frame == 0 ) || Game::validateAnimationDelay( Game::BATTLE_DISRUPTING_DELAY ) ) {
4057             RedrawPartialStart();
4058 
4059             const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::SPARKS, frame );
4060 
4061             for ( size_t i = 1; i < points.size(); ++i ) {
4062                 fheroes2::Point pt = points[i] - fheroes2::Point( sprite.width() / 2, 0 ) + roiOffset;
4063                 fheroes2::Blit( sprite, _mainSurface, pt.x, pt.y );
4064             }
4065             RedrawPartialFinish();
4066 
4067             ++frame;
4068         }
4069     }
4070 }
4071 
RedrawActionLightningBoltSpell(const Unit & target)4072 void Battle::Interface::RedrawActionLightningBoltSpell( const Unit & target )
4073 {
4074     _currentUnit = nullptr;
4075 
4076     const fheroes2::Point startingPos = arena.GetCurrentCommander() == opponent1->GetHero() ? opponent1->GetCastPosition() : opponent2->GetCastPosition();
4077     const fheroes2::Rect & pos = target.GetRectPosition();
4078     const fheroes2::Point endPos( pos.x + pos.width / 2, pos.y );
4079 
4080     std::vector<fheroes2::Point> points;
4081     points.push_back( startingPos );
4082     points.push_back( endPos );
4083 
4084     RedrawLightningOnTargets( points, _surfaceInnerArea );
4085 }
4086 
RedrawActionChainLightningSpell(const TargetsInfo & targets)4087 void Battle::Interface::RedrawActionChainLightningSpell( const TargetsInfo & targets )
4088 {
4089     const fheroes2::Point startingPos = arena.GetCurrentCommander() == opponent1->GetHero() ? opponent1->GetCastPosition() : opponent2->GetCastPosition();
4090     std::vector<fheroes2::Point> points;
4091     points.push_back( startingPos );
4092 
4093     for ( TargetsInfo::const_iterator it = targets.begin(); it != targets.end(); ++it ) {
4094         const fheroes2::Rect & pos = it->defender->GetRectPosition();
4095         points.push_back( fheroes2::Point( pos.x + pos.width / 2, pos.y ) );
4096     }
4097 
4098     RedrawLightningOnTargets( points, _surfaceInnerArea );
4099 }
4100 
RedrawActionBloodLustSpell(const Unit & target)4101 void Battle::Interface::RedrawActionBloodLustSpell( const Unit & target )
4102 {
4103     LocalEvent & le = LocalEvent::Get();
4104 
4105     fheroes2::Sprite unitSprite = fheroes2::AGG::GetICN( target.GetMonsterSprite(), target.GetFrame() );
4106 
4107     std::vector<std::vector<uint8_t> > originalPalette;
4108     if ( target.Modes( SP_STONE ) ) {
4109         originalPalette.push_back( PAL::GetPalette( PAL::PaletteType::GRAY ) );
4110     }
4111     else if ( target.Modes( CAP_MIRRORIMAGE ) ) {
4112         originalPalette.push_back( PAL::GetPalette( PAL::PaletteType::MIRROR_IMAGE ) );
4113     }
4114 
4115     if ( !originalPalette.empty() ) {
4116         for ( size_t i = 1; i < originalPalette.size(); ++i ) {
4117             originalPalette[0] = PAL::CombinePalettes( originalPalette[0], originalPalette[i] );
4118         }
4119         fheroes2::ApplyPalette( unitSprite, originalPalette[0] );
4120     }
4121 
4122     std::vector<uint8_t> convert = PAL::GetPalette( PAL::PaletteType::RED );
4123     if ( !originalPalette.empty() ) {
4124         convert = PAL::CombinePalettes( PAL::GetPalette( PAL::PaletteType::GRAY ), convert );
4125     }
4126 
4127     fheroes2::Sprite bloodlustEffect( unitSprite );
4128     fheroes2::ApplyPalette( bloodlustEffect, convert );
4129 
4130     fheroes2::Sprite mixSprite( unitSprite );
4131 
4132     Cursor::Get().SetThemes( Cursor::WAR_POINTER );
4133 
4134     _currentUnit = &target;
4135     b_current_sprite = &mixSprite;
4136 
4137     const uint32_t bloodlustDelay = 1800 / 20;
4138     // duration is 1900ms
4139     AGG::PlaySound( M82::BLOODLUS );
4140 
4141     uint32_t alpha = 0;
4142     uint32_t frame = 0;
4143     while ( le.HandleEvents() && Mixer::isPlaying( -1 ) ) {
4144         CheckGlobalEvents( le );
4145 
4146         if ( frame < 20 && Game::validateCustomAnimationDelay( bloodlustDelay ) ) {
4147             mixSprite = unitSprite;
4148             fheroes2::AlphaBlit( bloodlustEffect, mixSprite, static_cast<uint8_t>( alpha ) );
4149             Redraw();
4150 
4151             alpha += ( frame < 10 ) ? 20 : -20;
4152             ++frame;
4153         }
4154     }
4155 
4156     _currentUnit = nullptr;
4157     b_current_sprite = nullptr;
4158 }
4159 
RedrawActionStoneSpell(const Unit & target)4160 void Battle::Interface::RedrawActionStoneSpell( const Unit & target )
4161 {
4162     LocalEvent & le = LocalEvent::Get();
4163 
4164     const fheroes2::Sprite & unitSprite = fheroes2::AGG::GetICN( target.GetMonsterSprite(), target.GetFrame() );
4165 
4166     fheroes2::Sprite stoneEffect( unitSprite );
4167     fheroes2::ApplyPalette( stoneEffect, PAL::GetPalette( PAL::PaletteType::GRAY ) );
4168 
4169     fheroes2::Sprite mixSprite( unitSprite );
4170 
4171     Cursor::Get().SetThemes( Cursor::WAR_POINTER );
4172 
4173     _currentUnit = &target;
4174     b_current_sprite = &mixSprite;
4175 
4176     AGG::PlaySound( M82::PARALIZE );
4177 
4178     uint32_t alpha = 0;
4179     uint32_t frame = 0;
4180     while ( le.HandleEvents() && Mixer::isPlaying( -1 ) ) {
4181         CheckGlobalEvents( le );
4182 
4183         if ( frame < 25 && Game::validateCustomAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) {
4184             mixSprite = fheroes2::Sprite( unitSprite );
4185             fheroes2::AlphaBlit( stoneEffect, mixSprite, static_cast<uint8_t>( alpha ) );
4186             Redraw();
4187 
4188             alpha += 10;
4189             ++frame;
4190         }
4191     }
4192 
4193     _currentUnit = nullptr;
4194     b_current_sprite = nullptr;
4195 }
4196 
RedrawActionResurrectSpell(Unit & target,const Spell & spell)4197 void Battle::Interface::RedrawActionResurrectSpell( Unit & target, const Spell & spell )
4198 {
4199     LocalEvent & le = LocalEvent::Get();
4200 
4201     if ( !target.isValid() ) {
4202         Game::passAnimationDelay( Game::BATTLE_SPELL_DELAY );
4203 
4204         while ( le.HandleEvents() && !target.isFinishAnimFrame() ) {
4205             CheckGlobalEvents( le );
4206 
4207             if ( Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) {
4208                 Redraw();
4209                 target.IncreaseAnimFrame();
4210             }
4211         }
4212     }
4213 
4214     AGG::PlaySound( M82::FromSpell( spell.GetID() ) );
4215 
4216     RedrawTroopWithFrameAnimation( target, ICN::YINYANG, M82::UNKNOWN, target.GetHitPoints() == 0 ? RESURRECT : NONE );
4217 }
4218 
RedrawActionColdRaySpell(Unit & target)4219 void Battle::Interface::RedrawActionColdRaySpell( Unit & target )
4220 {
4221     RedrawRaySpell( target, ICN::COLDRAY, M82::COLDRAY, 18 );
4222     RedrawTroopWithFrameAnimation( target, ICN::ICECLOUD, M82::UNKNOWN, NONE );
4223 }
4224 
RedrawRaySpell(const Unit & target,int spellICN,int spellSound,int32_t size)4225 void Battle::Interface::RedrawRaySpell( const Unit & target, int spellICN, int spellSound, int32_t size )
4226 {
4227     Cursor & cursor = Cursor::Get();
4228     LocalEvent & le = LocalEvent::Get();
4229 
4230     // Casting hero position
4231     const fheroes2::Point startingPos = arena.GetCurrentCommander() == opponent1->GetHero() ? opponent1->GetCastPosition() : opponent2->GetCastPosition();
4232     const fheroes2::Point targetPos = target.GetCenterPoint();
4233 
4234     const std::vector<fheroes2::Point> path = GetEuclideanLine( startingPos, targetPos, size );
4235     const uint32_t spriteCount = fheroes2::AGG::GetICNCount( spellICN );
4236 
4237     cursor.SetThemes( Cursor::WAR_POINTER );
4238     AGG::PlaySound( spellSound );
4239 
4240     size_t i = 0;
4241     while ( le.HandleEvents() && i < path.size() ) {
4242         CheckGlobalEvents( le );
4243 
4244         if ( Game::validateAnimationDelay( Game::BATTLE_DISRUPTING_DELAY ) ) {
4245             const uint32_t frame = static_cast<uint32_t>( i * spriteCount / path.size() ); // it's safe to do such as i <= path.size()
4246             const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( spellICN, frame );
4247             fheroes2::Blit( sprite, _mainSurface, path[i].x - sprite.width() / 2, path[i].y - sprite.height() / 2 );
4248             RedrawPartialFinish();
4249             ++i;
4250         }
4251     }
4252 }
4253 
RedrawActionDisruptingRaySpell(const Unit & target)4254 void Battle::Interface::RedrawActionDisruptingRaySpell( const Unit & target )
4255 {
4256     LocalEvent & le = LocalEvent::Get();
4257 
4258     RedrawRaySpell( target, ICN::DISRRAY, M82::DISRUPTR, 24 );
4259 
4260     // Part 2 - ripple effect
4261     const fheroes2::Sprite & unitSprite = fheroes2::AGG::GetICN( target.GetMonsterSprite(), target.GetFrame() );
4262     fheroes2::Sprite rippleSprite;
4263 
4264     const Unit * old_current = _currentUnit;
4265     _currentUnit = &target;
4266     _movingPos = fheroes2::Point( 0, 0 );
4267 
4268     uint32_t frame = 0;
4269     while ( le.HandleEvents() && frame < 60 ) {
4270         CheckGlobalEvents( le );
4271 
4272         if ( Game::validateAnimationDelay( Game::BATTLE_DISRUPTING_DELAY ) ) {
4273             rippleSprite = fheroes2::CreateRippleEffect( unitSprite, frame );
4274             rippleSprite.setPosition( unitSprite.x(), unitSprite.y() );
4275 
4276             b_current_sprite = &rippleSprite;
4277             Redraw();
4278 
4279             frame += 2;
4280         }
4281     }
4282 
4283     _currentUnit = old_current;
4284     b_current_sprite = nullptr;
4285 }
4286 
RedrawActionDeathWaveSpell(const TargetsInfo & targets,int strength)4287 void Battle::Interface::RedrawActionDeathWaveSpell( const TargetsInfo & targets, int strength )
4288 {
4289     Cursor & cursor = Cursor::Get();
4290     LocalEvent & le = LocalEvent::Get();
4291     _currentUnit = nullptr;
4292     cursor.SetThemes( Cursor::WAR_POINTER );
4293 
4294     fheroes2::Rect area = GetArea();
4295     area.height -= 36;
4296 
4297     const fheroes2::Sprite & copy = fheroes2::Crop( _mainSurface, area.x, area.y, area.width, area.height );
4298     const int waveLength = strength * 2 + 10;
4299 
4300     AGG::PlaySound( M82::MNRDEATH );
4301 
4302     int position = 10;
4303     while ( le.HandleEvents() && position < area.width + waveLength ) {
4304         CheckGlobalEvents( le );
4305 
4306         if ( Game::validateAnimationDelay( Game::BATTLE_DISRUPTING_DELAY ) ) {
4307             fheroes2::Blit( fheroes2::CreateDeathWaveEffect( copy, position, waveLength, strength ), _mainSurface );
4308             RedrawPartialFinish();
4309 
4310             position += 3;
4311         }
4312     }
4313 
4314     RedrawTargetsWithFrameAnimation( targets, ICN::REDDEATH, M82::UNKNOWN, true );
4315 }
4316 
RedrawActionColdRingSpell(s32 dst,const TargetsInfo & targets)4317 void Battle::Interface::RedrawActionColdRingSpell( s32 dst, const TargetsInfo & targets )
4318 {
4319     LocalEvent & le = LocalEvent::Get();
4320 
4321     const int icn = ICN::COLDRING;
4322     const int m82 = M82::FromSpell( Spell::COLDRING );
4323     uint32_t frame = 0;
4324     const fheroes2::Rect & center = Board::GetCell( dst )->GetPos();
4325 
4326     Cursor::Get().SetThemes( Cursor::WAR_POINTER );
4327 
4328     // set WNCE
4329     _currentUnit = nullptr;
4330     for ( TargetsInfo::const_iterator it = targets.begin(); it != targets.end(); ++it )
4331         if ( ( *it ).defender && ( *it ).damage )
4332             ( *it ).defender->SwitchAnimation( Monster_Info::WNCE );
4333 
4334     if ( M82::UNKNOWN != m82 )
4335         AGG::PlaySound( m82 );
4336 
4337     Game::passAnimationDelay( Game::BATTLE_SPELL_DELAY );
4338 
4339     while ( le.HandleEvents() && frame < fheroes2::AGG::GetICNCount( icn ) ) {
4340         CheckGlobalEvents( le );
4341 
4342         if ( Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) {
4343             RedrawPartialStart();
4344 
4345             const fheroes2::Sprite & sprite1 = fheroes2::AGG::GetICN( icn, frame );
4346             fheroes2::Blit( sprite1, _mainSurface, center.x + center.width / 2 + sprite1.x(), center.y + center.height / 2 + sprite1.y() );
4347             const fheroes2::Sprite & sprite2 = fheroes2::AGG::GetICN( icn, frame );
4348             fheroes2::Blit( sprite2, _mainSurface, center.x + center.width / 2 - sprite2.width() - sprite2.x(), center.y + center.height / 2 + sprite2.y(), true );
4349             RedrawPartialFinish();
4350 
4351             for ( TargetsInfo::const_iterator it = targets.begin(); it != targets.end(); ++it )
4352                 if ( ( *it ).defender && ( *it ).damage )
4353                     ( *it ).defender->IncreaseAnimFrame( false );
4354             ++frame;
4355         }
4356     }
4357 
4358     for ( TargetsInfo::const_iterator it = targets.begin(); it != targets.end(); ++it )
4359         if ( ( *it ).defender ) {
4360             ( *it ).defender->SwitchAnimation( Monster_Info::STATIC );
4361             _currentUnit = nullptr;
4362         }
4363 }
4364 
RedrawActionHolyShoutSpell(const TargetsInfo & targets,int strength)4365 void Battle::Interface::RedrawActionHolyShoutSpell( const TargetsInfo & targets, int strength )
4366 {
4367     Cursor & cursor = Cursor::Get();
4368     LocalEvent & le = LocalEvent::Get();
4369 
4370     cursor.SetThemes( Cursor::WAR_POINTER );
4371 
4372     const fheroes2::Image original( _mainSurface );
4373     const fheroes2::Image blurred = fheroes2::CreateBlurredImage( _mainSurface, strength );
4374 
4375     _currentUnit = nullptr;
4376     AGG::PlaySound( M82::MASSCURS );
4377 
4378     const uint32_t spellcastDelay = Game::ApplyBattleSpeed( 3000 ) / 20;
4379     uint32_t frame = 0;
4380     uint8_t alpha = 30;
4381 
4382     while ( le.HandleEvents() && frame < 20 ) {
4383         CheckGlobalEvents( le );
4384 
4385         if ( Game::validateCustomAnimationDelay( spellcastDelay ) ) {
4386             // stay at maximum blur for 2 frames
4387             if ( frame < 9 || frame > 10 ) {
4388                 fheroes2::Copy( original, _mainSurface );
4389                 fheroes2::AlphaBlit( blurred, _mainSurface, alpha );
4390                 RedrawPartialFinish();
4391 
4392                 alpha += ( frame < 10 ) ? 25 : -25;
4393             }
4394             ++frame;
4395         }
4396     }
4397 
4398     RedrawTargetsWithFrameAnimation( targets, ICN::MAGIC08, M82::UNKNOWN, true );
4399 }
4400 
RedrawActionElementalStormSpell(const TargetsInfo & targets)4401 void Battle::Interface::RedrawActionElementalStormSpell( const TargetsInfo & targets )
4402 {
4403     LocalEvent & le = LocalEvent::Get();
4404 
4405     const int icn = ICN::STORM;
4406     const int m82 = M82::FromSpell( Spell::ELEMENTALSTORM );
4407     const int spriteSize = 54;
4408     const uint32_t icnCount = fheroes2::AGG::GetICNCount( icn );
4409 
4410     std::vector<fheroes2::Sprite> spriteCache;
4411     spriteCache.reserve( icnCount );
4412     for ( uint32_t i = 0; i < icnCount; ++i ) {
4413         spriteCache.push_back( fheroes2::AGG::GetICN( icn, i ) );
4414     }
4415 
4416     Cursor::Get().SetThemes( Cursor::WAR_POINTER );
4417 
4418     _currentUnit = nullptr;
4419     for ( TargetsInfo::const_iterator it = targets.begin(); it != targets.end(); ++it )
4420         if ( ( *it ).defender && ( *it ).damage )
4421             ( *it ).defender->SwitchAnimation( Monster_Info::WNCE );
4422 
4423     if ( M82::UNKNOWN != m82 )
4424         AGG::PlaySound( m82 );
4425 
4426     Game::passAnimationDelay( Game::BATTLE_SPELL_DELAY );
4427 
4428     uint32_t frame = 0;
4429     while ( le.HandleEvents() && frame < 60 ) {
4430         CheckGlobalEvents( le );
4431 
4432         if ( Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) {
4433             RedrawPartialStart();
4434 
4435             if ( icnCount > 0 ) {
4436                 for ( int x = 0; x * spriteSize < _surfaceInnerArea.width; ++x ) {
4437                     const int idX = frame + x * 3;
4438                     const int offsetX = x * spriteSize;
4439                     for ( int y = 0; y * spriteSize < _surfaceInnerArea.height; ++y ) {
4440                         const fheroes2::Sprite & sprite = spriteCache[( idX + y ) % icnCount];
4441                         fheroes2::Blit( sprite, _mainSurface, offsetX + sprite.x(), y * spriteSize + sprite.y() );
4442                     }
4443                 }
4444             }
4445 
4446             RedrawPartialFinish();
4447 
4448             for ( TargetsInfo::const_iterator it = targets.begin(); it != targets.end(); ++it )
4449                 if ( ( *it ).defender && ( *it ).damage )
4450                     ( *it ).defender->IncreaseAnimFrame( false );
4451             ++frame;
4452         }
4453     }
4454 
4455     for ( TargetsInfo::const_iterator it = targets.begin(); it != targets.end(); ++it )
4456         if ( ( *it ).defender ) {
4457             ( *it ).defender->SwitchAnimation( Monster_Info::STATIC );
4458             _currentUnit = nullptr;
4459         }
4460 }
4461 
RedrawActionArmageddonSpell()4462 void Battle::Interface::RedrawActionArmageddonSpell()
4463 {
4464     Cursor & cursor = Cursor::Get();
4465     LocalEvent & le = LocalEvent::Get();
4466     fheroes2::Rect area = GetArea();
4467 
4468     area.height -= 37;
4469 
4470     fheroes2::Image spriteWhitening( area.width, area.height );
4471     fheroes2::Image spriteReddish( area.width, area.height );
4472     fheroes2::Copy( _mainSurface, area.x, area.y, spriteWhitening, 0, 0, area.width, area.height );
4473     fheroes2::Copy( _mainSurface, area.x, area.y, spriteReddish, 0, 0, area.width, area.height );
4474 
4475     cursor.SetThemes( Cursor::WAR_POINTER );
4476 
4477     _currentUnit = nullptr;
4478     AGG::PlaySound( M82::ARMGEDN );
4479     u32 alpha = 10;
4480 
4481     Game::passAnimationDelay( Game::BATTLE_SPELL_DELAY );
4482 
4483     while ( le.HandleEvents() && alpha < 100 ) {
4484         CheckGlobalEvents( le );
4485 
4486         if ( Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) {
4487             fheroes2::ApplyPalette( spriteWhitening, 9 );
4488             fheroes2::Blit( spriteWhitening, _mainSurface, area.x, area.y );
4489             RedrawPartialFinish();
4490 
4491             alpha += 10;
4492         }
4493     }
4494 
4495     fheroes2::ApplyPalette( spriteReddish, PAL::GetPalette( PAL::PaletteType::RED ) );
4496     fheroes2::Copy( spriteReddish, 0, 0, _mainSurface, area.x, area.y, area.width, area.height );
4497 
4498     while ( le.HandleEvents() && Mixer::isPlaying( -1 ) ) {
4499         CheckGlobalEvents( le );
4500 
4501         if ( Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) {
4502             const int32_t offsetX = static_cast<int32_t>( Rand::Get( 0, 14 ) ) - 7;
4503             const int32_t offsetY = static_cast<int32_t>( Rand::Get( 0, 14 ) ) - 7;
4504             const fheroes2::Rect initialArea( area );
4505             fheroes2::Rect original = initialArea ^ fheroes2::Rect( area.x + offsetX, area.y + offsetY, area.width, area.height );
4506 
4507             fheroes2::Rect shifted( initialArea.x - original.x, initialArea.y - original.y, original.width, original.height );
4508             if ( shifted.x < 0 ) {
4509                 const int32_t offset = -shifted.x;
4510                 shifted.x = 0;
4511                 original.x += offset;
4512                 shifted.width -= offset;
4513                 shifted.x = 0;
4514             }
4515             if ( shifted.y < 0 ) {
4516                 const int32_t offset = -shifted.y;
4517                 shifted.y = 0;
4518                 original.y += offset;
4519                 shifted.height -= offset;
4520                 shifted.y = 0;
4521             }
4522             fheroes2::Blit( spriteReddish, shifted.x, shifted.y, _mainSurface, original.x, original.y, shifted.width, shifted.height );
4523 
4524             RedrawPartialFinish();
4525         }
4526     }
4527 }
4528 
RedrawActionEarthQuakeSpell(const std::vector<int> & targets)4529 void Battle::Interface::RedrawActionEarthQuakeSpell( const std::vector<int> & targets )
4530 {
4531     Cursor & cursor = Cursor::Get();
4532     LocalEvent & le = LocalEvent::Get();
4533     fheroes2::Rect area = GetArea();
4534 
4535     uint32_t frame = 0;
4536     area.height -= 38;
4537 
4538     cursor.SetThemes( Cursor::WAR_POINTER );
4539 
4540     fheroes2::Image sprite( area.width, area.height );
4541     fheroes2::Copy( _mainSurface, area.x, area.y, sprite, 0, 0, area.width, area.height );
4542 
4543     _currentUnit = nullptr;
4544     AGG::PlaySound( M82::ERTHQUAK );
4545 
4546     Game::passAnimationDelay( Game::BATTLE_SPELL_DELAY );
4547 
4548     // draw earth quake
4549     while ( le.HandleEvents() && frame < 18 ) {
4550         CheckGlobalEvents( le );
4551 
4552         if ( Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) {
4553             const int32_t offsetX = static_cast<int32_t>( Rand::Get( 0, 14 ) ) - 7;
4554             const int32_t offsetY = static_cast<int32_t>( Rand::Get( 0, 14 ) ) - 7;
4555             const fheroes2::Rect initialArea( area );
4556             fheroes2::Rect original = initialArea ^ fheroes2::Rect( area.x + offsetX, area.y + offsetY, area.width, area.height );
4557 
4558             fheroes2::Rect shifted( initialArea.x - original.x, initialArea.y - original.y, original.width, original.height );
4559             if ( shifted.x < 0 ) {
4560                 const int32_t offset = -shifted.x;
4561                 shifted.x = 0;
4562                 original.x += offset;
4563                 shifted.width -= offset;
4564                 shifted.x = 0;
4565             }
4566             if ( shifted.y < 0 ) {
4567                 const int32_t offset = -shifted.y;
4568                 shifted.y = 0;
4569                 original.y += offset;
4570                 shifted.height -= offset;
4571                 shifted.y = 0;
4572             }
4573 
4574             fheroes2::Blit( sprite, shifted.x, shifted.y, _mainSurface, original.x, original.y, shifted.width, shifted.height );
4575 
4576             RedrawPartialFinish();
4577             ++frame;
4578         }
4579     }
4580 
4581     // draw cloud
4582     const int icn = ICN::LICHCLOD;
4583     frame = 0;
4584 
4585     AGG::PlaySound( M82::CATSND02 );
4586 
4587     Game::passAnimationDelay( Game::BATTLE_SPELL_DELAY );
4588 
4589     while ( le.HandleEvents() && frame < fheroes2::AGG::GetICNCount( icn ) ) {
4590         CheckGlobalEvents( le );
4591 
4592         if ( Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) {
4593             RedrawPartialStart();
4594 
4595             for ( std::vector<int>::const_iterator it = targets.begin(); it != targets.end(); ++it ) {
4596                 fheroes2::Point pt2 = Catapult::GetTargetPosition( *it, true );
4597 
4598                 pt2.x += area.x;
4599                 pt2.y += area.y;
4600 
4601                 const fheroes2::Sprite & spriteCloud = fheroes2::AGG::GetICN( icn, frame );
4602                 fheroes2::Blit( spriteCloud, _mainSurface, pt2.x + spriteCloud.x(), pt2.y + spriteCloud.y() );
4603             }
4604 
4605             RedrawPartialFinish();
4606 
4607             ++frame;
4608         }
4609     }
4610 }
4611 
RedrawActionRemoveMirrorImage(const std::vector<Unit * > & mirrorImages)4612 void Battle::Interface::RedrawActionRemoveMirrorImage( const std::vector<Unit *> & mirrorImages )
4613 {
4614     if ( mirrorImages.empty() ) // nothing to animate
4615         return;
4616 
4617     LocalEvent & le = LocalEvent::Get();
4618 
4619     int frame = 10;
4620     while ( le.HandleEvents() && frame > 0 ) {
4621         CheckGlobalEvents( le );
4622 
4623         if ( Game::validateAnimationDelay( Game::BATTLE_FRAME_DELAY ) ) {
4624             const uint32_t alpha = static_cast<uint32_t>( frame ) * 25;
4625             for ( std::vector<Unit *>::const_iterator it = mirrorImages.begin(); it != mirrorImages.end(); ++it ) {
4626                 if ( *it )
4627                     ( *it )->SetCustomAlpha( alpha );
4628             }
4629 
4630             Redraw();
4631 
4632             --frame;
4633         }
4634     }
4635     status.SetMessage( _( "The mirror image is destroyed!" ), true );
4636 }
4637 
RedrawTargetsWithFrameAnimation(int32_t dst,const TargetsInfo & targets,int icn,int m82,int repeatCount)4638 void Battle::Interface::RedrawTargetsWithFrameAnimation( int32_t dst, const TargetsInfo & targets, int icn, int m82, int repeatCount )
4639 {
4640     LocalEvent & le = LocalEvent::Get();
4641 
4642     uint32_t frame = 0;
4643     const fheroes2::Rect & center = Board::GetCell( dst )->GetPos();
4644 
4645     Cursor::Get().SetThemes( Cursor::WAR_POINTER );
4646 
4647     _currentUnit = nullptr;
4648     for ( TargetsInfo::const_iterator it = targets.begin(); it != targets.end(); ++it )
4649         if ( ( *it ).defender && ( *it ).damage )
4650             ( *it ).defender->SwitchAnimation( Monster_Info::WNCE );
4651 
4652     if ( M82::UNKNOWN != m82 )
4653         AGG::PlaySound( m82 );
4654 
4655     uint32_t frameCount = fheroes2::AGG::GetICNCount( icn );
4656 
4657     Game::passAnimationDelay( Game::BATTLE_SPELL_DELAY );
4658 
4659     while ( le.HandleEvents() && frame < frameCount ) {
4660         CheckGlobalEvents( le );
4661 
4662         if ( Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) {
4663             RedrawPartialStart();
4664 
4665             const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( icn, frame );
4666             fheroes2::Blit( sprite, _mainSurface, center.x + center.width / 2 + sprite.x(), center.y + center.height / 2 + sprite.y() );
4667             RedrawPartialFinish();
4668 
4669             for ( TargetsInfo::const_iterator it = targets.begin(); it != targets.end(); ++it )
4670                 if ( ( *it ).defender && ( *it ).damage )
4671                     ( *it ).defender->IncreaseAnimFrame( false );
4672             ++frame;
4673 
4674             if ( frame == frameCount && repeatCount > 0 ) {
4675                 --repeatCount;
4676                 frame = 0;
4677             }
4678         }
4679     }
4680 
4681     for ( TargetsInfo::const_iterator it = targets.begin(); it != targets.end(); ++it )
4682         if ( ( *it ).defender ) {
4683             ( *it ).defender->SwitchAnimation( Monster_Info::STATIC );
4684             _currentUnit = nullptr;
4685         }
4686 }
4687 
CalculateSpellPosition(const Battle::Unit & target,int spellICN,const fheroes2::Sprite & spellSprite)4688 fheroes2::Point CalculateSpellPosition( const Battle::Unit & target, int spellICN, const fheroes2::Sprite & spellSprite )
4689 {
4690     const fheroes2::Rect & pos = target.GetRectPosition();
4691     const fheroes2::Sprite & unitSprite = fheroes2::AGG::GetICN( target.GetMonsterSprite(), target.GetFrame() );
4692 
4693     // Bottom-left corner (default) position with spell offset applied
4694     fheroes2::Point result( pos.x + spellSprite.x(), pos.y + pos.height + cellYOffset + spellSprite.y() );
4695 
4696     switch ( spellICN ) {
4697     case ICN::SHIELD:
4698         // in front of the unit
4699         result.x += target.isReflect() ? -pos.width / ( target.isWide() ? 2 : 1 ) : pos.width;
4700         result.y += unitSprite.y() / 2;
4701         break;
4702     case ICN::BLIND: {
4703         // unit's eyes
4704         const fheroes2::Point & offset = target.animation.getBlindOffset();
4705 
4706         // calculate OG Heroes2 unit position to apply offset to
4707         const int rearCenterX = ( target.isWide() && target.isReflect() ) ? pos.width * 3 / 4 : CELLW / 2;
4708 
4709         // Overwrite result with custom blind value
4710         result.x += rearCenterX + ( target.isReflect() ? -offset.x : offset.x );
4711         result.y += offset.y;
4712         break;
4713     }
4714     case ICN::STONSKIN:
4715     case ICN::STELSKIN:
4716         // bottom center point
4717         result.x += pos.width / 2;
4718         break;
4719     default:
4720         // center point of the unit
4721         result.x += pos.width / 2;
4722         result.y += unitSprite.y() / 2;
4723         break;
4724     }
4725 
4726     if ( result.y < 0 ) {
4727         const int maximumY = fheroes2::AGG::GetAbsoluteICNHeight( spellICN );
4728         result.y = maximumY + spellSprite.y();
4729     }
4730 
4731     return result;
4732 }
4733 
RedrawTargetsWithFrameAnimation(const TargetsInfo & targets,int icn,int m82,bool wnce)4734 void Battle::Interface::RedrawTargetsWithFrameAnimation( const TargetsInfo & targets, int icn, int m82, bool wnce )
4735 {
4736     LocalEvent & le = LocalEvent::Get();
4737 
4738     uint32_t frame = 0;
4739 
4740     Cursor::Get().SetThemes( Cursor::WAR_POINTER );
4741 
4742     _currentUnit = nullptr;
4743 
4744     if ( wnce )
4745         for ( TargetsInfo::const_iterator it = targets.begin(); it != targets.end(); ++it )
4746             if ( ( *it ).defender && ( *it ).damage )
4747                 ( *it ).defender->SwitchAnimation( Monster_Info::WNCE );
4748 
4749     if ( M82::UNKNOWN != m82 )
4750         AGG::PlaySound( m82 );
4751 
4752     Game::passAnimationDelay( Game::BATTLE_SPELL_DELAY );
4753 
4754     while ( le.HandleEvents() && frame < fheroes2::AGG::GetICNCount( icn ) ) {
4755         CheckGlobalEvents( le );
4756 
4757         if ( Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) {
4758             RedrawPartialStart();
4759 
4760             for ( TargetsInfo::const_iterator it = targets.begin(); it != targets.end(); ++it )
4761                 if ( ( *it ).defender ) {
4762                     const bool reflect = ( icn == ICN::SHIELD && it->defender->isReflect() );
4763                     const fheroes2::Sprite & spellSprite = fheroes2::AGG::GetICN( icn, frame );
4764                     const fheroes2::Point & pos = CalculateSpellPosition( *it->defender, icn, spellSprite );
4765                     fheroes2::Blit( spellSprite, _mainSurface, pos.x, pos.y, reflect );
4766                 }
4767             RedrawPartialFinish();
4768 
4769             if ( wnce )
4770                 for ( TargetsInfo::const_iterator it = targets.begin(); it != targets.end(); ++it )
4771                     if ( ( *it ).defender && ( *it ).damage )
4772                         ( *it ).defender->IncreaseAnimFrame( false );
4773             ++frame;
4774         }
4775     }
4776 
4777     if ( wnce )
4778         for ( TargetsInfo::const_iterator it = targets.begin(); it != targets.end(); ++it )
4779             if ( ( *it ).defender ) {
4780                 ( *it ).defender->SwitchAnimation( Monster_Info::STATIC );
4781                 _currentUnit = nullptr;
4782             }
4783 }
4784 
RedrawTroopWithFrameAnimation(Unit & b,int icn,int m82,CreatueSpellAnimation animation)4785 void Battle::Interface::RedrawTroopWithFrameAnimation( Unit & b, int icn, int m82, CreatueSpellAnimation animation )
4786 {
4787     LocalEvent & le = LocalEvent::Get();
4788 
4789     uint32_t frame = 0;
4790     const bool reflect = ( icn == ICN::SHIELD && b.isReflect() );
4791 
4792     Cursor::Get().SetThemes( Cursor::WAR_POINTER );
4793 
4794     if ( animation == WINCE ) {
4795         _currentUnit = nullptr;
4796         b.SwitchAnimation( Monster_Info::WNCE );
4797     }
4798     else if ( animation == RESURRECT ) {
4799         _currentUnit = nullptr;
4800         b.SwitchAnimation( Monster_Info::KILL, true );
4801     }
4802 
4803     if ( M82::UNKNOWN != m82 )
4804         AGG::PlaySound( m82 );
4805 
4806     Game::passAnimationDelay( Game::BATTLE_SPELL_DELAY );
4807 
4808     while ( le.HandleEvents() && frame < fheroes2::AGG::GetICNCount( icn ) ) {
4809         CheckGlobalEvents( le );
4810 
4811         if ( Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) {
4812             RedrawPartialStart();
4813 
4814             const fheroes2::Sprite & spellSprite = fheroes2::AGG::GetICN( icn, frame );
4815             const fheroes2::Point & pos = CalculateSpellPosition( b, icn, spellSprite );
4816             fheroes2::Blit( spellSprite, _mainSurface, pos.x, pos.y, reflect );
4817             RedrawPartialFinish();
4818 
4819             if ( animation != NONE ) {
4820                 if ( animation == RESURRECT ) {
4821                     if ( b.isFinishAnimFrame() )
4822                         b.SwitchAnimation( Monster_Info::STATIC );
4823                 }
4824                 b.IncreaseAnimFrame( false );
4825             }
4826             ++frame;
4827         }
4828     }
4829 
4830     if ( animation != NONE ) {
4831         b.SwitchAnimation( Monster_Info::STATIC );
4832         _currentUnit = nullptr;
4833     }
4834 }
4835 
RedrawBridgeAnimation(const bool bridgeDownAnimation)4836 void Battle::Interface::RedrawBridgeAnimation( const bool bridgeDownAnimation )
4837 {
4838     LocalEvent & le = LocalEvent::Get();
4839 
4840     _bridgeAnimation.animationIsRequired = true;
4841 
4842     _bridgeAnimation.currentFrameId = bridgeDownAnimation ? BridgeMovementAnimation::UP_POSITION : BridgeMovementAnimation::DOWN_POSITION;
4843 
4844     if ( bridgeDownAnimation )
4845         AGG::PlaySound( M82::DRAWBRG );
4846 
4847     while ( le.HandleEvents() ) {
4848         if ( bridgeDownAnimation ) {
4849             if ( _bridgeAnimation.currentFrameId < BridgeMovementAnimation::DOWN_POSITION )
4850                 break;
4851         }
4852         else {
4853             if ( _bridgeAnimation.currentFrameId > BridgeMovementAnimation::UP_POSITION )
4854                 break;
4855         }
4856 
4857         CheckGlobalEvents( le );
4858 
4859         if ( Game::validateAnimationDelay( Game::BATTLE_BRIDGE_DELAY ) ) {
4860             Redraw();
4861 
4862             if ( bridgeDownAnimation )
4863                 --_bridgeAnimation.currentFrameId;
4864             else
4865                 ++_bridgeAnimation.currentFrameId;
4866         }
4867     }
4868 
4869     _bridgeAnimation.animationIsRequired = false;
4870 
4871     if ( !bridgeDownAnimation )
4872         AGG::PlaySound( M82::DRAWBRG );
4873 }
4874 
IdleTroopsAnimation(void)4875 bool Battle::Interface::IdleTroopsAnimation( void )
4876 {
4877     if ( Game::validateAnimationDelay( Game::BATTLE_IDLE_DELAY ) ) {
4878         const bool redrawNeeded = arena.GetForce1().animateIdleUnits();
4879         return arena.GetForce2().animateIdleUnits() || redrawNeeded;
4880     }
4881 
4882     return false;
4883 }
4884 
ResetIdleTroopAnimation(void)4885 void Battle::Interface::ResetIdleTroopAnimation( void )
4886 {
4887     arena.GetForce1().resetIdleAnimation();
4888     arena.GetForce2().resetIdleAnimation();
4889 }
4890 
CheckGlobalEvents(LocalEvent & le)4891 void Battle::Interface::CheckGlobalEvents( LocalEvent & le )
4892 {
4893     if ( Game::validateAnimationDelay( Game::BATTLE_SELECTED_UNIT_DELAY ) )
4894         UpdateContourColor();
4895 
4896     // animate heroes
4897     if ( Game::validateAnimationDelay( Game::BATTLE_OPPONENTS_DELAY ) ) {
4898         if ( opponent1 )
4899             opponent1->Update();
4900 
4901         if ( opponent2 )
4902             opponent2->Update();
4903 
4904         humanturn_redraw = true;
4905     }
4906 
4907     // flags animation
4908     if ( Game::validateAnimationDelay( Game::BATTLE_FLAGS_DELAY ) ) {
4909         ++animation_flags_frame;
4910         humanturn_redraw = true;
4911     }
4912 
4913     // break auto battle
4914     if ( arena.CanBreakAutoBattle()
4915          && ( le.MouseClickLeft( btn_auto.area() )
4916               || ( le.KeyPress()
4917                    && ( Game::HotKeyPressEvent( Game::EVENT_BATTLE_AUTOSWITCH )
4918                         || ( Game::HotKeyPressEvent( Game::EVENT_BATTLE_RETREAT )
4919                              && Dialog::YES == Dialog::Message( "", _( "Break auto battle?" ), Font::BIG, Dialog::YES | Dialog::NO ) ) ) ) ) ) {
4920         arena.BreakAutoBattle();
4921     }
4922 }
4923 
ProcessingHeroDialogResult(int res,Actions & a)4924 void Battle::Interface::ProcessingHeroDialogResult( int res, Actions & a )
4925 {
4926     switch ( res ) {
4927     // cast
4928     case 1: {
4929         const HeroBase * hero = _currentUnit->GetCurrentOrArmyCommander();
4930 
4931         if ( hero ) {
4932             if ( hero->HaveSpellBook() ) {
4933                 std::string msg;
4934                 if ( arena.isDisableCastSpell( Spell::NONE, &msg ) )
4935                     Dialog::Message( "", msg, Font::BIG, Dialog::OK );
4936                 else {
4937                     std::function<void( const std::string & )> statusCallback = [this]( const std::string & statusStr ) {
4938                         status.SetMessage( statusStr );
4939                         status.Redraw();
4940                     };
4941 
4942                     const Spell spell = hero->OpenSpellBook( SpellBook::Filter::CMBT, true, &statusCallback );
4943                     if ( spell.isValid() ) {
4944                         std::string error;
4945 
4946                         if ( arena.isDisableCastSpell( spell, &msg ) )
4947                             Dialog::Message( "", msg, Font::BIG, Dialog::OK );
4948                         else if ( hero->CanCastSpell( spell, &error ) ) {
4949                             if ( spell.isApplyWithoutFocusObject() ) {
4950                                 a.push_back( Command( CommandType::MSG_BATTLE_CAST, spell.GetID(), -1 ) );
4951                                 humanturn_redraw = true;
4952                                 humanturn_exit = true;
4953                             }
4954                             else
4955                                 humanturn_spell = spell;
4956                         }
4957                         else if ( !error.empty() )
4958                             Dialog::Message( _( "Error" ), error, Font::BIG, Dialog::OK );
4959                     }
4960                 }
4961             }
4962             else
4963                 Dialog::Message( "", _( "No spells to cast." ), Font::BIG, Dialog::OK );
4964         }
4965         break;
4966     }
4967 
4968     // retreat
4969     case 2: {
4970         if ( arena.CanRetreatOpponent( _currentUnit->GetCurrentOrArmyColor() ) ) {
4971             if ( Dialog::YES == Dialog::Message( "", _( "Are you sure you want to retreat?" ), Font::BIG, Dialog::YES | Dialog::NO ) ) {
4972                 a.push_back( Command( CommandType::MSG_BATTLE_RETREAT ) );
4973                 a.push_back( Command( CommandType::MSG_BATTLE_END_TURN, _currentUnit->GetUID() ) );
4974                 humanturn_exit = true;
4975             }
4976         }
4977         else {
4978             Dialog::Message( "", _( "Retreat disabled" ), Font::BIG, Dialog::OK );
4979         }
4980         break;
4981     }
4982 
4983     // surrender
4984     case 3: {
4985         if ( arena.CanSurrenderOpponent( _currentUnit->GetCurrentOrArmyColor() ) ) {
4986             const HeroBase * enemy = arena.getEnemyCommander( arena.GetCurrentColor() );
4987 
4988             if ( enemy ) {
4989                 const s32 cost = arena.GetCurrentForce().GetSurrenderCost();
4990                 Kingdom & kingdom = world.GetKingdom( arena.GetCurrentColor() );
4991 
4992                 if ( DialogBattleSurrender( *enemy, cost, kingdom ) ) {
4993                     a.push_back( Command( CommandType::MSG_BATTLE_SURRENDER ) );
4994                     a.push_back( Command( CommandType::MSG_BATTLE_END_TURN, _currentUnit->GetUID() ) );
4995                     humanturn_exit = true;
4996                 }
4997             }
4998         }
4999         else {
5000             Dialog::Message( "", _( "Surrender disabled" ), Font::BIG, Dialog::OK );
5001         }
5002         break;
5003     }
5004 
5005     default:
5006         break;
5007     }
5008 }
5009 
PopupDamageInfo()5010 Battle::PopupDamageInfo::PopupDamageInfo()
5011     : Dialog::FrameBorder( 5 )
5012     , _cell( nullptr )
5013     , _attacker( nullptr )
5014     , _defender( nullptr )
5015     , _redraw( false )
5016 {}
5017 
SetInfo(const Cell * cell,const Unit * attacker,const Unit * defender,const fheroes2::Point & offset)5018 void Battle::PopupDamageInfo::SetInfo( const Cell * cell, const Unit * attacker, const Unit * defender, const fheroes2::Point & offset )
5019 {
5020     if ( cell == nullptr || attacker == nullptr || defender == nullptr ) {
5021         return;
5022     }
5023 
5024     if ( !Settings::Get().ExtBattleShowDamage() || !Game::validateAnimationDelay( Game::BATTLE_POPUP_DELAY ) ) {
5025         return;
5026     }
5027 
5028     _redraw = true;
5029     _cell = cell;
5030     _attacker = attacker;
5031     _defender = defender;
5032 
5033     const fheroes2::Rect & rt = cell->GetPos();
5034     SetPosition( rt.x + rt.width + offset.x, rt.y + offset.y, 20, 20 );
5035 }
5036 
Reset(void)5037 void Battle::PopupDamageInfo::Reset( void )
5038 {
5039     if ( _redraw ) {
5040         restorer.restore();
5041         _redraw = false;
5042         _cell = nullptr;
5043         _attacker = nullptr;
5044         _defender = nullptr;
5045     }
5046     Game::AnimateResetDelay( Game::BATTLE_POPUP_DELAY );
5047 }
5048 
Redraw(int maxw,int)5049 void Battle::PopupDamageInfo::Redraw( int maxw, int /*maxh*/ )
5050 {
5051     if ( _redraw ) {
5052         uint32_t tmp1 = _attacker->CalculateMinDamage( *_defender );
5053         uint32_t tmp2 = _attacker->CalculateMaxDamage( *_defender );
5054 
5055         if ( _attacker->Modes( SP_BLESS ) )
5056             tmp1 = tmp2;
5057         else if ( _attacker->Modes( SP_CURSE ) )
5058             tmp2 = tmp1;
5059 
5060         std::string str = tmp1 == tmp2 ? _( "Damage: %{max}" ) : _( "Damage: %{min} - %{max}" );
5061 
5062         StringReplace( str, "%{min}", tmp1 );
5063         StringReplace( str, "%{max}", tmp2 );
5064 
5065         Text text1( str, Font::SMALL );
5066 
5067         tmp1 = _defender->HowManyWillKilled( tmp1 );
5068         tmp2 = _defender->HowManyWillKilled( tmp2 );
5069 
5070         if ( tmp1 > _defender->GetCount() )
5071             tmp1 = _defender->GetCount();
5072         if ( tmp2 > _defender->GetCount() )
5073             tmp2 = _defender->GetCount();
5074 
5075         str = tmp1 == tmp2 ? _( "Perish: %{max}" ) : _( "Perish: %{min} - %{max}" );
5076 
5077         StringReplace( str, "%{min}", tmp1 );
5078         StringReplace( str, "%{max}", tmp2 );
5079 
5080         Text text2( str, Font::SMALL );
5081 
5082         int tw = 5 + ( text1.w() > text2.w() ? text1.w() : text2.w() );
5083         int th = ( text1.h() + text2.h() );
5084 
5085         const fheroes2::Rect & borderArea = GetArea();
5086         const fheroes2::Rect & borderRect = GetRect();
5087         const fheroes2::Rect & pos = _cell->GetPos();
5088 
5089         int tx = borderRect.x;
5090         int ty = borderRect.y;
5091 
5092         if ( borderRect.x + borderRect.width > maxw ) {
5093             tx = maxw - borderRect.width - 5;
5094             ty = pos.y - pos.height;
5095         }
5096 
5097         if ( borderRect.x != tx || borderRect.y != ty || borderArea.width != tw || borderArea.height != th ) {
5098             SetPosition( tx, ty, tw, th );
5099         }
5100 
5101         const fheroes2::Rect & currectArea = GetRect();
5102         Dialog::FrameBorder::RenderOther( fheroes2::AGG::GetICN( ICN::CELLWIN, 1 ), currectArea );
5103 
5104         text1.Blit( borderArea.x, borderArea.y );
5105         text2.Blit( borderArea.x, borderArea.y + borderArea.height / 2 );
5106     }
5107 }
5108