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 <queue>
24 
25 #include "agg.h"
26 #include "agg_image.h"
27 #include "army.h"
28 #include "battle.h"
29 #include "battle_arena.h"
30 #include "battle_army.h"
31 #include "battle_interface.h"
32 #include "cursor.h"
33 #include "game.h"
34 #include "game_delays.h"
35 #include "heroes.h"
36 #include "icn.h"
37 #include "kingdom.h"
38 #include "luck.h"
39 #include "morale.h"
40 #include "mus.h"
41 #include "race.h"
42 #include "settings.h"
43 #include "text.h"
44 #include "tools.h"
45 #include "translations.h"
46 
47 namespace
48 {
49     // DialogBattleSummary text related values
50     const int bsTextWidth = 270;
51     const int bsTextXOffset = 25;
52     const int bsTextYOffset = 175;
53     const int bsTextIndent = 30;
54 
55     class LoopedAnimation
56     {
57     public:
LoopedAnimation(int icnId=0,bool loop=false)58         explicit LoopedAnimation( int icnId = 0, bool loop = false )
59             : _icnId( icnId )
60             , _frameId( 0 )
61             , _counter( 0 )
62             , _finished( false )
63             , _loop( loop )
64         {
65             _frameId = ICN::AnimationFrame( _icnId, 1, _counter );
66         }
67 
frameId()68         uint32_t frameId()
69         {
70             if ( _finished )
71                 return _frameId;
72 
73             ++_counter;
74             uint32_t nextId = ICN::AnimationFrame( _icnId, 1, _counter );
75             if ( nextId < _frameId ) {
76                 if ( _loop ) {
77                     _counter = 0;
78                     nextId = ICN::AnimationFrame( _icnId, 1, _counter );
79                     std::swap( nextId, _frameId );
80                     return nextId;
81                 }
82                 else {
83                     _finished = true;
84                 }
85             }
86             else {
87                 std::swap( nextId, _frameId );
88                 return nextId;
89             }
90 
91             return _frameId;
92         }
93 
isFinished() const94         bool isFinished() const
95         {
96             return _finished;
97         }
98 
id() const99         int id() const
100         {
101             return _icnId;
102         }
103 
104     private:
105         int _icnId;
106         uint32_t _frameId;
107         uint32_t _counter;
108         bool _finished;
109         bool _loop;
110     };
111 
112     class LoopedAnimationSequence
113     {
114     public:
push(int icnId,bool loop)115         void push( int icnId, bool loop )
116         {
117             _queue.push( LoopedAnimation( icnId, loop ) );
118         }
119 
frameId()120         uint32_t frameId()
121         {
122             if ( isFinished() )
123                 return 0;
124 
125             return _queue.front().frameId();
126         }
127 
nextFrame()128         bool nextFrame() // returns true only if there is some frames left
129         {
130             if ( !_queue.empty() && _queue.front().isFinished() )
131                 _queue.pop();
132 
133             return _queue.empty();
134         }
135 
isFinished() const136         bool isFinished() const
137         {
138             return _queue.empty();
139         }
140 
id() const141         int id() const
142         {
143             if ( isFinished() )
144                 return 0;
145 
146             return _queue.front().id();
147         }
148 
149     private:
150         std::queue<LoopedAnimation> _queue;
151     };
152 }
153 
154 namespace Battle
155 {
156     void GetSummaryParams( int res1, int res2, const HeroBase & hero, u32 exp, LoopedAnimationSequence & sequence, std::string & title, std::string & msg );
157     void RedrawBattleSettings( const std::vector<fheroes2::Rect> & areas );
158     void RedrawOnOffSetting( const fheroes2::Rect & area, const std::string & name, uint32_t index, bool isSet );
159 }
160 
RedrawBattleSettings(const std::vector<fheroes2::Rect> & areas)161 void Battle::RedrawBattleSettings( const std::vector<fheroes2::Rect> & areas )
162 {
163     fheroes2::Display & display = fheroes2::Display::instance();
164     const Settings & conf = Settings::Get();
165 
166     // Speed setting
167     const Text speedTitle( _( "Speed" ), Font::SMALL );
168     speedTitle.Blit( areas[0].x + ( areas[0].width - speedTitle.w() ) / 2, areas[0].y - 13 );
169 
170     int speed = Settings::Get().BattleSpeed();
171     std::string str = _( "Speed: %{speed}" );
172     StringReplace( str, "%{speed}", speed );
173 
174     const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::CSPANEL, ( speed < 5 ? 0 : ( speed < 8 ? 1 : 2 ) ) );
175     fheroes2::Blit( sprite, fheroes2::Display::instance(), areas[0].x, areas[0].y );
176     Text text( str, Font::SMALL );
177     text.Blit( areas[0].x + ( sprite.width() - text.w() ) / 2, areas[0].y + sprite.height() + 3 );
178 
179     RedrawOnOffSetting( areas[2], _( "Auto Spell Casting" ), 6, conf.BattleAutoSpellcast() );
180     RedrawOnOffSetting( areas[3], _( "Grid" ), 8, conf.BattleShowGrid() );
181     RedrawOnOffSetting( areas[4], _( "Shadow Movement" ), 10, conf.BattleShowMoveShadow() );
182     RedrawOnOffSetting( areas[5], _( "Shadow Cursor" ), 12, conf.BattleShowMouseShadow() );
183 
184     display.render();
185 }
186 
RedrawOnOffSetting(const fheroes2::Rect & area,const std::string & name,uint32_t index,bool isSet)187 void Battle::RedrawOnOffSetting( const fheroes2::Rect & area, const std::string & name, uint32_t index, bool isSet )
188 {
189     fheroes2::Display & display = fheroes2::Display::instance();
190     const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::CSPANEL, isSet ? index + 1 : index );
191     const int textOffset = 2;
192 
193     TextBox upperText( name, Font::SMALL, area.width );
194     upperText.Blit( area.x + ( area.width - upperText.w() ) / 2, area.y - upperText.h() - textOffset );
195 
196     fheroes2::Blit( sprite, display, area.x, area.y );
197 
198     const Text lowerText( isSet ? _( "On" ) : _( "Off" ), Font::SMALL );
199     lowerText.Blit( area.x + ( area.width - lowerText.w() ) / 2, area.y + area.height + textOffset );
200 }
201 
DialogBattleSettings(void)202 void Battle::DialogBattleSettings( void )
203 {
204     fheroes2::Display & display = fheroes2::Display::instance();
205     LocalEvent & le = LocalEvent::Get();
206     Settings & conf = Settings::Get();
207 
208     // setup cursor
209     const CursorRestorer cursorRestorer( true, Cursor::POINTER );
210 
211     const bool isEvilInterface = conf.ExtGameEvilInterface();
212 
213     const fheroes2::Sprite & dialog = fheroes2::AGG::GetICN( ( isEvilInterface ? ICN::CSPANBKE : ICN::CSPANBKG ), 0 );
214     const fheroes2::Sprite & dialogShadow = fheroes2::AGG::GetICN( ( isEvilInterface ? ICN::CSPANBKE : ICN::CSPANBKG ), 1 );
215 
216     const fheroes2::Point dialogOffset( ( display.width() - dialog.width() ) / 2, ( display.height() - dialog.height() ) / 2 );
217     const fheroes2::Point shadowOffset( dialogOffset.x - BORDERWIDTH, dialogOffset.y );
218 
219     fheroes2::ImageRestorer back( display, shadowOffset.x, shadowOffset.y, dialog.width() + BORDERWIDTH, dialog.height() + BORDERWIDTH );
220     const fheroes2::Rect pos_rt( dialogOffset.x, dialogOffset.y, dialog.width(), dialog.height() );
221 
222     fheroes2::Fill( display, pos_rt.x, pos_rt.y, pos_rt.width, pos_rt.height, 0 );
223     fheroes2::Blit( dialogShadow, display, pos_rt.x - BORDERWIDTH, pos_rt.y + BORDERWIDTH );
224     fheroes2::Blit( dialog, display, pos_rt.x, pos_rt.y );
225 
226     const fheroes2::Sprite & panelSprite = fheroes2::AGG::GetICN( ICN::CSPANEL, 0 );
227     const int32_t panelWidth = panelSprite.width();
228     const int32_t panelHeight = panelSprite.height();
229 
230     std::vector<fheroes2::Rect> optionAreas;
231     optionAreas.reserve( 6 );
232     optionAreas.emplace_back( pos_rt.x + 36, pos_rt.y + 47, panelWidth, panelHeight ); // speed
233     optionAreas.emplace_back( pos_rt.x + 128, pos_rt.y + 47, panelWidth, panelHeight ); // info
234     optionAreas.emplace_back( pos_rt.x + 220, pos_rt.y + 47, panelWidth, panelHeight ); // auto spell cast
235     optionAreas.emplace_back( pos_rt.x + 36, pos_rt.y + 157, panelWidth, panelHeight ); // grid
236     optionAreas.emplace_back( pos_rt.x + 128, pos_rt.y + 157, panelWidth, panelHeight ); // move shadow
237     optionAreas.emplace_back( pos_rt.x + 220, pos_rt.y + 157, panelWidth, panelHeight ); // cursor shadow
238 
239     fheroes2::Button btn_ok( pos_rt.x + 112, pos_rt.y + 252, ( isEvilInterface ? ICN::CSPANBTE : ICN::CSPANBTN ), 0, 1 );
240     btn_ok.draw();
241 
242     RedrawBattleSettings( optionAreas );
243 
244     display.render();
245 
246     bool saveConfiguration = false;
247 
248     while ( le.HandleEvents() ) {
249         le.MousePressLeft( btn_ok.area() ) ? btn_ok.drawOnPress() : btn_ok.drawOnRelease();
250 
251         bool saveSpeed = false;
252         if ( le.MouseClickLeft( optionAreas[0] ) ) {
253             conf.SetBattleSpeed( conf.BattleSpeed() % 10 + 1 );
254             saveSpeed = true;
255         }
256         else if ( le.MouseWheelUp( optionAreas[0] ) ) {
257             conf.SetBattleSpeed( conf.BattleSpeed() + 1 );
258             saveSpeed = true;
259         }
260         else if ( le.MouseWheelDn( optionAreas[0] ) ) {
261             conf.SetBattleSpeed( conf.BattleSpeed() - 1 );
262             saveSpeed = true;
263         }
264         else if ( le.MousePressRight( optionAreas[0] ) ) {
265             Dialog::Message( _( "Speed" ), _( "Set the speed of combat actions and animations." ), Font::BIG );
266         }
267         if ( saveSpeed ) {
268             Game::UpdateGameSpeed();
269         }
270 
271         // For future use
272         // else if ( le.MousePressRight( optionAreas[1] ) ) {
273         //     Dialog::Message( _( "Monster Info" ), _( "Toggle the monster info window, which shows information on the active and targeted monsters." ), Font::BIG );
274         // }
275 
276         bool saveAutoSpellCast = false;
277         if ( le.MouseClickLeft( optionAreas[2] ) ) {
278             conf.setBattleAutoSpellcast( !conf.BattleAutoSpellcast() );
279             saveAutoSpellCast = true;
280         }
281         else if ( le.MousePressRight( optionAreas[2] ) ) {
282             Dialog::Message(
283                 _( "Auto Spell Casting" ),
284                 _( "Toggle whether or not the computer will cast spells for you when auto combat is on. (Note: This does not affect spell casting for computer players in any way, nor does it affect quick combat.)" ),
285                 Font::BIG );
286         }
287 
288         bool saveShowGrid = false;
289         if ( le.MouseClickLeft( optionAreas[3] ) ) {
290             conf.SetBattleGrid( !conf.BattleShowGrid() );
291             saveShowGrid = true;
292         }
293         else if ( le.MousePressRight( optionAreas[3] ) ) {
294             Dialog::Message(
295                 _( "Grid" ),
296                 _( "Toggle the hex grid on or off. The hex grid always underlies movement, even if turned off. This switch only determines if the grid is visible." ),
297                 Font::BIG );
298         }
299 
300         bool saveShowMoveShadow = false;
301         if ( le.MouseClickLeft( optionAreas[4] ) ) {
302             conf.SetBattleMovementShaded( !conf.BattleShowMoveShadow() );
303             saveShowMoveShadow = true;
304         }
305         else if ( le.MousePressRight( optionAreas[4] ) ) {
306             Dialog::Message( _( "Shadow Movement" ), _( "Toggle on or off shadows showing where your creatures can move and attack." ), Font::BIG );
307         }
308 
309         bool saveShowMouseShadow = false;
310         if ( le.MouseClickLeft( optionAreas[5] ) ) {
311             conf.SetBattleMouseShaded( !conf.BattleShowMouseShadow() );
312             saveShowMouseShadow = true;
313         }
314         else if ( le.MousePressRight( optionAreas[5] ) ) {
315             Dialog::Message( _( "Shadow Cursor" ), _( "Toggle on or off a shadow showing the current hex location of the mouse cursor." ), Font::BIG );
316         }
317 
318         if ( HotKeyCloseWindow || le.MouseClickLeft( btn_ok.area() ) ) {
319             break;
320         }
321 
322         if ( saveSpeed || saveAutoSpellCast || saveShowGrid || saveShowMoveShadow || saveShowMouseShadow ) {
323             // redraw
324             fheroes2::Blit( dialog, display, pos_rt.x, pos_rt.y );
325             RedrawBattleSettings( optionAreas );
326             display.render();
327 
328             saveConfiguration = true;
329         }
330     }
331 
332     if ( saveConfiguration ) {
333         conf.Save( "fheroes2.cfg" );
334     }
335 }
336 
GetSummaryParams(int res1,int res2,const HeroBase & hero,u32 exp,LoopedAnimationSequence & sequence,std::string & title,std::string & msg)337 void Battle::GetSummaryParams( int res1, int res2, const HeroBase & hero, u32 exp, LoopedAnimationSequence & sequence, std::string & title, std::string & msg )
338 {
339     if ( res1 & RESULT_WINS ) {
340         sequence.push( ICN::WINCMBT, true );
341         if ( res2 & RESULT_SURRENDER )
342             title.append( _( "The enemy has surrendered!" ) );
343         else if ( res2 & RESULT_RETREAT )
344             title.append( _( "The enemy has fled!" ) );
345         else
346             title.append( _( "A glorious victory!" ) );
347 
348         if ( hero.isHeroes() ) {
349             msg.append( _( "For valor in combat, %{name} receives %{exp} experience." ) );
350             StringReplace( msg, "%{name}", hero.GetName() );
351             StringReplace( msg, "%{exp}", exp );
352         }
353     }
354     else if ( res1 & RESULT_RETREAT ) {
355         sequence.push( ICN::CMBTFLE1, false );
356         sequence.push( ICN::CMBTFLE2, false );
357         sequence.push( ICN::CMBTFLE3, false );
358         msg.append( _( "The cowardly %{name} flees from battle." ) );
359         StringReplace( msg, "%{name}", hero.GetName() );
360     }
361     else if ( res1 & RESULT_SURRENDER ) {
362         sequence.push( ICN::CMBTSURR, true );
363         msg.append( _( "%{name} surrenders to the enemy, and departs in shame." ) );
364         StringReplace( msg, "%{name}", hero.GetName() );
365     }
366     else {
367         sequence.push( ICN::CMBTLOS1, false );
368         sequence.push( ICN::CMBTLOS2, false );
369         sequence.push( ICN::CMBTLOS3, true );
370         msg.append( _( "Your force suffer a bitter defeat, and %{name} abandons your cause." ) );
371         StringReplace( msg, "%{name}", hero.GetName() );
372     }
373 }
374 
375 // Returns true if player want to restart the battle
DialogBattleSummary(const Result & res,const std::vector<Artifact> & artifacts,bool allowToCancel) const376 bool Battle::Arena::DialogBattleSummary( const Result & res, const std::vector<Artifact> & artifacts, bool allowToCancel ) const
377 {
378     fheroes2::Display & display = fheroes2::Display::instance();
379     LocalEvent & le = LocalEvent::Get();
380     const Settings & conf = Settings::Get();
381 
382     const Troops killed1 = army1->GetKilledTroops();
383     const Troops killed2 = army2->GetKilledTroops();
384 
385     // setup cursor
386     const CursorRestorer cursorRestorer( true, Cursor::POINTER );
387 
388     std::string msg;
389     std::string title;
390     LoopedAnimationSequence sequence;
391 
392     if ( ( res.army1 & RESULT_WINS ) && army1->GetCommander() && army1->GetCommander()->isControlHuman() ) {
393         GetSummaryParams( res.army1, res.army2, *army1->GetCommander(), res.exp1, sequence, title, msg );
394         AGG::PlayMusic( MUS::BATTLEWIN, false );
395     }
396     else if ( ( res.army2 & RESULT_WINS ) && army2->GetCommander() && army2->GetCommander()->isControlHuman() ) {
397         GetSummaryParams( res.army2, res.army1, *army2->GetCommander(), res.exp2, sequence, title, msg );
398         AGG::PlayMusic( MUS::BATTLEWIN, false );
399     }
400     else if ( army1->GetCommander() && army1->GetCommander()->isControlHuman() ) {
401         GetSummaryParams( res.army1, res.army2, *army1->GetCommander(), res.exp1, sequence, title, msg );
402         AGG::PlayMusic( MUS::BATTLELOSE, false );
403     }
404     else if ( army2->GetCommander() && army2->GetCommander()->isControlHuman() ) {
405         GetSummaryParams( res.army2, res.army1, *army2->GetCommander(), res.exp2, sequence, title, msg );
406         AGG::PlayMusic( MUS::BATTLELOSE, false );
407     }
408     else
409         // AI move
410         if ( army1->GetCommander() && army1->GetCommander()->isControlAI() ) {
411         // AI wins
412         if ( res.army1 & RESULT_WINS ) {
413             sequence.push( ICN::CMBTLOS1, false );
414             sequence.push( ICN::CMBTLOS2, false );
415             sequence.push( ICN::CMBTLOS3, false );
416             msg.append( _( "Your force suffer a bitter defeat." ) );
417         }
418         else
419             // Human wins
420             if ( res.army2 & RESULT_WINS ) {
421             sequence.push( ICN::WINCMBT, true );
422             msg.append( _( "A glorious victory!" ) );
423         }
424     }
425 
426     if ( sequence.isFinished() ) // Cannot be!
427         sequence.push( ICN::UNKNOWN, false );
428 
429     const bool isEvilInterface = conf.ExtGameEvilInterface();
430     const fheroes2::Sprite & dialog = fheroes2::AGG::GetICN( ( isEvilInterface ? ICN::WINLOSEE : ICN::WINLOSE ), 0 );
431     const fheroes2::Sprite & dialogShadow = fheroes2::AGG::GetICN( ( isEvilInterface ? ICN::WINLOSEE : ICN::WINLOSE ), 1 );
432 
433     const fheroes2::Point dialogOffset( ( display.width() - dialog.width() ) / 2, ( display.height() - dialog.height() ) / 2 );
434     const fheroes2::Point shadowOffset( dialogOffset.x - BORDERWIDTH, dialogOffset.y );
435 
436     fheroes2::ImageRestorer back( display, shadowOffset.x, shadowOffset.y, dialog.width() + BORDERWIDTH, dialog.height() + BORDERWIDTH - 1 );
437     const fheroes2::Rect pos_rt( dialogOffset.x, dialogOffset.y, dialog.width(), dialog.height() );
438 
439     fheroes2::Blit( dialogShadow, display, pos_rt.x - BORDERWIDTH, pos_rt.y + BORDERWIDTH - 1 );
440     fheroes2::Blit( dialog, display, pos_rt.x, pos_rt.y );
441 
442     const int anime_ox = 47;
443     const int anime_oy = 36;
444 
445     const fheroes2::Sprite & sequenceBase = fheroes2::AGG::GetICN( sequence.id(), 0 );
446     const fheroes2::Sprite & sequenceStart = fheroes2::AGG::GetICN( sequence.id(), 1 );
447 
448     fheroes2::Blit( sequenceBase, display, pos_rt.x + anime_ox + sequenceBase.x(), pos_rt.y + anime_oy + sequenceBase.y() );
449     fheroes2::Blit( sequenceStart, display, pos_rt.x + anime_ox + sequenceStart.x(), pos_rt.y + anime_oy + sequenceStart.y() );
450 
451     int32_t messageYOffset = 0;
452     if ( !title.empty() ) {
453         TextBox box( title, Font::YELLOW_BIG, bsTextWidth );
454         box.Blit( pos_rt.x + bsTextXOffset, pos_rt.y + bsTextYOffset );
455         messageYOffset = bsTextIndent;
456     }
457 
458     if ( !msg.empty() ) {
459         TextBox box( msg, Font::BIG, bsTextWidth );
460         box.Blit( pos_rt.x + bsTextXOffset, pos_rt.y + bsTextYOffset + messageYOffset );
461     }
462 
463     // battlefield casualties
464     Text text( _( "Battlefield Casualties" ), Font::SMALL );
465     text.Blit( pos_rt.x + ( pos_rt.width - text.w() ) / 2, pos_rt.y + 270 );
466 
467     // attacker
468     text.Set( _( "Attacker" ), Font::SMALL );
469     text.Blit( pos_rt.x + ( pos_rt.width - text.w() ) / 2, pos_rt.y + 285 );
470 
471     if ( killed1.isValid() )
472         Army::DrawMons32Line( killed1, pos_rt.x + 25, pos_rt.y + 303, 270 );
473     else {
474         text.Set( _( "None" ), Font::SMALL );
475         text.Blit( pos_rt.x + ( pos_rt.width - text.w() ) / 2, pos_rt.y + 300 );
476     }
477 
478     // defender
479     text.Set( _( "Defender" ), Font::SMALL );
480     text.Blit( pos_rt.x + ( pos_rt.width - text.w() ) / 2, pos_rt.y + 345 );
481 
482     if ( killed2.isValid() )
483         Army::DrawMons32Line( killed2, pos_rt.x + 25, pos_rt.y + 363, 270 );
484     else {
485         text.Set( _( "None" ), Font::SMALL );
486         text.Blit( pos_rt.x + ( pos_rt.width - text.w() ) / 2, pos_rt.y + 360 );
487     }
488 
489     if ( allowToCancel ) {
490         const fheroes2::Sprite & buttonOverride = fheroes2::Crop( dialog, 20, 410, 84, 32 );
491         fheroes2::Blit( buttonOverride, display, pos_rt.x + 116, pos_rt.y + 410 );
492     }
493 
494     const int buttonOffset = allowToCancel ? 39 : 120;
495     const int buttonOkICN
496         = isEvilInterface ? ( allowToCancel ? ICN::NON_UNIFORM_EVIL_OKAY_BUTTON : ICN::WINCMBBE ) : ( allowToCancel ? ICN::NON_UNIFORM_GOOD_OKAY_BUTTON : ICN::WINCMBTB );
497     const int buttonCancelICN = isEvilInterface ? ICN::NON_UNIFORM_EVIL_RESTART_BUTTON : ICN::NON_UNIFORM_GOOD_RESTART_BUTTON;
498 
499     std::unique_ptr<fheroes2::ButtonBase> btnOk;
500     fheroes2::ButtonSprite btnCancel = fheroes2::makeButtonWithShadow( pos_rt.x + buttonOffset + 129, pos_rt.y + 410, fheroes2::AGG::GetICN( buttonCancelICN, 0 ),
501                                                                        fheroes2::AGG::GetICN( buttonCancelICN, 1 ), display );
502 
503     if ( allowToCancel ) {
504         btnCancel.draw();
505         btnOk.reset( new fheroes2::ButtonSprite( fheroes2::makeButtonWithShadow( pos_rt.x + buttonOffset, pos_rt.y + 410, fheroes2::AGG::GetICN( buttonOkICN, 0 ),
506                                                                                  fheroes2::AGG::GetICN( buttonOkICN, 1 ), display ) ) );
507     }
508     else {
509         btnOk.reset( new fheroes2::Button( pos_rt.x + buttonOffset, pos_rt.y + 410, buttonOkICN, 0, 1 ) );
510     }
511     btnOk->draw();
512 
513     display.render();
514 
515     while ( le.HandleEvents() ) {
516         le.MousePressLeft( btnOk->area() ) ? btnOk->drawOnPress() : btnOk->drawOnRelease();
517         if ( allowToCancel ) {
518             le.MousePressLeft( btnCancel.area() ) ? btnCancel.drawOnPress() : btnCancel.drawOnRelease();
519         }
520 
521         // exit
522         if ( HotKeyCloseWindow || le.MouseClickLeft( btnOk->area() ) )
523             break;
524 
525         if ( allowToCancel && le.MouseClickLeft( btnCancel.area() ) ) {
526             // Skip artifact transfer and return to restart battle in manual mode
527             return true;
528         }
529 
530         // animation
531         if ( Game::validateAnimationDelay( Game::BATTLE_DIALOG_DELAY ) && !sequence.nextFrame() ) {
532             const fheroes2::Sprite & base = fheroes2::AGG::GetICN( sequence.id(), 0 );
533             const fheroes2::Sprite & sequenceCurrent = fheroes2::AGG::GetICN( sequence.id(), sequence.frameId() );
534 
535             fheroes2::Blit( base, display, pos_rt.x + anime_ox + sequenceBase.x(), pos_rt.y + anime_oy + sequenceBase.y() );
536             fheroes2::Blit( sequenceCurrent, display, pos_rt.x + anime_ox + sequenceCurrent.x(), pos_rt.y + anime_oy + sequenceCurrent.y() );
537             display.render();
538         }
539     }
540 
541     if ( !artifacts.empty() ) {
542         const HeroBase * winner = ( res.army1 & RESULT_WINS ? army1->GetCommander() : ( res.army2 & RESULT_WINS ? army2->GetCommander() : nullptr ) );
543         const HeroBase * loser = ( res.army1 & RESULT_LOSS ? army1->GetCommander() : ( res.army2 & RESULT_LOSS ? army2->GetCommander() : nullptr ) );
544 
545         // Can't transfer artifacts
546         if ( winner == nullptr || loser == nullptr )
547             return false;
548 
549         const bool isWinnerHuman = winner && winner->isControlHuman();
550 
551         btnOk.reset( new fheroes2::Button( pos_rt.x + 120, pos_rt.y + 410, isEvilInterface ? ICN::WINCMBBE : ICN::WINCMBTB, 0, 1 ) );
552 
553         for ( const Artifact & art : artifacts ) {
554             if ( isWinnerHuman || art.isUltimate() ) { // always show the message for ultimate artifacts
555                 back.restore();
556                 back.update( shadowOffset.x, shadowOffset.y, dialog.width() + BORDERWIDTH, dialog.height() + BORDERWIDTH - 1 );
557                 fheroes2::Blit( dialogShadow, display, pos_rt.x - BORDERWIDTH, pos_rt.y + BORDERWIDTH - 1 );
558                 fheroes2::Blit( dialog, display, pos_rt.x, pos_rt.y );
559 
560                 btnOk->draw();
561 
562                 std::string artMsg;
563                 if ( art.isUltimate() ) {
564                     if ( isWinnerHuman ) {
565                         artMsg = _( "As you reach for the %{name}, it mysteriously disappears." );
566                     }
567                     else {
568                         artMsg = _( "As your enemy reaches for the %{name}, it mysteriously disappears." );
569                     }
570                     StringReplace( artMsg, "%{name}", art.GetName() );
571                 }
572                 else {
573                     artMsg = _( "You have captured an enemy artifact!" );
574                     Game::PlayPickupSound();
575                 }
576 
577                 TextBox box( artMsg, Font::YELLOW_BIG, bsTextWidth );
578                 box.Blit( pos_rt.x + bsTextXOffset, pos_rt.y + bsTextYOffset );
579 
580                 const fheroes2::Sprite & border = fheroes2::AGG::GetICN( ICN::WINLOSEB, 0 );
581                 const fheroes2::Sprite & artifact = fheroes2::AGG::GetICN( ICN::ARTIFACT, art.IndexSprite64() );
582                 const fheroes2::Point artifactOffset( pos_rt.x + 119, pos_rt.y + 310 );
583 
584                 fheroes2::Blit( border, display, artifactOffset.x, artifactOffset.y );
585                 fheroes2::Blit( artifact, display, artifactOffset.x + 8, artifactOffset.y + 8 );
586 
587                 TextBox artName( art.GetName(), Font::SMALL, bsTextWidth );
588                 artName.Blit( pos_rt.x + bsTextXOffset, artifactOffset.y + border.height() + 5 );
589 
590                 const fheroes2::Rect artifactArea( artifactOffset.x, artifactOffset.y, border.width(), border.height() );
591 
592                 while ( le.HandleEvents() ) {
593                     le.MousePressLeft( btnOk->area() ) ? btnOk->drawOnPress() : btnOk->drawOnRelease();
594 
595                     // display captured artifact info on right click
596                     if ( le.MousePressRight( artifactArea ) )
597                         Dialog::ArtifactInfo( art.GetName(), "", art, 0 );
598 
599                     // exit
600                     if ( HotKeyCloseWindow || le.MouseClickLeft( btnOk->area() ) )
601                         break;
602 
603                     // animation
604                     if ( Game::validateAnimationDelay( Game::BATTLE_DIALOG_DELAY ) && !sequence.nextFrame() ) {
605                         const fheroes2::Sprite & base = fheroes2::AGG::GetICN( sequence.id(), 0 );
606                         const fheroes2::Sprite & sequenceCurrent = fheroes2::AGG::GetICN( sequence.id(), sequence.frameId() );
607 
608                         fheroes2::Blit( base, display, pos_rt.x + anime_ox + sequenceBase.x(), pos_rt.y + anime_oy + sequenceBase.y() );
609                         fheroes2::Blit( sequenceCurrent, display, pos_rt.x + anime_ox + sequenceCurrent.x(), pos_rt.y + anime_oy + sequenceCurrent.y() );
610                         display.render();
611                     }
612                 }
613             }
614         }
615     }
616     return false;
617 }
618 
DialogBattleNecromancy(const uint32_t raiseCount,const uint32_t raisedMonsterType) const619 void Battle::Arena::DialogBattleNecromancy( const uint32_t raiseCount, const uint32_t raisedMonsterType ) const
620 {
621     // setup cursor
622     const CursorRestorer cursorRestorer( true, Cursor::POINTER );
623 
624     const bool isEvilInterface = Settings::Get().ExtGameEvilInterface();
625     const fheroes2::Sprite & dialog = fheroes2::AGG::GetICN( ( isEvilInterface ? ICN::WINLOSEE : ICN::WINLOSE ), 0 );
626     const fheroes2::Sprite & dialogShadow = fheroes2::AGG::GetICN( ( isEvilInterface ? ICN::WINLOSEE : ICN::WINLOSE ), 1 );
627 
628     fheroes2::Display & display = fheroes2::Display::instance();
629     const fheroes2::Point dialogOffset( ( display.width() - dialog.width() ) / 2, ( display.height() - dialog.height() ) / 2 );
630     const fheroes2::Point shadowOffset( dialogOffset.x - BORDERWIDTH, dialogOffset.y );
631 
632     fheroes2::ImageRestorer back( display, shadowOffset.x, shadowOffset.y, dialog.width() + BORDERWIDTH, dialog.height() + BORDERWIDTH - 1 );
633     const fheroes2::Rect renderArea( dialogOffset.x, dialogOffset.y, dialog.width(), dialog.height() );
634 
635     fheroes2::Blit( dialogShadow, display, renderArea.x - BORDERWIDTH, renderArea.y + BORDERWIDTH - 1 );
636     fheroes2::Blit( dialog, display, renderArea.x, renderArea.y );
637 
638     LoopedAnimationSequence sequence;
639     sequence.push( ICN::WINCMBT, true );
640 
641     if ( sequence.isFinished() ) // Cannot be!
642         sequence.push( ICN::UNKNOWN, false );
643 
644     const fheroes2::Sprite & sequenceBase = fheroes2::AGG::GetICN( sequence.id(), 0 );
645     const fheroes2::Sprite & sequenceStart = fheroes2::AGG::GetICN( sequence.id(), 1 );
646 
647     const fheroes2::Point sequenceRenderAreaOffset( 47, 36 );
648 
649     fheroes2::Blit( sequenceBase, display, renderArea.x + sequenceRenderAreaOffset.x + sequenceBase.x(), renderArea.y + sequenceRenderAreaOffset.y + sequenceBase.y() );
650     fheroes2::Blit( sequenceStart, display, renderArea.x + sequenceRenderAreaOffset.x + sequenceStart.x(),
651                     renderArea.y + sequenceRenderAreaOffset.y + sequenceStart.y() );
652 
653     int xOffset = renderArea.x + bsTextXOffset;
654     int yOffset = renderArea.y + bsTextYOffset;
655 
656     TextBox titleBox( _( "Necromancy!" ), Font::YELLOW_BIG, bsTextWidth );
657     titleBox.Blit( xOffset, yOffset );
658 
659     const Monster mons( raisedMonsterType );
660     std::string msg = _( "Practicing the dark arts of necromancy, you are able to raise %{count} of the enemy's dead to return under your service as %{monster}." );
661     StringReplace( msg, "%{count}", raiseCount );
662     StringReplace( msg, "%{monster}", mons.GetPluralName( raiseCount ) );
663 
664     TextBox messageBox( msg, Font::BIG, bsTextWidth );
665     yOffset += bsTextIndent;
666     messageBox.Blit( xOffset, yOffset );
667 
668     const fheroes2::Sprite & monsterSprite = fheroes2::AGG::GetICN( ICN::MONS32, mons.GetSpriteIndex() );
669     yOffset += messageBox.h() + monsterSprite.height();
670     fheroes2::Blit( monsterSprite, display, ( display.width() - monsterSprite.width() ) / 2, yOffset );
671 
672     const Text raiseCountText( std::to_string( raiseCount ), Font::SMALL );
673     yOffset += 30;
674     raiseCountText.Blit( ( display.width() - raiseCountText.w() ) / 2, yOffset, bsTextWidth );
675     Game::PlayPickupSound();
676 
677     const int buttonOffset = 121;
678     const int buttonICN = isEvilInterface ? ICN::WINCMBBE : ICN::WINCMBTB;
679     fheroes2::Button buttonOk( renderArea.x + buttonOffset, renderArea.y + 410, buttonICN, 0, 1 );
680     buttonOk.draw();
681 
682     display.render();
683 
684     LocalEvent & le = LocalEvent::Get();
685     while ( le.HandleEvents() ) {
686         le.MousePressLeft( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease();
687 
688         // exit
689         if ( HotKeyCloseWindow || le.MouseClickLeft( buttonOk.area() ) )
690             break;
691 
692         // animation
693         if ( Game::validateAnimationDelay( Game::BATTLE_DIALOG_DELAY ) && !sequence.nextFrame() ) {
694             const fheroes2::Sprite & base = fheroes2::AGG::GetICN( sequence.id(), 0 );
695             const fheroes2::Sprite & sequenceCurrent = fheroes2::AGG::GetICN( sequence.id(), sequence.frameId() );
696 
697             fheroes2::Blit( base, display, renderArea.x + sequenceRenderAreaOffset.x + sequenceBase.x(), renderArea.y + sequenceRenderAreaOffset.y + sequenceBase.y() );
698             fheroes2::Blit( sequenceCurrent, display, renderArea.x + sequenceRenderAreaOffset.x + sequenceCurrent.x(),
699                             renderArea.y + sequenceRenderAreaOffset.y + sequenceCurrent.y() );
700             display.render();
701         }
702     }
703 }
704 
DialogBattleHero(const HeroBase & hero,const bool buttons,Status & status) const705 int Battle::Arena::DialogBattleHero( const HeroBase & hero, const bool buttons, Status & status ) const
706 {
707     const Settings & conf = Settings::Get();
708 
709     Cursor & cursor = Cursor::Get();
710     cursor.SetThemes( Cursor::POINTER );
711 
712     const bool readonly = current_color != hero.GetColor() || !buttons;
713     const fheroes2::Sprite & dialog = fheroes2::AGG::GetICN( ( conf.ExtGameEvilInterface() ? ICN::VGENBKGE : ICN::VGENBKG ), 0 );
714 
715     const fheroes2::Point dialogShadow( 15, 15 );
716 
717     fheroes2::Display & display = fheroes2::Display::instance();
718     fheroes2::Rect pos_rt( ( display.width() - dialog.width() - dialogShadow.x ) / 2, ( display.height() - dialog.height() - dialogShadow.y ) / 2, dialog.width(),
719                            dialog.height() );
720 
721     fheroes2::ImageRestorer back( display, pos_rt.x, pos_rt.y, pos_rt.width, pos_rt.height );
722 
723     fheroes2::Blit( dialog, display, pos_rt.x, pos_rt.y );
724 
725     // first 15 pixels in the dialog is left shadow, skip
726     pos_rt.x += 15;
727     pos_rt.width -= 15;
728 
729     const fheroes2::Rect portraitArea( pos_rt.x + 7, pos_rt.y + 35, 113, 108 );
730     const Heroes * actionHero = ( current_color == hero.GetColor() ) ? dynamic_cast<const Heroes *>( &hero ) : nullptr;
731 
732     hero.PortraitRedraw( pos_rt.x + 12, pos_rt.y + 42, PORT_BIG, display );
733     int col = ( Color::NONE == hero.GetColor() ? 1 : Color::GetIndex( hero.GetColor() ) + 1 );
734     fheroes2::Blit( fheroes2::AGG::GetICN( ICN::VIEWGEN, col ), display, pos_rt.x + 133, pos_rt.y + 36 );
735 
736     fheroes2::Point tp( pos_rt.x, pos_rt.y );
737 
738     std::string str;
739     Text text;
740     text.Set( Font::SMALL );
741     str = _( "%{name} the %{race}" );
742     StringReplace( str, "%{name}", hero.GetName() );
743     StringReplace( str, "%{race}", Race::String( hero.GetRace() ) );
744     text.Set( str );
745     tp.x = pos_rt.x + ( pos_rt.width - text.w() ) / 2;
746     tp.y = pos_rt.y + 11;
747     text.Blit( tp.x, tp.y );
748     str = _( "Attack" ) + std::string( ": " ) + std::to_string( hero.GetAttack() );
749     text.Set( str );
750     tp.x = pos_rt.x + 190 - text.w() / 2;
751     tp.y = pos_rt.y + 40;
752     text.Blit( tp.x, tp.y );
753     str = _( "Defense" ) + std::string( ": " ) + std::to_string( hero.GetDefense() );
754     text.Set( str );
755     tp.x = pos_rt.x + 190 - text.w() / 2;
756     tp.y = pos_rt.y + 51;
757     text.Blit( tp.x, tp.y );
758     str = _( "Spell Power" ) + std::string( ": " ) + std::to_string( hero.GetPower() );
759     text.Set( str );
760     tp.x = pos_rt.x + 190 - text.w() / 2;
761     tp.y = pos_rt.y + 62;
762     text.Blit( tp.x, tp.y );
763     str = _( "Knowledge" ) + std::string( ": " ) + std::to_string( hero.GetKnowledge() );
764     text.Set( str );
765     tp.x = pos_rt.x + 190 - text.w() / 2;
766     tp.y = pos_rt.y + 73;
767     text.Blit( tp.x, tp.y );
768     str = _( "Morale" ) + std::string( ": " ) + Morale::String( hero.GetMorale() );
769     text.Set( str );
770     tp.x = pos_rt.x + 190 - text.w() / 2;
771     tp.y = pos_rt.y + 84;
772     text.Blit( tp.x, tp.y );
773     str = _( "Luck" ) + std::string( ": " ) + Luck::String( hero.GetLuck() );
774     text.Set( str );
775     tp.x = pos_rt.x + 190 - text.w() / 2;
776     tp.y = pos_rt.y + 95;
777     text.Blit( tp.x, tp.y );
778     str = _( "Spell Points" ) + std::string( ": " ) + std::to_string( hero.GetSpellPoints() ) + "/" + std::to_string( hero.GetMaxSpellPoints() );
779     text.Set( str );
780     tp.x = pos_rt.x + 190 - text.w() / 2;
781     tp.y = pos_rt.y + 117;
782     text.Blit( tp.x, tp.y );
783 
784     fheroes2::Button btnCast( pos_rt.x + 15, pos_rt.y + 148, ICN::VIEWGEN, 9, 10 );
785     fheroes2::Button btnRetreat( pos_rt.x + 74, pos_rt.y + 148, ICN::VIEWGEN, 11, 12 );
786     fheroes2::Button btnSurrender( pos_rt.x + 133, pos_rt.y + 148, ICN::VIEWGEN, 13, 14 );
787     fheroes2::Button btnClose( pos_rt.x + 192, pos_rt.y + 148, ICN::VIEWGEN, 15, 16 );
788 
789     if ( readonly || !hero.HaveSpellBook() || hero.Modes( Heroes::SPELLCASTED ) )
790         btnCast.disable();
791 
792     if ( readonly || !CanRetreatOpponent( hero.GetColor() ) )
793         btnRetreat.disable();
794 
795     if ( readonly || !CanSurrenderOpponent( hero.GetColor() ) )
796         btnSurrender.disable();
797 
798     btnCast.draw();
799     btnRetreat.draw();
800     btnSurrender.draw();
801     btnClose.draw();
802 
803     int result = 0;
804 
805     display.render();
806 
807     std::string statusMessage = _( "Hero's Options" );
808 
809     LocalEvent & le = LocalEvent::Get();
810     while ( le.HandleEvents() && !result ) {
811         btnCast.isEnabled() && le.MousePressLeft( btnCast.area() ) ? btnCast.drawOnPress() : btnCast.drawOnRelease();
812         btnRetreat.isEnabled() && le.MousePressLeft( btnRetreat.area() ) ? btnRetreat.drawOnPress() : btnRetreat.drawOnRelease();
813         btnSurrender.isEnabled() && le.MousePressLeft( btnSurrender.area() ) ? btnSurrender.drawOnPress() : btnSurrender.drawOnRelease();
814         le.MousePressLeft( btnClose.area() ) ? btnClose.drawOnPress() : btnClose.drawOnRelease();
815 
816         if ( buttons ) {
817             if ( le.MouseCursor( btnCast.area() ) ) {
818                 statusMessage = _( "Cast Spell" );
819             }
820             else if ( le.MouseCursor( btnRetreat.area() ) ) {
821                 statusMessage = _( "Retreat" );
822             }
823             else if ( le.MouseCursor( btnSurrender.area() ) ) {
824                 statusMessage = _( "Surrender" );
825             }
826             else if ( le.MouseCursor( btnClose.area() ) ) {
827                 statusMessage = _( "Cancel" );
828             }
829             else if ( le.MouseCursor( portraitArea ) && actionHero != nullptr ) {
830                 statusMessage = _( "Hero Screen" );
831             }
832             else {
833                 statusMessage = _( "Hero's Options" );
834             }
835         }
836 
837         if ( !buttons && !le.MousePressRight() )
838             break;
839 
840         if ( Game::HotKeyPressEvent( Game::EVENT_BATTLE_CASTSPELL ) || ( btnCast.isEnabled() && le.MouseClickLeft( btnCast.area() ) ) )
841             result = 1;
842 
843         if ( Game::HotKeyPressEvent( Game::EVENT_BATTLE_RETREAT ) || ( btnRetreat.isEnabled() && le.MouseClickLeft( btnRetreat.area() ) ) )
844             result = 2;
845 
846         if ( Game::HotKeyPressEvent( Game::EVENT_BATTLE_SURRENDER ) || ( btnSurrender.isEnabled() && le.MouseClickLeft( btnSurrender.area() ) ) )
847             result = 3;
848 
849         if ( le.MouseClickLeft( portraitArea ) && actionHero != nullptr ) {
850             // IMPORTANT!!! This is extremely dangerous but we have no choice with current code. Make sure that this trick doesn't allow user to modify the hero.
851             LocalEvent::GetClean();
852             const_cast<Heroes *>( actionHero )->OpenDialog( true, false, true, true );
853         }
854 
855         if ( le.MousePressRight( btnCast.area() ) ) {
856             Dialog::Message( _( "Cast Spell" ),
857                              _( "Cast a magical spell. You may only cast one spell per combat round. The round is reset when every creature has had a turn." ),
858                              Font::BIG );
859         }
860         else if ( le.MousePressRight( btnRetreat.area() ) ) {
861             Dialog::Message(
862                 _( "Retreat" ),
863                 _( "Retreat your hero, abandoning your creatures. Your hero will be available for you to recruit again, however, the hero will have only a novice hero's forces." ),
864                 Font::BIG );
865         }
866         else if ( le.MousePressRight( btnSurrender.area() ) ) {
867             Dialog::Message(
868                 _( "Surrender" ),
869                 _( "Surrendering costs gold. However if you pay the ransom, the hero and all of his or her surviving creatures will be available to recruit again." ),
870                 Font::BIG );
871         }
872         else if ( le.MousePressRight( portraitArea ) ) {
873             Dialog::Message( _( "Hero Screen" ), _( "Open Hero Screen to view full information about the hero." ), Font::BIG );
874         }
875         else if ( le.MousePressRight( btnClose.area() ) ) {
876             Dialog::Message( _( "Cancel" ), _( "Return to the battle." ), Font::BIG );
877         }
878 
879         // exit
880         if ( HotKeyCloseWindow || le.MouseClickLeft( btnClose.area() ) )
881             break;
882 
883         if ( statusMessage != status.GetMessage() ) {
884             status.SetMessage( statusMessage );
885             status.Redraw();
886         }
887     }
888 
889     return result;
890 }
891 
DialogBattleSurrender(const HeroBase & hero,u32 cost,Kingdom & kingdom)892 bool Battle::DialogBattleSurrender( const HeroBase & hero, u32 cost, Kingdom & kingdom )
893 {
894     if ( kingdom.GetColor() == hero.GetColor() ) // this is weird. You're surrending to yourself!
895         return false;
896 
897     fheroes2::Display & display = fheroes2::Display::instance();
898     LocalEvent & le = LocalEvent::Get();
899     const Settings & conf = Settings::Get();
900 
901     // setup cursor
902     const CursorRestorer cursorRestorer( true, Cursor::POINTER );
903 
904     const bool isEvilInterface = conf.ExtGameEvilInterface();
905 
906     const fheroes2::Sprite & dialog = fheroes2::AGG::GetICN( isEvilInterface ? ICN::SURDRBKE : ICN::SURDRBKG, 0 );
907 
908     fheroes2::Rect pos_rt( ( display.width() - dialog.width() + 16 ) / 2, ( display.height() - dialog.height() + 16 ) / 2, dialog.width(), dialog.height() );
909 
910     fheroes2::Blit( dialog, display, pos_rt.x, pos_rt.y );
911 
912     const int icn = isEvilInterface ? ICN::SURRENDE : ICN::SURRENDR;
913     const int icnMarket = isEvilInterface ? ICN::EVIL_MARKET_BUTTON : ICN::GOOD_MARKET_BUTTON;
914 
915     fheroes2::ButtonSprite btnAccept
916         = fheroes2::makeButtonWithShadow( pos_rt.x + 91, pos_rt.y + 152, fheroes2::AGG::GetICN( icn, 0 ), fheroes2::AGG::GetICN( icn, 1 ), display );
917 
918     fheroes2::ButtonSprite btnDecline
919         = fheroes2::makeButtonWithShadow( pos_rt.x + 295, pos_rt.y + 152, fheroes2::AGG::GetICN( icn, 2 ), fheroes2::AGG::GetICN( icn, 3 ), display );
920 
921     fheroes2::ButtonSprite btnMarket = fheroes2::makeButtonWithShadow( pos_rt.x + ( pos_rt.width - 16 ) / 2, pos_rt.y + 145, fheroes2::AGG::GetICN( icnMarket, 0 ),
922                                                                        fheroes2::AGG::GetICN( icnMarket, 1 ), display );
923 
924     if ( !kingdom.AllowPayment( payment_t( Resource::GOLD, cost ) ) ) {
925         btnAccept.disable();
926     }
927 
928     if ( kingdom.GetCountMarketplace() ) {
929         if ( kingdom.AllowPayment( payment_t( Resource::GOLD, cost ) ) ) {
930             btnMarket.disable();
931         }
932         else {
933             btnMarket.draw();
934         }
935     }
936     else {
937         btnMarket.disable();
938     }
939 
940     btnAccept.draw();
941     btnDecline.draw();
942 
943     auto drawGoldMsg = [cost, &kingdom, &btnAccept]() {
944         std::string str = _( "Not enough gold (%{gold})" );
945 
946         StringReplace( str, "%{gold}", cost - kingdom.GetFunds().Get( Resource::GOLD ) );
947 
948         const Text text( str, Font::SMALL );
949         const fheroes2::Rect rect = btnAccept.area();
950 
951         text.Blit( rect.x + ( rect.width - text.w() ) / 2, rect.y - 15 );
952     };
953 
954     const fheroes2::Sprite & window = fheroes2::AGG::GetICN( icn, 4 );
955     fheroes2::Blit( window, display, pos_rt.x + 55, pos_rt.y + 32 );
956     hero.PortraitRedraw( pos_rt.x + 60, pos_rt.y + 38, PORT_BIG, display );
957 
958     std::string str = _( "%{name} states:" );
959     StringReplace( str, "%{name}", hero.GetName() );
960     Text text( str, Font::BIG );
961     text.Blit( pos_rt.x + 320 - text.w() / 2, pos_rt.y + 30 );
962 
963     str = _( "\"I will accept your surrender and grant you and your troops safe passage for the price of %{price} gold.\"" );
964     StringReplace( str, "%{price}", cost );
965 
966     TextBox box( str, Font::BIG, 275 );
967     box.Blit( pos_rt.x + 175, pos_rt.y + 50 );
968 
969     fheroes2::ImageRestorer back( display, pos_rt.x, pos_rt.y, pos_rt.width, pos_rt.height );
970 
971     if ( !kingdom.AllowPayment( payment_t( Resource::GOLD, cost ) ) ) {
972         drawGoldMsg();
973     }
974 
975     display.render();
976 
977     bool result = false;
978 
979     while ( le.HandleEvents() && !result ) {
980         if ( btnAccept.isEnabled() )
981             le.MousePressLeft( btnAccept.area() ) ? btnAccept.drawOnPress() : btnAccept.drawOnRelease();
982         le.MousePressLeft( btnDecline.area() ) ? btnDecline.drawOnPress() : btnDecline.drawOnRelease();
983 
984         if ( btnMarket.isEnabled() )
985             le.MousePressLeft( btnMarket.area() ) ? btnMarket.drawOnPress() : btnMarket.drawOnRelease();
986 
987         if ( btnAccept.isEnabled() && le.MouseClickLeft( btnAccept.area() ) )
988             result = true;
989 
990         if ( btnMarket.isEnabled() && le.MouseClickLeft( btnMarket.area() ) ) {
991             Dialog::Marketplace( kingdom, false );
992 
993             back.restore();
994 
995             if ( kingdom.AllowPayment( payment_t( Resource::GOLD, cost ) ) ) {
996                 btnAccept.enable();
997             }
998             else {
999                 btnAccept.disable();
1000 
1001                 drawGoldMsg();
1002             }
1003 
1004             btnAccept.draw();
1005             display.render();
1006         }
1007 
1008         // exit
1009         if ( Game::HotKeyPressEvent( Game::EVENT_DEFAULT_EXIT ) || le.MouseClickLeft( btnDecline.area() ) )
1010             break;
1011     }
1012 
1013     return result;
1014 }
1015