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