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 <algorithm>
24 #include <string>
25 
26 #include "agg_image.h"
27 #include "army.h"
28 #include "army_bar.h"
29 #include "army_troop.h"
30 #include "cursor.h"
31 #include "dialog.h"
32 #include "game.h"
33 #include "heroes.h"
34 #include "heroes_indicator.h"
35 #include "icn.h"
36 #include "logging.h"
37 #include "settings.h"
38 #include "skill_bar.h"
39 #include "text.h"
40 #include "tools.h"
41 #include "translations.h"
42 #include "ui_button.h"
43 
44 namespace
45 {
createMoveButton(const int32_t icnId,const int32_t offsetX,const int32_t offsetY,const fheroes2::Image & display)46     fheroes2::ButtonSprite createMoveButton( const int32_t icnId, const int32_t offsetX, const int32_t offsetY, const fheroes2::Image & display )
47     {
48         const fheroes2::Sprite & originalReleasedImage = fheroes2::AGG::GetICN( icnId, 0 );
49         const fheroes2::Sprite & originalPressedImage = fheroes2::AGG::GetICN( icnId, 1 );
50 
51         const int32_t minX = std::min( originalReleasedImage.x(), originalPressedImage.x() );
52         const int32_t minY = std::min( originalReleasedImage.y(), originalPressedImage.y() );
53 
54         return fheroes2::makeButtonWithShadow( offsetX + minX, offsetY + minY, originalReleasedImage, originalPressedImage, display, fheroes2::Point( -3, 3 ) );
55     }
56 
moveArtifacts(BagArtifacts & bagFrom,BagArtifacts & bagTo)57     void moveArtifacts( BagArtifacts & bagFrom, BagArtifacts & bagTo )
58     {
59         size_t toIdx = 0;
60 
61         for ( size_t fromIdx = 0; fromIdx < bagFrom.size(); ++fromIdx ) {
62             if ( bagFrom[fromIdx].GetID() != Artifact::UNKNOWN && bagFrom[fromIdx].GetID() != Artifact::MAGIC_BOOK ) {
63                 while ( toIdx < bagTo.size() ) {
64                     if ( bagTo[toIdx].GetID() == Artifact::UNKNOWN )
65                         break;
66 
67                     ++toIdx;
68                 }
69 
70                 if ( toIdx == bagTo.size() )
71                     break;
72 
73                 std::swap( bagFrom[fromIdx], bagTo[toIdx] );
74             }
75         }
76     }
77 }
78 
79 class MeetingArmyBar : public ArmyBar
80 {
81 public:
MeetingArmyBar(Army * army)82     explicit MeetingArmyBar( Army * army )
83         : ArmyBar( army, true, false, false )
84     {}
85 
RedrawBackground(const fheroes2::Rect & roi,fheroes2::Image & image)86     void RedrawBackground( const fheroes2::Rect & roi, fheroes2::Image & image ) override
87     {
88         if ( _cachedBackground.empty() ) {
89             _cachedBackground.resize( roi.width, roi.height );
90             fheroes2::Copy( image, roi.x, roi.y, _cachedBackground, 0, 0, roi.width, roi.height );
91         }
92 
93         fheroes2::Blit( _cachedBackground, 0, 0, image, roi.x, roi.y, roi.width, roi.height );
94     }
95 
RedrawItem(ArmyTroop & troop,const fheroes2::Rect & roi,bool isSelected,fheroes2::Image & image)96     void RedrawItem( ArmyTroop & troop, const fheroes2::Rect & roi, bool isSelected, fheroes2::Image & image ) override
97     {
98         if ( !troop.isValid() )
99             return;
100 
101         Text text( std::to_string( troop.GetCount() ), Font::SMALL );
102 
103         const fheroes2::Sprite & mons32 = fheroes2::AGG::GetICN( ICN::MONS32, troop.GetSpriteIndex() );
104         fheroes2::Rect srcrt( 0, 0, mons32.width(), mons32.height() );
105 
106         if ( mons32.width() > roi.width ) {
107             srcrt.x = ( mons32.width() - roi.width ) / 2;
108             srcrt.width = roi.width;
109         }
110 
111         if ( mons32.height() > roi.height ) {
112             srcrt.y = ( mons32.height() - roi.height ) / 2;
113             srcrt.height = roi.height;
114         }
115 
116         int32_t offsetX = ( roi.width - mons32.width() ) / 2;
117         int32_t offsetY = roi.height - mons32.height() - 3;
118 
119         if ( offsetX < 1 )
120             offsetX = 1;
121 
122         if ( offsetY < 1 )
123             offsetY = 1;
124 
125         fheroes2::Blit( mons32, srcrt.x, srcrt.y, image, roi.x + offsetX, roi.y + offsetY, srcrt.width, srcrt.height );
126 
127         text.Blit( roi.x + ( roi.width - text.w() ) / 2, roi.y + roi.height - 1, image );
128 
129         if ( isSelected ) {
130             spcursor.setPosition( roi.x, roi.y );
131             spcursor.show();
132         }
133     }
134 
135 private:
136     fheroes2::Image _cachedBackground;
137 };
138 
139 class MeetingArtifactBar : public ArtifactsBar
140 {
141 public:
MeetingArtifactBar(const Heroes * hero)142     explicit MeetingArtifactBar( const Heroes * hero )
143         : ArtifactsBar( hero, true, false, false, false, nullptr )
144     {}
145 
RedrawBackground(const fheroes2::Rect & roi,fheroes2::Image & image)146     void RedrawBackground( const fheroes2::Rect & roi, fheroes2::Image & image ) override
147     {
148         if ( _cachedBackground.empty() ) {
149             _cachedBackground.resize( roi.width, roi.height );
150             fheroes2::Copy( image, roi.x, roi.y, _cachedBackground, 0, 0, roi.width, roi.height );
151         }
152 
153         fheroes2::Blit( _cachedBackground, 0, 0, image, roi.x, roi.y, roi.width, roi.height );
154     }
155 
RedrawItem(Artifact & arifact,const fheroes2::Rect & roi,bool isSelected,fheroes2::Image & image)156     void RedrawItem( Artifact & arifact, const fheroes2::Rect & roi, bool isSelected, fheroes2::Image & image ) override
157     {
158         if ( !arifact.isValid() )
159             return;
160 
161         fheroes2::Blit( fheroes2::AGG::GetICN( ICN::ARTFX, arifact.IndexSprite32() ), image, roi.x + 1, roi.y + 1 );
162 
163         if ( isSelected ) {
164             spcursor.setPosition( roi.x, roi.y );
165             spcursor.show();
166         }
167     }
168 
169 private:
170     fheroes2::Image _cachedBackground;
171 };
172 
173 class MeetingPrimarySkillsBar : public PrimarySkillsBar
174 {
175 public:
MeetingPrimarySkillsBar(const Heroes * hero)176     explicit MeetingPrimarySkillsBar( const Heroes * hero )
177         : PrimarySkillsBar( hero, true )
178     {}
179 
RedrawBackground(const fheroes2::Rect &,fheroes2::Image &)180     void RedrawBackground( const fheroes2::Rect &, fheroes2::Image & ) override
181     {
182         // Just do nothing
183     }
184 };
185 
186 class MeetingSecondarySkillsBar : public SecondarySkillsBar
187 {
188 public:
MeetingSecondarySkillsBar(const Heroes & hero)189     explicit MeetingSecondarySkillsBar( const Heroes & hero )
190         : SecondarySkillsBar( hero )
191     {}
192 
RedrawBackground(const fheroes2::Rect & roi,fheroes2::Image & image)193     void RedrawBackground( const fheroes2::Rect & roi, fheroes2::Image & image ) override
194     {
195         if ( _cachedBackground.empty() ) {
196             _cachedBackground.resize( roi.width, roi.height );
197             fheroes2::Copy( image, roi.x, roi.y, _cachedBackground, 0, 0, roi.width, roi.height );
198         }
199 
200         fheroes2::Blit( _cachedBackground, 0, 0, image, roi.x, roi.y, roi.width, roi.height );
201     }
202 
RedrawItem(Skill::Secondary & skill,const fheroes2::Rect & roi,fheroes2::Image & image)203     void RedrawItem( Skill::Secondary & skill, const fheroes2::Rect & roi, fheroes2::Image & image ) override
204     {
205         if ( !skill.isValid() )
206             return;
207 
208         const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::MINISS, skill.GetIndexSprite2() );
209         fheroes2::Blit( sprite, image, roi.x + ( roi.width - sprite.width() ) / 2, roi.y + ( roi.height - sprite.height() ) / 2 );
210 
211         Text text( std::to_string( skill.Level() ), Font::SMALL );
212         text.Blit( roi.x + ( roi.width - text.w() ) - 3, roi.y + roi.height - text.h(), image );
213     }
214 
215 private:
216     fheroes2::Image _cachedBackground;
217 };
218 
MeetingDialog(Heroes & otherHero)219 void Heroes::MeetingDialog( Heroes & otherHero )
220 {
221     fheroes2::Display & display = fheroes2::Display::instance();
222 
223     // setup cursor
224     const CursorRestorer cursorRestorer( true, Cursor::POINTER );
225 
226     const fheroes2::Sprite & backSprite = fheroes2::AGG::GetICN( ICN::SWAPWIN, 0 );
227     const fheroes2::Point cur_pt( ( display.width() - backSprite.width() ) / 2, ( display.height() - backSprite.height() ) / 2 );
228     fheroes2::ImageRestorer restorer( display, cur_pt.x, cur_pt.y, backSprite.width(), backSprite.height() );
229     fheroes2::Point dst_pt( cur_pt );
230     std::string message;
231 
232     fheroes2::Rect src_rt( 0, 0, fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT );
233 
234     // background
235     dst_pt.x = cur_pt.x;
236     dst_pt.y = cur_pt.y;
237     fheroes2::Blit( backSprite, src_rt.x, src_rt.y, display, dst_pt.x, dst_pt.y, src_rt.width, src_rt.height );
238 
239     // header
240     message = _( "%{name1} meets %{name2}" );
241     StringReplace( message, "%{name1}", GetName() );
242     StringReplace( message, "%{name2}", otherHero.GetName() );
243     Text text( message, Font::BIG );
244     text.Blit( cur_pt.x + 320 - text.w() / 2, cur_pt.y + 27 );
245 
246     const int iconsH1XOffset = 34;
247     const int iconsH2XOffset = 566;
248     const int portraitYOffset = 72;
249 
250     // portrait
251     dst_pt.x = cur_pt.x + 93;
252     dst_pt.y = cur_pt.y + portraitYOffset;
253     const fheroes2::Sprite & portrait1 = GetPortrait( PORT_BIG );
254     fheroes2::Rect hero1Area( dst_pt.x, dst_pt.y, portrait1.width(), portrait1.height() );
255     PortraitRedraw( dst_pt.x, dst_pt.y, PORT_BIG, display );
256 
257     dst_pt.x = cur_pt.x + 445;
258     dst_pt.y = cur_pt.y + portraitYOffset;
259     const fheroes2::Sprite & portrait2 = otherHero.GetPortrait( PORT_BIG );
260     fheroes2::Rect hero2Area( dst_pt.x, dst_pt.y, portrait2.width(), portrait2.height() );
261     otherHero.PortraitRedraw( dst_pt.x, dst_pt.y, PORT_BIG, display );
262 
263     MoraleIndicator moraleIndicator1( this );
264     dst_pt.x = cur_pt.x + iconsH1XOffset;
265     dst_pt.y = cur_pt.y + portraitYOffset + moraleIndicator1.GetArea().height / 3;
266     moraleIndicator1.SetPos( dst_pt );
267     moraleIndicator1.Redraw();
268 
269     LuckIndicator luckIndicator1( this );
270     dst_pt.x = cur_pt.x + iconsH1XOffset;
271     dst_pt.y = cur_pt.y + portraitYOffset + portrait1.height() - luckIndicator1.GetArea().height * 4 / 3;
272     luckIndicator1.SetPos( dst_pt );
273     luckIndicator1.Redraw();
274 
275     MoraleIndicator moraleIndicator2( &otherHero );
276     dst_pt.x = cur_pt.x + iconsH2XOffset;
277     dst_pt.y = cur_pt.y + portraitYOffset + moraleIndicator2.GetArea().height / 3;
278     moraleIndicator2.SetPos( dst_pt );
279     moraleIndicator2.Redraw();
280 
281     LuckIndicator luckIndicator2( &otherHero );
282     dst_pt.x = cur_pt.x + iconsH2XOffset;
283     dst_pt.y = cur_pt.y + portraitYOffset + portrait2.height() - luckIndicator2.GetArea().height * 4 / 3;
284     luckIndicator2.SetPos( dst_pt );
285     luckIndicator2.Redraw();
286 
287     // primary skill
288     fheroes2::ImageRestorer backPrimary( display, cur_pt.x + 255, cur_pt.y + 50, 130, 135 );
289 
290     MeetingPrimarySkillsBar primskill_bar1( this );
291     primskill_bar1.SetColRows( 1, 4 );
292     primskill_bar1.SetVSpace( -1 );
293     primskill_bar1.SetTextOff( 70, -25 );
294     primskill_bar1.SetPos( cur_pt.x + 216, cur_pt.y + 51 );
295 
296     MeetingPrimarySkillsBar primskill_bar2( &otherHero );
297     primskill_bar2.SetColRows( 1, 4 );
298     primskill_bar2.SetVSpace( -1 );
299     primskill_bar2.SetTextOff( -70, -25 );
300     primskill_bar2.SetPos( cur_pt.x + 389, cur_pt.y + 51 );
301 
302     fheroes2::RedrawPrimarySkillInfo( cur_pt, &primskill_bar1, &primskill_bar2 );
303 
304     // secondary skill
305     MeetingSecondarySkillsBar secskill_bar1( *this );
306     secskill_bar1.SetColRows( 8, 1 );
307     secskill_bar1.SetHSpace( -1 );
308     secskill_bar1.SetContent( secondary_skills.ToVector() );
309     secskill_bar1.SetPos( cur_pt.x + 22, cur_pt.y + 199 );
310     secskill_bar1.Redraw();
311 
312     MeetingSecondarySkillsBar secskill_bar2( otherHero );
313     secskill_bar2.SetColRows( 8, 1 );
314     secskill_bar2.SetHSpace( -1 );
315     secskill_bar2.SetContent( otherHero.GetSecondarySkills().ToVector() );
316     secskill_bar2.SetPos( cur_pt.x + 353, cur_pt.y + 199 );
317     secskill_bar2.Redraw();
318 
319     const fheroes2::Sprite & moveButtonBackground = fheroes2::AGG::GetICN( ICN::STONEBAK, 0 );
320     fheroes2::Blit( moveButtonBackground, 292, 270, display, cur_pt.x + 292, cur_pt.y + 270, 48, 44 );
321 
322     // The original resources do not have such animated buttons so we have to create those.
323     fheroes2::ButtonSprite moveArmyToHero2 = createMoveButton( ICN::SWAP_ARROW_LEFT_TO_RIGHT, cur_pt.x + 298, cur_pt.y + 267, display );
324     fheroes2::ButtonSprite moveArmyToHero1 = createMoveButton( ICN::SWAP_ARROW_RIGHT_TO_LEFT, cur_pt.x + 298, cur_pt.y + 290, display );
325 
326     fheroes2::ImageRestorer armyCountBackgroundRestorerLeft( display, cur_pt.x + 36, cur_pt.y + 310, 223, 20 );
327     fheroes2::ImageRestorer armyCountBackgroundRestorerRight( display, cur_pt.x + 381, cur_pt.y + 310, 223, 20 );
328 
329     // army
330     dst_pt.x = cur_pt.x + 36;
331     dst_pt.y = cur_pt.y + 267;
332 
333     MeetingArmyBar selectArmy1( &GetArmy() );
334     selectArmy1.SetColRows( 5, 1 );
335     selectArmy1.SetPos( dst_pt.x, dst_pt.y );
336     selectArmy1.SetHSpace( 2 );
337     selectArmy1.Redraw();
338 
339     dst_pt.x = cur_pt.x + 381;
340     dst_pt.y = cur_pt.y + 267;
341 
342     MeetingArmyBar selectArmy2( &otherHero.GetArmy() );
343     selectArmy2.SetColRows( 5, 1 );
344     selectArmy2.SetPos( dst_pt.x, dst_pt.y );
345     selectArmy2.SetHSpace( 2 );
346     selectArmy2.Redraw();
347 
348     // artifact
349     dst_pt.x = cur_pt.x + 23;
350     dst_pt.y = cur_pt.y + 347;
351 
352     MeetingArtifactBar selectArtifacts1( this );
353     selectArtifacts1.SetColRows( 7, 2 );
354     selectArtifacts1.SetHSpace( 2 );
355     selectArtifacts1.SetVSpace( 2 );
356     selectArtifacts1.SetContent( GetBagArtifacts() );
357     selectArtifacts1.SetPos( dst_pt.x, dst_pt.y );
358     selectArtifacts1.Redraw();
359 
360     dst_pt.x = cur_pt.x + 367;
361     dst_pt.y = cur_pt.y + 347;
362 
363     MeetingArtifactBar selectArtifacts2( &otherHero );
364     selectArtifacts2.SetColRows( 7, 2 );
365     selectArtifacts2.SetHSpace( 2 );
366     selectArtifacts2.SetVSpace( 2 );
367     selectArtifacts2.SetContent( otherHero.GetBagArtifacts() );
368     selectArtifacts2.SetPos( dst_pt.x, dst_pt.y );
369     selectArtifacts2.Redraw();
370 
371     fheroes2::Blit( moveButtonBackground, 292, 363, display, cur_pt.x + 292, cur_pt.y + 363, 48, 44 );
372     fheroes2::ButtonSprite moveArtifactsToHero2 = createMoveButton( ICN::SWAP_ARROW_LEFT_TO_RIGHT, cur_pt.x + 298, cur_pt.y + 361, display );
373     fheroes2::ButtonSprite moveArtifactsToHero1 = createMoveButton( ICN::SWAP_ARROW_RIGHT_TO_LEFT, cur_pt.x + 298, cur_pt.y + 384, display );
374 
375     // button exit
376     dst_pt.x = cur_pt.x + 280;
377     dst_pt.y = cur_pt.y + 428;
378     fheroes2::Button buttonExit( dst_pt.x, dst_pt.y, ICN::SWAPBTN, 0, 1 );
379 
380     moveArmyToHero2.draw();
381     moveArmyToHero1.draw();
382     moveArtifactsToHero2.draw();
383     moveArtifactsToHero1.draw();
384     buttonExit.draw();
385 
386     display.render();
387 
388     MovePointsScaleFixed();
389     otherHero.MovePointsScaleFixed();
390 
391     // scholar action
392     if ( Settings::Get().ExtWorldEyeEagleAsScholar() )
393         Heroes::ScholarAction( *this, otherHero );
394 
395     LocalEvent & le = LocalEvent::Get();
396 
397     // message loop
398     while ( le.HandleEvents() ) {
399         le.MousePressLeft( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease();
400 
401         if ( le.MousePressLeft( moveArmyToHero2.area() ) ) {
402             moveArmyToHero2.drawOnPress();
403             moveArmyToHero1.drawOnRelease();
404         }
405         else if ( le.MousePressLeft( moveArmyToHero1.area() ) ) {
406             moveArmyToHero1.drawOnPress();
407             moveArmyToHero2.drawOnRelease();
408         }
409         else {
410             moveArmyToHero1.drawOnRelease();
411             moveArmyToHero2.drawOnRelease();
412         }
413 
414         if ( le.MousePressLeft( moveArtifactsToHero2.area() ) ) {
415             moveArtifactsToHero2.drawOnPress();
416             moveArtifactsToHero1.drawOnRelease();
417         }
418         else if ( le.MousePressLeft( moveArtifactsToHero1.area() ) ) {
419             moveArtifactsToHero1.drawOnPress();
420             moveArtifactsToHero2.drawOnRelease();
421         }
422         else {
423             moveArtifactsToHero1.drawOnRelease();
424             moveArtifactsToHero2.drawOnRelease();
425         }
426 
427         if ( le.MouseClickLeft( buttonExit.area() ) || HotKeyCloseWindow )
428             break;
429 
430         // selector troops event
431         if ( ( le.MouseCursor( selectArmy1.GetArea() ) && selectArmy1.QueueEventProcessing( selectArmy2 ) )
432              || ( le.MouseCursor( selectArmy2.GetArea() ) && selectArmy2.QueueEventProcessing( selectArmy1 ) ) ) {
433             if ( selectArtifacts1.isSelected() )
434                 selectArtifacts1.ResetSelected();
435             else if ( selectArtifacts2.isSelected() )
436                 selectArtifacts2.ResetSelected();
437 
438             armyCountBackgroundRestorerLeft.restore();
439             armyCountBackgroundRestorerRight.restore();
440 
441             selectArmy1.Redraw();
442             selectArmy2.Redraw();
443 
444             moraleIndicator1.Redraw();
445             moraleIndicator2.Redraw();
446             luckIndicator1.Redraw();
447             luckIndicator2.Redraw();
448 
449             display.render();
450         }
451 
452         // selector artifacts event
453         if ( ( le.MouseCursor( selectArtifacts1.GetArea() ) && selectArtifacts1.QueueEventProcessing( selectArtifacts2 ) )
454              || ( le.MouseCursor( selectArtifacts2.GetArea() ) && selectArtifacts2.QueueEventProcessing( selectArtifacts1 ) ) ) {
455             if ( selectArmy1.isSelected() )
456                 selectArmy1.ResetSelected();
457             else if ( selectArmy2.isSelected() )
458                 selectArmy2.ResetSelected();
459 
460             std::set<ArtifactSetData> assembledArtifacts = bag_artifacts.assembleArtifactSetIfPossible();
461             const std::set<ArtifactSetData> otherHeroAssembledArtifacts = otherHero.bag_artifacts.assembleArtifactSetIfPossible();
462 
463             // Use insert instead of std::merge to make appveyour happy
464             assembledArtifacts.insert( otherHeroAssembledArtifacts.begin(), otherHeroAssembledArtifacts.end() );
465 
466             for ( const ArtifactSetData & artifactSetData : assembledArtifacts )
467                 Dialog::ArtifactInfo( "", _( artifactSetData._assembleMessage ), artifactSetData._assembledArtifactID );
468 
469             selectArtifacts1.Redraw();
470             selectArtifacts2.Redraw();
471 
472             backPrimary.restore();
473             fheroes2::RedrawPrimarySkillInfo( cur_pt, &primskill_bar1, &primskill_bar2 );
474             moraleIndicator1.Redraw();
475             moraleIndicator2.Redraw();
476             luckIndicator1.Redraw();
477             luckIndicator2.Redraw();
478 
479             display.render();
480         }
481 
482         if ( ( le.MouseCursor( primskill_bar1.GetArea() ) && primskill_bar1.QueueEventProcessing() )
483              || ( le.MouseCursor( primskill_bar2.GetArea() ) && primskill_bar2.QueueEventProcessing() )
484              || ( le.MouseCursor( secskill_bar1.GetArea() ) && secskill_bar1.QueueEventProcessing() )
485              || ( le.MouseCursor( secskill_bar2.GetArea() ) && secskill_bar2.QueueEventProcessing() ) ) {
486             display.render();
487         }
488 
489         if ( le.MouseCursor( moraleIndicator1.GetArea() ) ) {
490             MoraleIndicator::QueueEventProcessing( moraleIndicator1 );
491         }
492         else if ( le.MouseCursor( moraleIndicator2.GetArea() ) ) {
493             MoraleIndicator::QueueEventProcessing( moraleIndicator2 );
494         }
495         else if ( le.MouseCursor( luckIndicator1.GetArea() ) ) {
496             LuckIndicator::QueueEventProcessing( luckIndicator1 );
497         }
498         else if ( le.MouseCursor( luckIndicator2.GetArea() ) ) {
499             LuckIndicator::QueueEventProcessing( luckIndicator2 );
500         }
501 
502         if ( le.MouseClickLeft( hero1Area ) ) {
503             Game::OpenHeroesDialog( *this, false, false, true );
504 
505             armyCountBackgroundRestorerLeft.restore();
506             armyCountBackgroundRestorerRight.restore();
507 
508             selectArtifacts1.ResetSelected();
509             selectArtifacts2.ResetSelected();
510             selectArtifacts1.Redraw();
511             selectArtifacts2.Redraw();
512 
513             selectArmy1.ResetSelected();
514             selectArmy2.ResetSelected();
515             selectArmy1.Redraw();
516             selectArmy2.Redraw();
517 
518             moraleIndicator1.Redraw();
519             luckIndicator1.Redraw();
520 
521             display.render();
522         }
523         else if ( le.MouseClickLeft( hero2Area ) ) {
524             Game::OpenHeroesDialog( otherHero, false, false, true );
525 
526             armyCountBackgroundRestorerLeft.restore();
527             armyCountBackgroundRestorerRight.restore();
528 
529             selectArtifacts1.ResetSelected();
530             selectArtifacts2.ResetSelected();
531             selectArtifacts1.Redraw();
532             selectArtifacts2.Redraw();
533 
534             selectArmy1.ResetSelected();
535             selectArmy2.ResetSelected();
536             selectArmy1.Redraw();
537             selectArmy2.Redraw();
538 
539             moraleIndicator2.Redraw();
540             luckIndicator2.Redraw();
541 
542             display.render();
543         }
544         else if ( le.MouseClickLeft( moveArmyToHero2.area() ) ) {
545             otherHero.GetArmy().MoveTroops( GetArmy() );
546 
547             armyCountBackgroundRestorerLeft.restore();
548             armyCountBackgroundRestorerRight.restore();
549 
550             selectArmy1.ResetSelected();
551             selectArmy2.ResetSelected();
552             selectArmy1.Redraw();
553             selectArmy2.Redraw();
554 
555             moraleIndicator1.Redraw();
556             moraleIndicator2.Redraw();
557 
558             display.render();
559         }
560         else if ( le.MouseClickLeft( moveArmyToHero1.area() ) ) {
561             GetArmy().MoveTroops( otherHero.GetArmy() );
562 
563             armyCountBackgroundRestorerLeft.restore();
564             armyCountBackgroundRestorerRight.restore();
565 
566             selectArmy1.ResetSelected();
567             selectArmy2.ResetSelected();
568             selectArmy1.Redraw();
569             selectArmy2.Redraw();
570 
571             moraleIndicator1.Redraw();
572             moraleIndicator2.Redraw();
573 
574             display.render();
575         }
576         else if ( le.MouseClickLeft( moveArtifactsToHero2.area() ) ) {
577             moveArtifacts( GetBagArtifacts(), otherHero.GetBagArtifacts() );
578 
579             selectArtifacts1.ResetSelected();
580             selectArtifacts2.ResetSelected();
581             selectArtifacts1.Redraw();
582             selectArtifacts2.Redraw();
583 
584             backPrimary.restore();
585             fheroes2::RedrawPrimarySkillInfo( cur_pt, &primskill_bar1, &primskill_bar2 );
586             moraleIndicator1.Redraw();
587             moraleIndicator2.Redraw();
588             luckIndicator1.Redraw();
589             luckIndicator2.Redraw();
590 
591             display.render();
592         }
593         else if ( le.MouseClickLeft( moveArtifactsToHero1.area() ) ) {
594             moveArtifacts( otherHero.GetBagArtifacts(), GetBagArtifacts() );
595 
596             selectArtifacts1.ResetSelected();
597             selectArtifacts2.ResetSelected();
598             selectArtifacts1.Redraw();
599             selectArtifacts2.Redraw();
600 
601             backPrimary.restore();
602             fheroes2::RedrawPrimarySkillInfo( cur_pt, &primskill_bar1, &primskill_bar2 );
603             moraleIndicator1.Redraw();
604             moraleIndicator2.Redraw();
605             luckIndicator1.Redraw();
606             luckIndicator2.Redraw();
607 
608             display.render();
609         }
610     }
611 
612     selectArmy1.ResetSelected();
613     selectArmy2.ResetSelected();
614     selectArtifacts1.ResetSelected();
615     selectArtifacts2.ResetSelected();
616 
617     backPrimary.reset();
618     armyCountBackgroundRestorerLeft.reset();
619     armyCountBackgroundRestorerRight.reset();
620     restorer.restore();
621     display.render();
622 }
623 
ScholarAction(Heroes & hero1,Heroes & hero2)624 void Heroes::ScholarAction( Heroes & hero1, Heroes & hero2 )
625 {
626     if ( !hero1.HaveSpellBook() || !hero2.HaveSpellBook() ) {
627         DEBUG_LOG( DBG_GAME, DBG_INFO, "spell_book disabled" );
628         return;
629     }
630     else if ( !Settings::Get().ExtWorldEyeEagleAsScholar() ) {
631         DEBUG_LOG( DBG_GAME, DBG_WARN, "EyeEagleAsScholar settings disabled" );
632         return;
633     }
634 
635     const int scholar1 = hero1.GetLevelSkill( Skill::Secondary::EAGLEEYE );
636     const int scholar2 = hero2.GetLevelSkill( Skill::Secondary::EAGLEEYE );
637     int scholar = 0;
638 
639     Heroes * teacher = nullptr;
640     Heroes * learner = nullptr;
641 
642     if ( scholar1 && scholar1 >= scholar2 ) {
643         teacher = &hero1;
644         learner = &hero2;
645         scholar = scholar1;
646     }
647     else if ( scholar2 && scholar2 >= scholar1 ) {
648         teacher = &hero2;
649         learner = &hero1;
650         scholar = scholar2;
651     }
652     else {
653         DEBUG_LOG( DBG_GAME, DBG_WARN, "Eagle Eye skill not found" );
654         return;
655     }
656 
657     // skip bag artifacts
658     SpellStorage teach = teacher->spell_book.SetFilter( SpellBook::Filter::ALL );
659     SpellStorage learn = learner->spell_book.SetFilter( SpellBook::Filter::ALL );
660 
661     // remove_if for learn spells
662     if ( !learn.empty() ) {
663         learn.erase( std::remove_if( learn.begin(), learn.end(),
664                                      [teacher]( const Spell & spell ) { return teacher->HaveSpell( spell ) || !teacher->CanTeachSpell( spell ); } ),
665                      learn.end() );
666     }
667 
668     // remove_if for teach spells
669     if ( !teach.empty() ) {
670         teach.erase( std::remove_if( teach.begin(), teach.end(),
671                                      [learner, teacher]( const Spell & spell ) { return learner->HaveSpell( spell ) || !teacher->CanTeachSpell( spell ); } ),
672                      teach.end() );
673     }
674 
675     std::string spells1;
676     std::string spells2;
677 
678     // learning
679     for ( SpellStorage::const_iterator it = learn.begin(); it != learn.end(); ++it ) {
680         teacher->AppendSpellToBook( *it );
681         if ( !spells1.empty() )
682             spells1.append( it + 1 == learn.end() ? _( " and " ) : ", " );
683         spells1.append( ( *it ).GetName() );
684     }
685 
686     // teacher
687     for ( SpellStorage::const_iterator it = teach.begin(); it != teach.end(); ++it ) {
688         learner->AppendSpellToBook( *it );
689         if ( !spells2.empty() )
690             spells2.append( it + 1 == teach.end() ? _( " and " ) : ", " );
691         spells2.append( ( *it ).GetName() );
692     }
693 
694     if ( teacher->isControlHuman() || learner->isControlHuman() ) {
695         std::string message;
696 
697         if ( !spells1.empty() && !spells2.empty() )
698             message = _( "%{teacher}, whose %{level} %{scholar} knows many magical secrets, learns %{spells1} from %{learner}, and teaches %{spells2} to %{learner}." );
699         else if ( !spells1.empty() )
700             message = _( "%{teacher}, whose %{level} %{scholar} knows many magical secrets, learns %{spells1} from %{learner}." );
701         else if ( !spells2.empty() )
702             message = _( "%{teacher}, whose %{level} %{scholar} knows many magical secrets, teaches %{spells2} to %{learner}." );
703 
704         if ( !message.empty() ) {
705             StringReplace( message, "%{teacher}", teacher->GetName() );
706             StringReplace( message, "%{learner}", learner->GetName() );
707             StringReplace( message, "%{level}", Skill::Level::String( scholar ) );
708             StringReplace( message, "%{scholar}", Skill::Secondary::String( Skill::Secondary::EAGLEEYE ) );
709             StringReplace( message, "%{spells1}", spells1 );
710             StringReplace( message, "%{spells2}", spells2 );
711 
712             Dialog::Message( _( "Scholar Ability" ), message, Font::BIG, Dialog::OK );
713         }
714     }
715 }
716