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