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 <cassert>
25 #include <string>
26 #include <vector>
27 
28 #include "agg.h"
29 #include "agg_image.h"
30 #include "cursor.h"
31 #include "dialog.h"
32 #include "dialog_selectscenario.h"
33 #include "difficulty.h"
34 #include "game.h"
35 #include "game_interface.h"
36 #include "game_mainmenu_ui.h"
37 #include "gamedefs.h"
38 #include "icn.h"
39 #include "logging.h"
40 #include "maps_fileinfo.h"
41 #include "mus.h"
42 #include "player_info.h"
43 #include "settings.h"
44 #include "system.h"
45 #include "text.h"
46 #include "tools.h"
47 #include "translations.h"
48 #include "ui_button.h"
49 #include "ui_text.h"
50 #include "ui_tool.h"
51 #include "world.h"
52 
53 namespace
54 {
updatePlayers(Players & players,const int humanPlayerCount)55     void updatePlayers( Players & players, const int humanPlayerCount )
56     {
57         if ( humanPlayerCount < 2 )
58             return;
59 
60         int foundHumans = 0;
61 
62         for ( size_t i = 0; i < players.size(); ++i ) {
63             if ( players[i]->isControlHuman() ) {
64                 ++foundHumans;
65                 if ( players[i]->isControlAI() )
66                     players[i]->SetControl( CONTROL_HUMAN );
67             }
68 
69             if ( foundHumans == humanPlayerCount )
70                 break;
71         }
72     }
73 
GetSelectedMapId(const MapsFileInfoList & lists)74     size_t GetSelectedMapId( const MapsFileInfoList & lists )
75     {
76         const Settings & conf = Settings::Get();
77 
78         const std::string & mapName = conf.CurrentFileInfo().name;
79         const std::string & mapFileName = System::GetBasename( conf.CurrentFileInfo().file );
80         size_t mapId = 0;
81         for ( MapsFileInfoList::const_iterator mapIter = lists.begin(); mapIter != lists.end(); ++mapIter, ++mapId ) {
82             if ( ( mapIter->name == mapName ) && ( System::GetBasename( mapIter->file ) == mapFileName ) ) {
83                 return mapId;
84             }
85         }
86 
87         return 0;
88     }
89 
RedrawScenarioStaticInfo(const fheroes2::Rect & rt,bool firstDraw=false)90     void RedrawScenarioStaticInfo( const fheroes2::Rect & rt, bool firstDraw = false )
91     {
92         const Settings & conf = Settings::Get();
93         fheroes2::Display & display = fheroes2::Display::instance();
94 
95         if ( firstDraw ) {
96             fheroes2::Blit( fheroes2::AGG::GetICN( ICN::NGHSBKG, 1 ), display, rt.x - BORDERWIDTH, rt.y + BORDERWIDTH );
97         }
98 
99         // image panel
100         const fheroes2::Sprite & panel = fheroes2::AGG::GetICN( ICN::NGHSBKG, 0 );
101         fheroes2::Blit( panel, display, rt.x, rt.y );
102 
103         // Redraw select button as the original image has a wrong position of it
104         fheroes2::Blit( fheroes2::AGG::GetICN( ICN::NGEXTRA, 64 ), display, rt.x + 309, rt.y + 45 );
105 
106         fheroes2::FontType normalWhiteFont = { fheroes2::FontSize::NORMAL, fheroes2::FontColor::WHITE };
107 
108         // text scenario
109         fheroes2::Text text( _( "Scenario:" ), normalWhiteFont );
110         text.draw( rt.x + ( rt.width - text.width() ) / 2, rt.y + 25, display );
111 
112         // maps name
113         text.set( conf.MapsName(), normalWhiteFont );
114         text.draw( rt.x + ( rt.width - text.width() ) / 2, rt.y + 48, display );
115 
116         // text game difficulty
117         text.set( _( "Game Difficulty:" ), normalWhiteFont );
118         text.draw( rt.x + ( rt.width - text.width() ) / 2, rt.y + 77, display );
119 
120         // text opponents
121         text.set( _( "Opponents:" ), normalWhiteFont );
122         text.draw( rt.x + ( rt.width - text.width() ) / 2, rt.y + 183, display );
123 
124         // text class
125         text.set( _( "Class:" ), normalWhiteFont );
126         text.draw( rt.x + ( rt.width - text.width() ) / 2, rt.y + 264, display );
127     }
128 
RedrawDifficultyInfo(const fheroes2::Point & dst)129     void RedrawDifficultyInfo( const fheroes2::Point & dst )
130     {
131         const uint32_t width = 77;
132         const uint32_t height = 70;
133 
134         for ( u32 current = Difficulty::EASY; current <= Difficulty::IMPOSSIBLE; ++current ) {
135             const uint32_t offset = width * current;
136 
137             fheroes2::Text text( Difficulty::String( current ), { fheroes2::FontSize::SMALL, fheroes2::FontColor::WHITE } );
138             text.draw( dst.x + 31 + offset - ( text.width() / 2 ), dst.y + height, fheroes2::Display::instance() );
139         }
140     }
141 
RedrawRatingInfo(TextSprite & sprite)142     void RedrawRatingInfo( TextSprite & sprite )
143     {
144         sprite.Hide();
145         std::string str( _( "Rating %{rating}%" ) );
146         StringReplace( str, "%{rating}", Game::GetRating() );
147         sprite.SetText( str );
148         sprite.Show();
149     }
150 
ChooseNewMap(const MapsFileInfoList & lists)151     fheroes2::GameMode ChooseNewMap( const MapsFileInfoList & lists )
152     {
153         // setup cursor
154         const CursorRestorer cursorRestorer( true, Cursor::POINTER );
155 
156         fheroes2::Display & display = fheroes2::Display::instance();
157         const fheroes2::Sprite & panel = fheroes2::AGG::GetICN( ICN::NGHSBKG, 0 );
158         const fheroes2::Rect rectPanel( ( display.width() - panel.width() ) / 2, ( display.height() - panel.height() ) / 2, panel.width(), panel.height() );
159         const fheroes2::Point pointDifficultyInfo( rectPanel.x + 24, rectPanel.y + 93 );
160         const fheroes2::Point pointOpponentInfo( rectPanel.x + 24, rectPanel.y + 202 );
161         const fheroes2::Point pointClassInfo( rectPanel.x + 24, rectPanel.y + 282 );
162 
163         const fheroes2::Sprite & ngextra = fheroes2::AGG::GetICN( ICN::NGEXTRA, 62 );
164 
165         const int32_t ngextraWidth = ngextra.width();
166         const int32_t ngextraHeight = ngextra.height();
167 
168         // vector coord difficulty
169         std::vector<fheroes2::Rect> coordDifficulty;
170         coordDifficulty.reserve( 5 );
171 
172         coordDifficulty.emplace_back( rectPanel.x + 21, rectPanel.y + 91, ngextraWidth, ngextraHeight );
173         coordDifficulty.emplace_back( rectPanel.x + 98, rectPanel.y + 91, ngextraWidth, ngextraHeight );
174         coordDifficulty.emplace_back( rectPanel.x + 174, rectPanel.y + 91, ngextraWidth, ngextraHeight );
175         coordDifficulty.emplace_back( rectPanel.x + 251, rectPanel.y + 91, ngextraWidth, ngextraHeight );
176         coordDifficulty.emplace_back( rectPanel.x + 328, rectPanel.y + 91, ngextraWidth, ngextraHeight );
177 
178         fheroes2::Button buttonSelectMaps( rectPanel.x + 309, rectPanel.y + 45, ICN::NGEXTRA, 64, 65 );
179         fheroes2::Button buttonOk( rectPanel.x + 31, rectPanel.y + 380, ICN::NGEXTRA, 66, 67 );
180         fheroes2::Button buttonCancel( rectPanel.x + 287, rectPanel.y + 380, ICN::NGEXTRA, 68, 69 );
181 
182         fheroes2::drawMainMenuScreen();
183 
184         Settings & conf = Settings::Get();
185         bool resetStartingSettings = conf.MapsFile().empty();
186         Players & players = conf.GetPlayers();
187         Interface::PlayersInfo playersInfo( true, true, true );
188 
189         const int humanPlayerCount = Settings::Get().PreferablyCountPlayers();
190 
191         if ( !resetStartingSettings ) { // verify that current map really exists in map's list
192             resetStartingSettings = true;
193             const std::string & mapName = conf.CurrentFileInfo().name;
194             const std::string & mapFileName = System::GetBasename( conf.CurrentFileInfo().file );
195             for ( const Maps::FileInfo & mapInfo : lists ) {
196                 if ( ( mapInfo.name == mapName ) && ( System::GetBasename( mapInfo.file ) == mapFileName ) ) {
197                     if ( mapInfo.file == conf.CurrentFileInfo().file ) {
198                         conf.SetCurrentFileInfo( mapInfo );
199                         updatePlayers( players, humanPlayerCount );
200                         Game::LoadPlayers( mapInfo.file, players );
201                         resetStartingSettings = false;
202                         break;
203                     }
204                 }
205             }
206         }
207 
208         // set first map's settings
209         if ( resetStartingSettings ) {
210             conf.SetCurrentFileInfo( lists.front() );
211             updatePlayers( players, humanPlayerCount );
212             Game::LoadPlayers( lists.front().file, players );
213         }
214 
215         playersInfo.UpdateInfo( players, pointOpponentInfo, pointClassInfo );
216 
217         RedrawScenarioStaticInfo( rectPanel, true );
218         RedrawDifficultyInfo( pointDifficultyInfo );
219 
220         playersInfo.RedrawInfo();
221 
222         TextSprite rating;
223         rating.SetFont( Font::BIG );
224         rating.SetPos( rectPanel.x + 166, rectPanel.y + 383 );
225         RedrawRatingInfo( rating );
226 
227         fheroes2::MovableSprite levelCursor( ngextra );
228 
229         switch ( Game::getDifficulty() ) {
230         case Difficulty::EASY:
231             levelCursor.setPosition( coordDifficulty[0].x, coordDifficulty[0].y );
232             break;
233         case Difficulty::NORMAL:
234             levelCursor.setPosition( coordDifficulty[1].x, coordDifficulty[1].y );
235             break;
236         case Difficulty::HARD:
237             levelCursor.setPosition( coordDifficulty[2].x, coordDifficulty[2].y );
238             break;
239         case Difficulty::EXPERT:
240             levelCursor.setPosition( coordDifficulty[3].x, coordDifficulty[3].y );
241             break;
242         case Difficulty::IMPOSSIBLE:
243             levelCursor.setPosition( coordDifficulty[4].x, coordDifficulty[4].y );
244             break;
245         default:
246             // Did you add a new difficulty mode? Add the corresponding case above!
247             assert( 0 );
248             break;
249         }
250         levelCursor.redraw();
251 
252         buttonSelectMaps.draw();
253         buttonOk.draw();
254         buttonCancel.draw();
255 
256         display.render();
257 
258         fheroes2::GameMode result = fheroes2::GameMode::QUIT_GAME;
259         LocalEvent & le = LocalEvent::Get();
260         while ( true ) {
261             if ( !le.HandleEvents( true, true ) ) {
262                 if ( Interface::Basic::EventExit() == fheroes2::GameMode::QUIT_GAME ) {
263                     if ( conf.ExtGameUseFade() ) {
264                         fheroes2::FadeDisplay();
265                     }
266                     return fheroes2::GameMode::QUIT_GAME;
267                 }
268 
269                 continue;
270             }
271 
272             // press button
273             le.MousePressLeft( buttonSelectMaps.area() ) ? buttonSelectMaps.drawOnPress() : buttonSelectMaps.drawOnRelease();
274             le.MousePressLeft( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease();
275             le.MousePressLeft( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease();
276 
277             // click select
278             if ( HotKeyPressEvent( Game::EVENT_BUTTON_SELECT ) || le.MouseClickLeft( buttonSelectMaps.area() ) ) {
279                 const Maps::FileInfo * fi = Dialog::SelectScenario( lists, GetSelectedMapId( lists ) );
280 
281                 if ( fi ) {
282                     Game::SavePlayers( conf.CurrentFileInfo().file, conf.GetPlayers() );
283                     conf.SetCurrentFileInfo( *fi );
284                     Game::LoadPlayers( fi->file, players );
285 
286                     updatePlayers( players, humanPlayerCount );
287                     playersInfo.UpdateInfo( players, pointOpponentInfo, pointClassInfo );
288 
289                     RedrawScenarioStaticInfo( rectPanel );
290                     RedrawDifficultyInfo( pointDifficultyInfo );
291                     playersInfo.resetSelection();
292                     playersInfo.RedrawInfo();
293                     RedrawRatingInfo( rating );
294                     levelCursor.setPosition( coordDifficulty[Game::getDifficulty()].x, coordDifficulty[Game::getDifficulty()].y ); // From 0 to 4, see: Difficulty enum
295                     buttonOk.draw();
296                     buttonCancel.draw();
297                 }
298 
299                 display.render();
300             }
301             else if ( Game::HotKeyPressEvent( Game::EVENT_DEFAULT_EXIT ) || le.MouseClickLeft( buttonCancel.area() ) ) {
302                 result = fheroes2::GameMode::MAIN_MENU;
303                 break;
304             }
305             else if ( Game::HotKeyPressEvent( Game::EVENT_DEFAULT_READY ) || le.MouseClickLeft( buttonOk.area() ) ) {
306                 DEBUG_LOG( DBG_GAME, DBG_INFO, "select maps: " << conf.MapsFile() << ", difficulty: " << Difficulty::String( Game::getDifficulty() ) );
307                 result = fheroes2::GameMode::START_GAME;
308                 break;
309             }
310             else if ( le.MouseClickLeft( rectPanel ) ) {
311                 const int32_t index = GetRectIndex( coordDifficulty, le.GetMouseCursor() );
312 
313                 // select difficulty
314                 if ( 0 <= index ) {
315                     levelCursor.setPosition( coordDifficulty[index].x, coordDifficulty[index].y );
316                     levelCursor.redraw();
317                     Game::saveDifficulty( index );
318                     RedrawRatingInfo( rating );
319                     display.render();
320                 }
321                 // playersInfo
322                 else if ( playersInfo.QueueEventProcessing() ) {
323                     RedrawScenarioStaticInfo( rectPanel );
324                     levelCursor.redraw();
325                     RedrawDifficultyInfo( pointDifficultyInfo );
326 
327                     playersInfo.RedrawInfo();
328                     RedrawRatingInfo( rating );
329                     buttonOk.draw();
330                     buttonCancel.draw();
331                     display.render();
332                 }
333             }
334             else if ( le.MouseWheelUp() || le.MouseWheelDn() ) {
335                 if ( playersInfo.QueueEventProcessing() ) {
336                     playersInfo.resetSelection();
337 
338                     RedrawScenarioStaticInfo( rectPanel );
339                     levelCursor.redraw();
340                     RedrawDifficultyInfo( pointDifficultyInfo );
341 
342                     playersInfo.RedrawInfo();
343                     RedrawRatingInfo( rating );
344                     buttonOk.draw();
345                     buttonCancel.draw();
346                     display.render();
347                 }
348             }
349 
350             if ( le.MousePressRight( rectPanel ) ) {
351                 if ( le.MousePressRight( buttonSelectMaps.area() ) )
352                     Dialog::Message( _( "Scenario" ), _( "Click here to select which scenario to play." ), Font::BIG );
353                 else if ( 0 <= GetRectIndex( coordDifficulty, le.GetMouseCursor() ) )
354                     Dialog::Message(
355                         _( "Game Difficulty" ),
356                         _( "This lets you change the starting difficulty at which you will play. Higher difficulty levels start you of with fewer resources, and at the higher settings, give extra resources to the computer." ),
357                         Font::BIG );
358                 else if ( le.MousePressRight( rating.GetRect() ) )
359                     Dialog::
360                         Message( _( "Difficulty Rating" ),
361                                  _( "The difficulty rating reflects a combination of various settings for your game. This number will be applied to your final score." ),
362                                  Font::BIG );
363                 else if ( le.MousePressRight( buttonOk.area() ) )
364                     Dialog::Message( _( "OK" ), _( "Click to accept these settings and start a new game." ), Font::BIG );
365                 else if ( le.MousePressRight( buttonCancel.area() ) )
366                     Dialog::Message( _( "Cancel" ), _( "Click to return to the main menu." ), Font::BIG );
367                 else
368                     playersInfo.QueueEventProcessing();
369             }
370         }
371 
372         Game::SavePlayers( conf.CurrentFileInfo().file, conf.GetPlayers() );
373 
374         return result;
375     }
376 
LoadNewMap()377     fheroes2::GameMode LoadNewMap()
378     {
379         Settings & conf = Settings::Get();
380 
381         conf.GetPlayers().SetStartGame();
382         if ( conf.ExtGameUseFade() ) {
383             fheroes2::FadeDisplay();
384         }
385 
386         Game::ShowMapLoadingText();
387         // Load maps
388         std::string lower = StringLower( conf.MapsFile() );
389 
390         if ( lower.size() > 3 ) {
391             std::string ext = lower.substr( lower.size() - 3 );
392 
393             if ( ext == "mp2" || ext == "mx2" ) {
394                 return world.LoadMapMP2( conf.MapsFile() ) ? fheroes2::GameMode::START_GAME : fheroes2::GameMode::MAIN_MENU;
395             }
396 
397             DEBUG_LOG( DBG_GAME, DBG_WARN,
398                        conf.MapsFile() << ", "
399                                        << "unknown map format" );
400             return fheroes2::GameMode::MAIN_MENU;
401         }
402 
403         DEBUG_LOG( DBG_GAME, DBG_WARN,
404                    conf.MapsFile() << ", "
405                                    << "unknown map format" );
406         return fheroes2::GameMode::MAIN_MENU;
407     }
408 }
409 
SelectScenario()410 fheroes2::GameMode Game::SelectScenario()
411 {
412     return fheroes2::GameMode::SCENARIO_INFO;
413 }
414 
ScenarioInfo()415 fheroes2::GameMode Game::ScenarioInfo()
416 {
417     AGG::PlayMusic( MUS::MAINMENU, true, true );
418 
419     const MapsFileInfoList lists = Maps::PrepareMapsFileInfoList( Settings::Get().IsGameType( Game::TYPE_MULTI ) );
420     if ( lists.empty() ) {
421         Dialog::Message( _( "Warning" ), _( "No maps available!" ), Font::BIG, Dialog::OK );
422         return fheroes2::GameMode::MAIN_MENU;
423     }
424 
425     // We must release UI resources for this window before loading a new map. That's why all UI logic is in a separate function.
426     const fheroes2::GameMode result = ChooseNewMap( lists );
427     if ( result != fheroes2::GameMode::START_GAME ) {
428         return result;
429     }
430 
431     return LoadNewMap();
432 }
433 
GetStep4Player(const int32_t currentId,const int32_t width,const int32_t totalCount)434 int32_t Game::GetStep4Player( const int32_t currentId, const int32_t width, const int32_t totalCount )
435 {
436     return currentId * width * KINGDOMMAX / totalCount + ( width * ( KINGDOMMAX - totalCount ) / ( 2 * totalCount ) );
437 }
438