1 /***************************************************************************
2 * Copyright (C) 2009 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 <cassert>
24
25 #include "agg_image.h"
26 #include "army.h"
27 #include "battle.h"
28 #include "battle_cell.h"
29 #include "cursor.h"
30 #include "dialog.h"
31 #include "game.h"
32 #include "game_delays.h"
33 #include "icn.h"
34 #include "luck.h"
35 #include "monster.h"
36 #include "monster_anim.h"
37 #include "morale.h"
38 #include "payment.h"
39 #include "settings.h"
40 #include "text.h"
41 #include "tools.h"
42 #include "translations.h"
43 #include "ui_button.h"
44 #include "ui_text.h"
45 #include "world.h"
46
47 namespace
48 {
49 const int offsetXAmountBox = 80;
50 const int offsetYAmountBox = 223;
51 const int widthAmountBox = 125;
52 const int heightAmountBox = 23;
53
54 struct SpellInfo
55 {
SpellInfo__anonde09e35c0111::SpellInfo56 SpellInfo( const uint32_t mode_, const uint32_t duration_, const int32_t offset_, const int32_t space_ )
57 : mode( mode_ )
58 , duration( duration_ )
59 , offset( offset_ )
60 , space( space_ )
61 {}
62
63 uint32_t mode;
64 uint32_t duration;
65 int32_t offset;
66 int32_t space;
67 Spell spell;
68 };
69
modeToSpell(const uint32_t modeId)70 Spell modeToSpell( const uint32_t modeId )
71 {
72 switch ( modeId ) {
73 case Battle::SP_BLOODLUST:
74 return Spell::BLOODLUST;
75 case Battle::SP_BLESS:
76 return Spell::BLESS;
77 case Battle::SP_HASTE:
78 return Spell::HASTE;
79 case Battle::SP_SHIELD:
80 return Spell::SHIELD;
81 case Battle::SP_STONESKIN:
82 return Spell::STONESKIN;
83 case Battle::SP_DRAGONSLAYER:
84 return Spell::DRAGONSLAYER;
85 case Battle::SP_STEELSKIN:
86 return Spell::STEELSKIN;
87 case Battle::SP_ANTIMAGIC:
88 return Spell::ANTIMAGIC;
89 case Battle::SP_CURSE:
90 return Spell::CURSE;
91 case Battle::SP_SLOW:
92 return Spell::SLOW;
93 case Battle::SP_BERSERKER:
94 return Spell::BERSERKER;
95 case Battle::SP_HYPNOTIZE:
96 return Spell::HYPNOTIZE;
97 case Battle::SP_BLIND:
98 return Spell::BLIND;
99 case Battle::SP_PARALYZE:
100 return Spell::PARALYZE;
101 case Battle::SP_STONE:
102 return Spell::STONE;
103 default:
104 // Did you add another mode? Please add a corresponding spell.
105 assert( 0 );
106 break;
107 }
108
109 return Spell::NONE;
110 }
111 }
112
113 void DrawMonsterStats( const fheroes2::Point & dst, const Troop & troop );
114 std::vector<std::pair<fheroes2::Rect, Spell>> DrawBattleStats( const fheroes2::Point & dst, const Troop & b );
115 void DrawMonsterInfo( const fheroes2::Point & dst, const Troop & troop );
116 void DrawMonster( fheroes2::RandomMonsterAnimation & monsterAnimation, const Troop & troop, const fheroes2::Point & offset, bool isReflected, bool isAnimated,
117 const fheroes2::Rect & roi );
118
ArmyInfo(const Troop & troop,int flags,bool isReflected)119 int Dialog::ArmyInfo( const Troop & troop, int flags, bool isReflected )
120 {
121 // The active size of the window is 520 by 256 pixels
122 fheroes2::Display & display = fheroes2::Display::instance();
123 const bool isEvilInterface = Settings::Get().ExtGameEvilInterface();
124
125 const int viewarmy = isEvilInterface ? ICN::VIEWARME : ICN::VIEWARMY;
126 const fheroes2::Sprite & sprite_dialog = fheroes2::AGG::GetICN( viewarmy, 0 );
127 const fheroes2::Sprite & spriteDialogShadow = fheroes2::AGG::GetICN( viewarmy, 7 );
128
129 // setup cursor
130 const CursorRestorer cursorRestorer( ( flags & BUTTONS ) != 0, Cursor::POINTER );
131
132 fheroes2::Point dialogOffset( ( display.width() - sprite_dialog.width() ) / 2, ( display.height() - sprite_dialog.height() ) / 2 );
133 if ( isEvilInterface ) {
134 dialogOffset.y += 3;
135 }
136
137 const fheroes2::Point shadowShift( spriteDialogShadow.x() - sprite_dialog.x(), spriteDialogShadow.y() - sprite_dialog.y() );
138 const fheroes2::Point shadowOffset( dialogOffset.x + shadowShift.x, dialogOffset.y + shadowShift.y );
139
140 fheroes2::ImageRestorer restorer( display, shadowOffset.x, dialogOffset.y, sprite_dialog.width() - shadowShift.x, sprite_dialog.height() + shadowShift.y );
141 fheroes2::Blit( spriteDialogShadow, display, dialogOffset.x + shadowShift.x, dialogOffset.y + shadowShift.y );
142 fheroes2::Blit( sprite_dialog, display, dialogOffset.x, dialogOffset.y );
143
144 fheroes2::Rect pos_rt( dialogOffset.x, dialogOffset.y, sprite_dialog.width(), sprite_dialog.height() );
145 if ( isEvilInterface ) {
146 pos_rt.x += 9;
147 pos_rt.y -= 1;
148 }
149
150 const fheroes2::Point monsterStatOffset( pos_rt.x + 400, pos_rt.y + 37 );
151 DrawMonsterStats( monsterStatOffset, troop );
152
153 std::vector<std::pair<fheroes2::Rect, Spell>> spellAreas;
154
155 const fheroes2::Point battleStatOffset( pos_rt.x + 395, pos_rt.y + 184 );
156 if ( troop.isBattle() )
157 spellAreas = DrawBattleStats( battleStatOffset, troop );
158
159 DrawMonsterInfo( pos_rt.getPosition(), troop );
160
161 const bool isAnimated = ( flags & BUTTONS ) != 0;
162 fheroes2::RandomMonsterAnimation monsterAnimation( troop );
163 const fheroes2::Point monsterOffset( pos_rt.x + 520 / 4 + 16, pos_rt.y + 175 );
164 if ( !isAnimated )
165 monsterAnimation.reset();
166
167 const fheroes2::Rect dialogRoi( pos_rt.x, pos_rt.y + SHADOWWIDTH, sprite_dialog.width(), sprite_dialog.height() - 2 * SHADOWWIDTH );
168 DrawMonster( monsterAnimation, troop, monsterOffset, isReflected, isAnimated, dialogRoi );
169
170 // button upgrade
171 fheroes2::Point dst_pt( pos_rt.x + 400, pos_rt.y + 40 );
172 dst_pt.x = pos_rt.x + 280;
173 dst_pt.y = pos_rt.y + 192;
174 fheroes2::Button buttonUpgrade( dst_pt.x, dst_pt.y, viewarmy, 5, 6 );
175
176 // button dismiss
177 dst_pt.x = pos_rt.x + 280;
178 dst_pt.y = pos_rt.y + 221;
179 fheroes2::Button buttonDismiss( dst_pt.x, dst_pt.y, viewarmy, 1, 2 );
180
181 // button exit
182 dst_pt.x = pos_rt.x + 415;
183 dst_pt.y = pos_rt.y + 221;
184 fheroes2::Button buttonExit( dst_pt.x, dst_pt.y, viewarmy, 3, 4 );
185
186 if ( READONLY & flags ) {
187 buttonDismiss.disable();
188 }
189
190 if ( !troop.isBattle() && troop.isAllowUpgrade() && ( UPGRADE & flags ) ) {
191 buttonUpgrade.enable();
192 buttonUpgrade.draw();
193 }
194 else
195 buttonUpgrade.disable();
196
197 if ( BUTTONS & flags ) {
198 if ( !troop.isBattle() && !( READONLY & flags ) )
199 buttonDismiss.draw();
200 buttonExit.draw();
201 }
202
203 LocalEvent & le = LocalEvent::Get();
204 int result = Dialog::ZERO;
205
206 display.render();
207
208 // dialog menu loop
209 while ( le.HandleEvents() ) {
210 if ( flags & BUTTONS ) {
211 if ( buttonUpgrade.isEnabled() )
212 le.MousePressLeft( buttonUpgrade.area() ) ? buttonUpgrade.drawOnPress() : buttonUpgrade.drawOnRelease();
213 if ( buttonDismiss.isEnabled() )
214 le.MousePressLeft( buttonDismiss.area() ) ? buttonDismiss.drawOnPress() : buttonDismiss.drawOnRelease();
215 le.MousePressLeft( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease();
216
217 // upgrade
218 if ( buttonUpgrade.isEnabled() && ( le.MouseClickLeft( buttonUpgrade.area() ) || Game::HotKeyPressEvent( Game::EVENT_UPGRADE_TROOP ) ) ) {
219 if ( UPGRADE_DISABLE & flags ) {
220 const std::string msg( _( "You can't afford to upgrade your troops!" ) );
221 if ( Dialog::YES == Dialog::ResourceInfo( "", msg, troop.GetUpgradeCost(), Dialog::OK ) ) {
222 result = Dialog::UPGRADE;
223 break;
224 }
225 }
226 else {
227 const std::string msg = _( "Your troops can be upgraded, but it will cost you dearly. Do you wish to upgrade them?" );
228
229 if ( Dialog::YES == Dialog::ResourceInfo( "", msg, troop.GetUpgradeCost(), Dialog::YES | Dialog::NO ) ) {
230 result = Dialog::UPGRADE;
231 break;
232 }
233 }
234 }
235 // dismiss
236 if ( buttonDismiss.isEnabled() && ( le.MouseClickLeft( buttonDismiss.area() ) || Game::HotKeyPressEvent( Game::EVENT_DISMISS_TROOP ) )
237 && Dialog::YES
238 == Dialog::Message( troop.GetPluralName( troop.GetCount() ), _( "Are you sure you want to dismiss this army?" ), Font::BIG,
239 Dialog::YES | Dialog::NO ) ) {
240 result = Dialog::DISMISS;
241 break;
242 }
243 // exit
244 if ( le.MouseClickLeft( buttonExit.area() ) || HotKeyCloseWindow ) {
245 result = Dialog::CANCEL;
246 break;
247 }
248
249 for ( const auto & spellInfo : spellAreas ) {
250 if ( le.MousePressRight( spellInfo.first ) ) {
251 Dialog::SpellInfo( spellInfo.second, nullptr, false );
252 break;
253 }
254 }
255
256 if ( Game::validateAnimationDelay( Game::CASTLE_UNIT_DELAY ) ) {
257 fheroes2::Blit( sprite_dialog, display, dialogOffset.x, dialogOffset.y );
258
259 DrawMonsterStats( monsterStatOffset, troop );
260
261 if ( troop.isBattle() )
262 spellAreas = DrawBattleStats( battleStatOffset, troop );
263
264 DrawMonsterInfo( pos_rt.getPosition(), troop );
265 DrawMonster( monsterAnimation, troop, monsterOffset, isReflected, true, dialogRoi );
266
267 if ( buttonUpgrade.isEnabled() )
268 buttonUpgrade.draw();
269
270 if ( buttonDismiss.isEnabled() )
271 buttonDismiss.draw();
272
273 if ( buttonExit.isEnabled() )
274 buttonExit.draw();
275
276 display.render();
277 }
278 }
279 else {
280 if ( !le.MousePressRight() )
281 break;
282 }
283 }
284
285 return result;
286 }
287
DrawMonsterStats(const fheroes2::Point & dst,const Troop & troop)288 void DrawMonsterStats( const fheroes2::Point & dst, const Troop & troop )
289 {
290 fheroes2::Point dst_pt;
291 Text text;
292
293 // attack
294 text.Set( std::string( _( "Attack Skill" ) ) + ":" );
295 dst_pt.x = dst.x - text.w();
296 dst_pt.y = dst.y;
297 text.Blit( dst_pt.x, dst_pt.y );
298
299 const int offsetX = 6;
300 const int offsetY = 16;
301
302 text.Set( troop.GetAttackString() );
303 dst_pt.x = dst.x + offsetX;
304 text.Blit( dst_pt.x, dst_pt.y );
305
306 // defense
307 text.Set( std::string( _( "Defense Skill" ) ) + ":" );
308 dst_pt.x = dst.x - text.w();
309 dst_pt.y += offsetY;
310 text.Blit( dst_pt.x, dst_pt.y );
311
312 text.Set( troop.GetDefenseString() );
313 dst_pt.x = dst.x + offsetX;
314 text.Blit( dst_pt.x, dst_pt.y );
315
316 // shot
317 if ( troop.isArchers() ) {
318 std::string message = troop.isBattle() ? _( "Shots Left" ) : _( "Shots" );
319 message += ':';
320 text.Set( message );
321 dst_pt.x = dst.x - text.w();
322 dst_pt.y += offsetY;
323 text.Blit( dst_pt.x, dst_pt.y );
324
325 text.Set( troop.GetShotString() );
326 dst_pt.x = dst.x + offsetX;
327 text.Blit( dst_pt.x, dst_pt.y );
328 }
329
330 // damage
331 text.Set( std::string( _( "Damage" ) ) + ":" );
332 dst_pt.x = dst.x - text.w();
333 dst_pt.y += offsetY;
334 text.Blit( dst_pt.x, dst_pt.y );
335
336 if ( troop.GetMonster().GetDamageMin() != troop.GetMonster().GetDamageMax() )
337 text.Set( std::to_string( troop.GetMonster().GetDamageMin() ) + "-" + std::to_string( troop.GetMonster().GetDamageMax() ) );
338 else
339 text.Set( std::to_string( troop.GetMonster().GetDamageMin() ) );
340 dst_pt.x = dst.x + offsetX;
341 text.Blit( dst_pt.x, dst_pt.y );
342
343 // hp
344 text.Set( std::string( _( "Hit Points" ) ) + ":" );
345 dst_pt.x = dst.x - text.w();
346 dst_pt.y += offsetY;
347 text.Blit( dst_pt.x, dst_pt.y );
348
349 text.Set( std::to_string( troop.GetMonster().GetHitPoints() ) );
350 dst_pt.x = dst.x + offsetX;
351 text.Blit( dst_pt.x, dst_pt.y );
352
353 if ( troop.isBattle() ) {
354 text.Set( std::string( _( "Hit Points Left" ) ) + ":" );
355 dst_pt.x = dst.x - text.w();
356 dst_pt.y += offsetY;
357 text.Blit( dst_pt.x, dst_pt.y );
358
359 text.Set( std::to_string( troop.GetHitPointsLeft() ) );
360 dst_pt.x = dst.x + offsetX;
361 text.Blit( dst_pt.x, dst_pt.y );
362 }
363
364 // speed
365 text.Set( std::string( _( "Speed" ) ) + ":" );
366 dst_pt.x = dst.x - text.w();
367 dst_pt.y += offsetY;
368 text.Blit( dst_pt.x, dst_pt.y );
369
370 text.Set( troop.GetSpeedString() );
371 dst_pt.x = dst.x + offsetX;
372 text.Blit( dst_pt.x, dst_pt.y );
373
374 // morale
375 text.Set( std::string( _( "Morale" ) ) + ":" );
376 dst_pt.x = dst.x - text.w();
377 dst_pt.y += offsetY;
378 text.Blit( dst_pt.x, dst_pt.y );
379
380 text.Set( Morale::String( troop.GetMorale() ) );
381 dst_pt.x = dst.x + offsetX;
382 text.Blit( dst_pt.x, dst_pt.y );
383
384 // luck
385 text.Set( std::string( _( "Luck" ) ) + ":" );
386 dst_pt.x = dst.x - text.w();
387 dst_pt.y += offsetY;
388 text.Blit( dst_pt.x, dst_pt.y );
389
390 text.Set( Luck::String( troop.GetLuck() ) );
391 dst_pt.x = dst.x + offsetX;
392 text.Blit( dst_pt.x, dst_pt.y );
393 }
394
GetModesSprite(u32 mod)395 fheroes2::Sprite GetModesSprite( u32 mod )
396 {
397 switch ( mod ) {
398 case Battle::SP_BLOODLUST:
399 return fheroes2::AGG::GetICN( ICN::SPELLINL, 9 );
400 case Battle::SP_BLESS:
401 return fheroes2::AGG::GetICN( ICN::SPELLINL, 3 );
402 case Battle::SP_HASTE:
403 return fheroes2::AGG::GetICN( ICN::SPELLINL, 0 );
404 case Battle::SP_SHIELD:
405 return fheroes2::AGG::GetICN( ICN::SPELLINL, 10 );
406 case Battle::SP_STONESKIN:
407 return fheroes2::AGG::GetICN( ICN::SPELLINL, 13 );
408 case Battle::SP_DRAGONSLAYER:
409 return fheroes2::AGG::GetICN( ICN::SPELLINL, 8 );
410 case Battle::SP_STEELSKIN:
411 return fheroes2::AGG::GetICN( ICN::SPELLINL, 14 );
412 case Battle::SP_ANTIMAGIC:
413 return fheroes2::AGG::GetICN( ICN::SPELLINL, 12 );
414 case Battle::SP_CURSE:
415 return fheroes2::AGG::GetICN( ICN::SPELLINL, 4 );
416 case Battle::SP_SLOW:
417 return fheroes2::AGG::GetICN( ICN::SPELLINL, 1 );
418 case Battle::SP_BERSERKER:
419 return fheroes2::AGG::GetICN( ICN::SPELLINL, 5 );
420 case Battle::SP_HYPNOTIZE:
421 return fheroes2::AGG::GetICN( ICN::SPELLINL, 7 );
422 case Battle::SP_BLIND:
423 return fheroes2::AGG::GetICN( ICN::SPELLINL, 2 );
424 case Battle::SP_PARALYZE:
425 return fheroes2::AGG::GetICN( ICN::SPELLINL, 6 );
426 case Battle::SP_STONE:
427 return fheroes2::AGG::GetICN( ICN::SPELLINL, 11 );
428 default:
429 break;
430 }
431
432 return fheroes2::Sprite();
433 }
434
DrawBattleStats(const fheroes2::Point & dst,const Troop & b)435 std::vector<std::pair<fheroes2::Rect, Spell>> DrawBattleStats( const fheroes2::Point & dst, const Troop & b )
436 {
437 std::vector<std::pair<fheroes2::Rect, Spell>> output;
438
439 const uint32_t modes[] = { Battle::SP_BLOODLUST, Battle::SP_BLESS, Battle::SP_HASTE, Battle::SP_SHIELD, Battle::SP_STONESKIN,
440 Battle::SP_DRAGONSLAYER, Battle::SP_STEELSKIN, Battle::SP_ANTIMAGIC, Battle::SP_CURSE, Battle::SP_SLOW,
441 Battle::SP_BERSERKER, Battle::SP_HYPNOTIZE, Battle::SP_BLIND, Battle::SP_PARALYZE, Battle::SP_STONE };
442
443 int32_t ow = 0;
444 int32_t spritesWidth = 0;
445
446 std::vector<SpellInfo> spellsInfo;
447 for ( const uint32_t mode : modes ) {
448 if ( !b.isModes( mode ) )
449 continue;
450
451 const fheroes2::Sprite & sprite = GetModesSprite( mode );
452 if ( sprite.empty() )
453 continue;
454
455 const uint32_t duration = b.GetAffectedDuration( mode );
456 int offset = 0;
457 if ( duration > 0 ) {
458 offset = duration >= 10 ? 12 : 7;
459 if ( mode >= Battle::SP_BLESS && mode <= Battle::SP_DRAGONSLAYER )
460 offset -= 5;
461 }
462 const int space = ( offset == 2 ) ? 10 : 5;
463
464 spellsInfo.emplace_back( mode, duration, offset, space );
465 ow += sprite.width() + offset + space;
466 spritesWidth += sprite.width();
467 }
468
469 if ( spellsInfo.empty() )
470 return output;
471
472 std::sort( spellsInfo.begin(), spellsInfo.end(),
473 []( const SpellInfo & first, const SpellInfo & second ) { return first.duration > 0 && first.duration < second.duration; } );
474
475 ow -= spellsInfo.back().space;
476
477 const int maxSpritesWidth = 212;
478 const int maxSpriteHeight = 32;
479
480 Text text;
481 if ( ow <= maxSpritesWidth ) {
482 ow = dst.x - ow / 2;
483 for ( const auto & spell : spellsInfo ) {
484 const fheroes2::Sprite & sprite = GetModesSprite( spell.mode );
485 const fheroes2::Point imageOffset( ow, dst.y + maxSpriteHeight - sprite.height() );
486
487 fheroes2::Blit( sprite, fheroes2::Display::instance(), imageOffset.x, imageOffset.y );
488 output.emplace_back( std::make_pair( fheroes2::Rect( imageOffset.x, imageOffset.y, sprite.width(), sprite.height() ), modeToSpell( spell.mode ) ) );
489
490 if ( spell.duration > 0 ) {
491 text.Set( std::to_string( spell.duration ), Font::SMALL );
492 ow += sprite.width() + spell.offset;
493 text.Blit( ow - text.w(), dst.y + maxSpriteHeight - text.h() + 1 );
494 }
495 ow += spell.space;
496 }
497 }
498 else {
499 // Too many spells
500 const int widthDiff = maxSpritesWidth - spritesWidth;
501 int space = widthDiff / static_cast<int>( spellsInfo.size() - 1 );
502 if ( widthDiff > 0 ) {
503 if ( space > 10 )
504 space = 10;
505 ow = dst.x + ( spritesWidth + space * static_cast<int>( spellsInfo.size() - 1 ) ) / 2;
506 }
507 else {
508 ow = dst.x + maxSpritesWidth / 2;
509 }
510
511 for ( auto spellIt = spellsInfo.crbegin(); spellIt != spellsInfo.crend(); ++spellIt ) {
512 const fheroes2::Sprite & sprite = GetModesSprite( spellIt->mode );
513 const fheroes2::Point imageOffset( ow - sprite.width(), dst.y + maxSpriteHeight - sprite.height() );
514
515 fheroes2::Blit( sprite, fheroes2::Display::instance(), imageOffset.x, imageOffset.y );
516 output.emplace_back( std::make_pair( fheroes2::Rect( imageOffset.x, imageOffset.y, sprite.width(), sprite.height() ), modeToSpell( spellIt->mode ) ) );
517
518 if ( spellIt->duration > 0 ) {
519 text.Set( std::to_string( spellIt->duration ), Font::SMALL );
520 text.Blit( ow - text.w(), dst.y + maxSpriteHeight - text.h() + 1 );
521 }
522 ow -= sprite.width() + space;
523 }
524 }
525
526 return output;
527 }
528
DrawMonsterInfo(const fheroes2::Point & offset,const Troop & troop)529 void DrawMonsterInfo( const fheroes2::Point & offset, const Troop & troop )
530 {
531 // name
532 Text text( troop.GetName(), Font::YELLOW_BIG );
533 fheroes2::Point pos( offset.x + 140 - text.w() / 2, offset.y + 40 );
534 text.Blit( pos.x, pos.y );
535
536 // Description.
537 const std::vector<std::string> descriptions = fheroes2::getMonsterPropertiesDescription( troop.GetID() );
538 if ( !descriptions.empty() ) {
539 const int32_t descriptionWidth = 210;
540 const int32_t maximumRowCount = 3;
541 const int32_t rowHeight = fheroes2::Text( std::string(), { fheroes2::FontSize::SMALL, fheroes2::FontColor::WHITE } ).height();
542
543 bool asSolidText = true;
544 if ( descriptions.size() <= static_cast<size_t>( maximumRowCount ) ) {
545 asSolidText = false;
546 for ( const std::string & sentence : descriptions ) {
547 if ( fheroes2::Text( sentence, { fheroes2::FontSize::SMALL, fheroes2::FontColor::WHITE } ).width() > descriptionWidth ) {
548 asSolidText = true;
549 break;
550 }
551 }
552 }
553
554 if ( asSolidText ) {
555 std::string description;
556 for ( const std::string & sentence : descriptions ) {
557 if ( !description.empty() ) {
558 description += ' ';
559 }
560
561 description += sentence;
562 }
563
564 const fheroes2::Text descriptionText( description, { fheroes2::FontSize::SMALL, fheroes2::FontColor::WHITE } );
565 const int32_t rowCount = descriptionText.rows( descriptionWidth );
566
567 descriptionText.draw( offset.x + 37, offset.y + 185 + ( maximumRowCount - rowCount ) * rowHeight, descriptionWidth, fheroes2::Display::instance() );
568 }
569 else {
570 int32_t sentenceId = maximumRowCount - static_cast<int32_t>( descriptions.size() ); // safe to cast as we check the size before.
571 for ( const std::string & sentence : descriptions ) {
572 const fheroes2::Text descriptionText( sentence, { fheroes2::FontSize::SMALL, fheroes2::FontColor::WHITE } );
573
574 descriptionText.draw( offset.x + 37, offset.y + 185 + sentenceId * rowHeight, descriptionWidth, fheroes2::Display::instance() );
575 ++sentenceId;
576 }
577 }
578 }
579
580 // amount
581 text.Set( std::to_string( troop.GetCount() ), Font::BIG );
582 pos.x = offset.x + offsetXAmountBox + widthAmountBox / 2 - text.w() / 2;
583 pos.y = offset.y + offsetYAmountBox + heightAmountBox / 2 - text.h() / 2;
584 text.Blit( pos.x, pos.y );
585 }
586
DrawMonster(fheroes2::RandomMonsterAnimation & monsterAnimation,const Troop & troop,const fheroes2::Point & offset,bool isReflected,bool isAnimated,const fheroes2::Rect & roi)587 void DrawMonster( fheroes2::RandomMonsterAnimation & monsterAnimation, const Troop & troop, const fheroes2::Point & offset, bool isReflected, bool isAnimated,
588 const fheroes2::Rect & roi )
589 {
590 const fheroes2::Sprite & monsterSprite = fheroes2::AGG::GetICN( monsterAnimation.icnFile(), monsterAnimation.frameId() );
591 fheroes2::Point monsterPos( offset.x, offset.y + monsterSprite.y() );
592 if ( isReflected )
593 monsterPos.x -= monsterSprite.x() - ( troop.isWide() ? CELLW / 2 : 0 ) - monsterAnimation.offset() + monsterSprite.width();
594 else
595 monsterPos.x += monsterSprite.x() - ( troop.isWide() ? CELLW / 2 : 0 ) - monsterAnimation.offset();
596
597 fheroes2::Point inPos( 0, 0 );
598 fheroes2::Point outPos( monsterPos.x, monsterPos.y );
599 fheroes2::Size inSize( monsterSprite.width(), monsterSprite.height() );
600
601 fheroes2::Display & display = fheroes2::Display::instance();
602
603 if ( fheroes2::FitToRoi( monsterSprite, inPos, display, outPos, inSize, roi ) ) {
604 fheroes2::Blit( monsterSprite, inPos, display, outPos, inSize, isReflected );
605 }
606
607 if ( isAnimated )
608 monsterAnimation.increment();
609 }
610
ArmyJoinFree(const Troop & troop,Heroes & hero)611 int Dialog::ArmyJoinFree( const Troop & troop, Heroes & hero )
612 {
613 fheroes2::Display & display = fheroes2::Display::instance();
614 const bool isEvilInterface = Settings::Get().ExtGameEvilInterface();
615
616 // setup cursor
617 const CursorRestorer cursorRestorer( true, Cursor::POINTER );
618
619 const Text title( _( "Followers" ), Font::YELLOW_BIG );
620
621 std::string message = _( "A group of %{monster} with a desire for greater glory wish to join you.\nDo you accept?" );
622 StringReplace( message, "%{monster}", StringLower( troop.GetMultiName() ) );
623
624 TextBox textbox( message, Font::BIG, BOXAREA_WIDTH );
625 const int buttons = Dialog::YES | Dialog::NO;
626 int posy = 0;
627
628 FrameBox box( 10 + 2 * title.h() + textbox.h() + 10, true );
629 const fheroes2::Rect & pos = box.GetArea();
630
631 title.Blit( pos.x + ( pos.width - title.w() ) / 2, pos.y );
632
633 posy = pos.y + 2 * title.h() - 3;
634 textbox.Blit( pos.x, posy );
635
636 fheroes2::ButtonGroup btnGroup( pos, buttons );
637
638 const int armyButtonIcn = isEvilInterface ? ICN::EVIL_ARMY_BUTTON : ICN::GOOD_ARMY_BUTTON;
639 const fheroes2::Sprite & armyButtonReleased = fheroes2::AGG::GetICN( armyButtonIcn, 0 );
640 const fheroes2::Sprite & armyButtonPressed = fheroes2::AGG::GetICN( armyButtonIcn, 1 );
641
642 fheroes2::ButtonSprite btnHeroes = fheroes2::makeButtonWithBackground( pos.x + pos.width / 2 - armyButtonReleased.width() / 2, pos.y + pos.height - 35,
643 armyButtonReleased, armyButtonPressed, display );
644
645 if ( hero.GetArmy().GetCount() < hero.GetArmy().Size() || hero.GetArmy().HasMonster( troop ) )
646 btnHeroes.disable();
647 else {
648 // TextBox textbox2(_("Not room in\nthe garrison"), Font::SMALL, 100);
649 // textbox2.Blit(btnHeroes.x - 35, btnHeroes.y - 30);
650 btnHeroes.draw();
651 btnGroup.button( 0 ).disable();
652 }
653
654 btnGroup.draw();
655 display.render();
656
657 LocalEvent & le = LocalEvent::Get();
658
659 // message loop
660 int result = Dialog::ZERO;
661
662 while ( result == Dialog::ZERO && le.HandleEvents() ) {
663 if ( btnHeroes.isEnabled() )
664 le.MousePressLeft( btnHeroes.area() ) ? btnHeroes.drawOnPress() : btnHeroes.drawOnRelease();
665
666 result = btnGroup.processEvents();
667
668 if ( btnHeroes.isEnabled() && le.MouseClickLeft( btnHeroes.area() ) ) {
669 LocalEvent::GetClean();
670 hero.OpenDialog( false, false, true, true );
671
672 if ( hero.GetArmy().GetCount() < hero.GetArmy().Size() ) {
673 btnGroup.button( 0 ).enable();
674 }
675 else {
676 btnGroup.button( 0 ).disable();
677 }
678
679 btnGroup.draw();
680
681 display.render();
682 }
683 else if ( le.MousePressRight( btnHeroes.area() ) ) {
684 Dialog::Message( "", _( "View Hero" ), Font::BIG );
685 }
686 }
687
688 return result;
689 }
690
ArmyJoinWithCost(const Troop & troop,u32 join,u32 gold,Heroes & hero)691 int Dialog::ArmyJoinWithCost( const Troop & troop, u32 join, u32 gold, Heroes & hero )
692 {
693 fheroes2::Display & display = fheroes2::Display::instance();
694 const bool isEvilInterface = Settings::Get().ExtGameEvilInterface();
695
696 // setup cursor
697 const CursorRestorer cursorRestorer( true, Cursor::POINTER );
698
699 std::string message;
700
701 if ( troop.GetCount() == 1 ) {
702 message = _( "The %{monster} is swayed by your diplomatic tongue, and offers to join your army for the sum of %{gold} gold.\nDo you accept?" );
703 }
704 else {
705 message = _( "The creatures are swayed by your diplomatic\ntongue, and make you an offer:\n \n" );
706
707 if ( join != troop.GetCount() )
708 message += _( "%{offer} of the %{total} %{monster} will join your army, and the rest will leave you alone, for the sum of %{gold} gold.\nDo you accept?" );
709 else
710 message += _( "All %{offer} of the %{monster} will join your army for the sum of %{gold} gold.\nDo you accept?" );
711 }
712
713 StringReplace( message, "%{offer}", join );
714 StringReplace( message, "%{total}", troop.GetCount() );
715 StringReplace( message, "%{monster}", StringLower( troop.GetPluralName( join ) ) );
716 StringReplace( message, "%{gold}", gold );
717
718 TextBox textbox( message, Font::BIG, BOXAREA_WIDTH );
719 const int buttons = Dialog::YES | Dialog::NO;
720 const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::RESOURCE, 6 );
721 int posy = 0;
722 Text text;
723
724 message = _( "(Rate: %{percent})" );
725 StringReplace( message, "%{percent}", troop.GetMonster().GetCost().gold * join * 100 / gold );
726 text.Set( message, Font::BIG );
727
728 FrameBox box( 10 + textbox.h() + 10 + text.h() + 40 + sprite.height() + 10, true );
729 const fheroes2::Rect & pos = box.GetArea();
730
731 posy = pos.y + 10;
732 textbox.Blit( pos.x, posy );
733
734 posy += textbox.h() + 10;
735 text.Blit( pos.x + ( pos.width - text.w() ) / 2, posy );
736
737 posy += text.h() + 40;
738 fheroes2::Blit( sprite, display, pos.x + ( pos.width - sprite.width() ) / 2, posy );
739
740 TextSprite tsTotal( std::to_string( gold ) + " (" + _( "Total: " ) + std::to_string( world.GetKingdom( hero.GetColor() ).GetFunds().Get( Resource::GOLD ) ) + ")",
741 Font::SMALL, pos.x + ( pos.width - text.w() ) / 2, posy + sprite.height() + 5 );
742 tsTotal.Show();
743
744 fheroes2::ButtonGroup btnGroup( pos, buttons );
745
746 const int icnMarket = isEvilInterface ? ICN::EVIL_MARKET_BUTTON : ICN::GOOD_MARKET_BUTTON;
747 const int icnHeroes = isEvilInterface ? ICN::EVIL_ARMY_BUTTON : ICN::GOOD_ARMY_BUTTON;
748
749 fheroes2::ButtonSprite btnMarket = fheroes2::makeButtonWithBackground( pos.x + pos.width / 2 - 60 - 36, posy, fheroes2::AGG::GetICN( icnMarket, 0 ),
750 fheroes2::AGG::GetICN( icnMarket, 1 ), display );
751
752 fheroes2::ButtonSprite btnHeroes
753 = fheroes2::makeButtonWithBackground( pos.x + pos.width / 2 + 60, posy, fheroes2::AGG::GetICN( icnHeroes, 0 ), fheroes2::AGG::GetICN( icnHeroes, 1 ), display );
754
755 Kingdom & kingdom = hero.GetKingdom();
756
757 fheroes2::Rect btnMarketArea = btnMarket.area();
758 fheroes2::Rect btnHeroesArea = btnHeroes.area();
759
760 if ( !kingdom.AllowPayment( payment_t( Resource::GOLD, gold ) ) )
761 btnGroup.button( 0 ).disable();
762
763 TextSprite tsNotEnoughGold;
764 tsNotEnoughGold.SetPos( btnMarketArea.x - 25, btnMarketArea.y - 17 );
765
766 fheroes2::ImageRestorer marketButtonRestorer( display, btnMarket.area().x, btnMarket.area().y, btnMarket.area().width, btnMarket.area().height );
767
768 if ( kingdom.AllowPayment( payment_t( Resource::GOLD, gold ) ) || kingdom.GetCountMarketplace() == 0 ) {
769 tsNotEnoughGold.Hide();
770 btnMarket.disable();
771 btnMarket.hide();
772 }
773 else {
774 std::string msg = _( "Not enough gold (%{gold})" );
775 StringReplace( msg, "%{gold}", gold - kingdom.GetFunds().Get( Resource::GOLD ) );
776 tsNotEnoughGold.SetText( msg, Font::SMALL );
777 tsNotEnoughGold.Show();
778 btnMarket.enable();
779 btnMarket.draw();
780 }
781
782 TextSprite noRoom1;
783 noRoom1.SetText( _( "No room in" ), Font::SMALL );
784 noRoom1.SetPos( btnHeroesArea.x - 16, btnHeroesArea.y - 30 );
785 TextSprite noRoom2;
786 noRoom2.SetText( _( "the garrison" ), Font::SMALL );
787 noRoom2.SetPos( btnHeroesArea.x - 23, btnHeroesArea.y - 15 );
788
789 if ( hero.GetArmy().GetCount() < hero.GetArmy().Size() || hero.GetArmy().HasMonster( troop ) )
790 btnHeroes.disable();
791 else {
792 noRoom1.Show();
793 noRoom2.Show();
794 btnHeroes.draw();
795 btnGroup.button( 0 ).disable();
796 }
797
798 btnGroup.draw();
799 display.render();
800
801 LocalEvent & le = LocalEvent::Get();
802
803 // message loop
804 int result = Dialog::ZERO;
805
806 while ( result == Dialog::ZERO && le.HandleEvents() ) {
807 if ( btnMarket.isEnabled() )
808 le.MousePressLeft( btnMarketArea ) ? btnMarket.drawOnPress() : btnMarket.drawOnRelease();
809
810 if ( btnHeroes.isEnabled() )
811 le.MousePressLeft( btnHeroesArea ) ? btnHeroes.drawOnPress() : btnHeroes.drawOnRelease();
812
813 result = btnGroup.processEvents();
814
815 bool needRedraw = false;
816
817 if ( btnMarket.isEnabled() && le.MouseClickLeft( btnMarketArea ) ) {
818 Marketplace( kingdom, false );
819
820 needRedraw = true;
821 }
822 else if ( btnHeroes.isEnabled() && le.MouseClickLeft( btnHeroesArea ) ) {
823 LocalEvent::GetClean();
824 hero.OpenDialog( false, false, true, true );
825
826 needRedraw = true;
827 }
828
829 if ( !needRedraw ) {
830 continue;
831 }
832
833 tsTotal.Hide();
834 tsTotal.SetText( std::to_string( gold ) + " (total: " + std::to_string( world.GetKingdom( hero.GetColor() ).GetFunds().Get( Resource::GOLD ) ) + ")" );
835 tsTotal.Show();
836
837 const bool allowPayment = kingdom.AllowPayment( payment_t( Resource::GOLD, gold ) );
838 const bool enoughRoom = hero.GetArmy().GetCount() < hero.GetArmy().Size() || hero.GetArmy().HasMonster( troop );
839
840 if ( allowPayment && enoughRoom ) {
841 btnGroup.button( 0 ).enable();
842 }
843 else {
844 btnGroup.button( 0 ).disable();
845 }
846
847 btnGroup.draw();
848
849 if ( allowPayment || kingdom.GetCountMarketplace() == 0 ) {
850 tsNotEnoughGold.Hide();
851 btnMarket.disable();
852 btnMarket.hide();
853 marketButtonRestorer.restore();
854 }
855 else {
856 std::string msg = _( "Not enough gold (%{gold})" );
857 StringReplace( msg, "%{gold}", gold - kingdom.GetFunds().Get( Resource::GOLD ) );
858 tsNotEnoughGold.SetText( msg, Font::SMALL );
859 tsNotEnoughGold.Show();
860 btnMarket.enable();
861 btnMarket.show();
862 }
863
864 btnMarket.draw();
865
866 if ( enoughRoom ) {
867 noRoom1.Hide();
868 noRoom2.Hide();
869 }
870 else {
871 noRoom1.Show();
872 noRoom2.Show();
873 }
874
875 display.render();
876 }
877
878 return result;
879 }
880