2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 /** @file intro_gui.cpp The main menu GUI. */
13 #include "window_gui.h"
14 #include "window_func.h"
15 #include "textbuf_gui.h"
17 #include "network/network.h"
19 #include "network/network_gui.h"
20 #include "network/network_content.h"
21 #include "network/network_survey.h"
22 #include "landscape_type.h"
23 #include "landscape.h"
24 #include "strings_func.h"
26 #include "ai/ai_gui.hpp"
27 #include "game/game_gui.hpp"
29 #include "core/geometry_func.hpp"
32 #include "highscore.h"
33 #include "signs_base.h"
34 #include "viewport_func.h"
35 #include "vehicle_base.h"
38 #include "widgets/intro_widget.h"
40 #include "table/strings.h"
41 #include "table/sprites.h"
43 #include "safeguards.h"
47 * A viewport command for the main menu background (intro game).
49 struct IntroGameViewportCommand
{
50 /** Horizontal alignment value. */
51 enum AlignmentH
: byte
{
56 /** Vertical alignment value. */
57 enum AlignmentV
: byte
{
63 int command_index
= 0; ///< Sequence number of the command (order they are performed in).
64 Point position
{ 0, 0 }; ///< Calculated world coordinate to position viewport top-left at.
65 VehicleID vehicle
= INVALID_VEHICLE
; ///< Vehicle to follow, or INVALID_VEHICLE if not following a vehicle.
66 uint delay
= 0; ///< Delay until next command.
67 int zoom_adjust
= 0; ///< Adjustment to zoom level from base zoom level.
68 bool pan_to_next
= false; ///< If true, do a smooth pan from this position to the next.
69 AlignmentH align_h
= CENTRE
; ///< Horizontal alignment.
70 AlignmentV align_v
= MIDDLE
; ///< Vertical alignment.
73 * Calculate effective position.
74 * This will update the position field if a vehicle is followed.
75 * @param vp Viewport to calculate position for.
76 * @return Calculated position in the viewport.
78 Point
PositionForViewport(const Viewport
*vp
)
80 if (this->vehicle
!= INVALID_VEHICLE
) {
81 const Vehicle
*v
= Vehicle::Get(this->vehicle
);
82 this->position
= RemapCoords(v
->x_pos
, v
->y_pos
, v
->z_pos
);
86 switch (this->align_h
) {
87 case LEFT
: p
.x
= this->position
.x
; break;
88 case CENTRE
: p
.x
= this->position
.x
- vp
->virtual_width
/ 2; break;
89 case RIGHT
: p
.x
= this->position
.x
- vp
->virtual_width
; break;
91 switch (this->align_v
) {
92 case TOP
: p
.y
= this->position
.y
; break;
93 case MIDDLE
: p
.y
= this->position
.y
- vp
->virtual_height
/ 2; break;
94 case BOTTOM
: p
.y
= this->position
.y
- vp
->virtual_height
; break;
101 struct SelectGameWindow
: public Window
{
102 /** Vector of viewport commands parsed. */
103 std::vector
<IntroGameViewportCommand
> intro_viewport_commands
;
104 /** Index of currently active viewport command. */
105 size_t cur_viewport_command_index
;
106 /** Time spent (milliseconds) on current viewport command. */
107 uint cur_viewport_command_time
;
108 uint mouse_idle_time
;
109 Point mouse_idle_pos
;
112 * Find and parse all viewport command signs.
113 * Fills the intro_viewport_commands vector and deletes parsed signs from the world.
115 void ReadIntroGameViewportCommands()
117 intro_viewport_commands
.clear();
119 /* Regular expression matching the commands: T, spaces, integer, spaces, flags, spaces, integer */
120 const char *sign_langauge
= "^T\\s*([0-9]+)\\s*([-+A-Z0-9]+)\\s*([0-9]+)";
121 std::regex
re(sign_langauge
, std::regex_constants::icase
);
123 /* List of signs successfully parsed to delete afterwards. */
124 std::vector
<SignID
> signs_to_delete
;
126 for (const Sign
*sign
: Sign::Iterate()) {
128 if (std::regex_search(sign
->name
, match
, re
)) {
129 IntroGameViewportCommand vc
;
130 /* Sequence index from the first matching group. */
131 vc
.command_index
= std::stoi(match
[1].str());
132 /* Sign coordinates for positioning. */
133 vc
.position
= RemapCoords(sign
->x
, sign
->y
, sign
->z
);
134 /* Delay from the third matching group. */
135 vc
.delay
= std::stoi(match
[3].str()) * 1000; // milliseconds
137 /* Parse flags from second matching group. */
141 for (char c
: match
[2].str()) {
143 if (id_type
== ID_VEHICLE
) {
144 vc
.vehicle
= vc
.vehicle
* 10 + (c
- '0');
148 switch (toupper(c
)) {
149 case '-': vc
.zoom_adjust
= +1; break;
150 case '+': vc
.zoom_adjust
= -1; break;
151 case 'T': vc
.align_v
= IntroGameViewportCommand::TOP
; break;
152 case 'M': vc
.align_v
= IntroGameViewportCommand::MIDDLE
; break;
153 case 'B': vc
.align_v
= IntroGameViewportCommand::BOTTOM
; break;
154 case 'L': vc
.align_h
= IntroGameViewportCommand::LEFT
; break;
155 case 'C': vc
.align_h
= IntroGameViewportCommand::CENTRE
; break;
156 case 'R': vc
.align_h
= IntroGameViewportCommand::RIGHT
; break;
157 case 'P': vc
.pan_to_next
= true; break;
158 case 'V': id_type
= ID_VEHICLE
; vc
.vehicle
= 0; break;
163 /* Successfully parsed, store. */
164 intro_viewport_commands
.push_back(vc
);
165 signs_to_delete
.push_back(sign
->index
);
169 /* Sort the commands by sequence index. */
170 std::sort(intro_viewport_commands
.begin(), intro_viewport_commands
.end(), [](const IntroGameViewportCommand
&a
, const IntroGameViewportCommand
&b
) { return a
.command_index
< b
.command_index
; });
172 /* Delete all the consumed signs, from last ID to first ID. */
173 std::sort(signs_to_delete
.begin(), signs_to_delete
.end(), [](SignID a
, SignID b
) { return a
> b
; });
174 for (SignID sign_id
: signs_to_delete
) {
175 delete Sign::Get(sign_id
);
179 SelectGameWindow(WindowDesc
*desc
) : Window(desc
)
181 this->CreateNestedTree();
182 this->FinishInitNested(0);
183 this->OnInvalidateData();
185 this->ReadIntroGameViewportCommands();
187 this->cur_viewport_command_index
= (size_t)-1;
188 this->cur_viewport_command_time
= 0;
189 this->mouse_idle_time
= 0;
190 this->mouse_idle_pos
= _cursor
.pos
;
193 void OnRealtimeTick(uint delta_ms
) override
195 /* Move the main game viewport according to intro viewport commands. */
197 if (intro_viewport_commands
.empty()) return;
199 bool suppress_panning
= true;
200 if (this->mouse_idle_pos
.x
!= _cursor
.pos
.x
|| this->mouse_idle_pos
.y
!= _cursor
.pos
.y
) {
201 this->mouse_idle_pos
= _cursor
.pos
;
202 this->mouse_idle_time
= 2000;
203 } else if (this->mouse_idle_time
> delta_ms
) {
204 this->mouse_idle_time
-= delta_ms
;
206 this->mouse_idle_time
= 0;
207 suppress_panning
= false;
210 /* Determine whether to move to the next command or stay at current. */
211 bool changed_command
= false;
212 if (this->cur_viewport_command_index
>= intro_viewport_commands
.size()) {
213 /* Reached last, rotate back to start of the list. */
214 this->cur_viewport_command_index
= 0;
215 changed_command
= true;
217 /* Check if current command has elapsed and switch to next. */
218 this->cur_viewport_command_time
+= delta_ms
;
219 if (this->cur_viewport_command_time
>= intro_viewport_commands
[this->cur_viewport_command_index
].delay
) {
220 this->cur_viewport_command_index
= (this->cur_viewport_command_index
+ 1) % intro_viewport_commands
.size();
221 this->cur_viewport_command_time
= 0;
222 changed_command
= true;
226 IntroGameViewportCommand
&vc
= intro_viewport_commands
[this->cur_viewport_command_index
];
227 Window
*mw
= GetMainWindow();
228 Viewport
*vp
= mw
->viewport
;
230 /* Early exit if the current command hasn't elapsed and isn't animated. */
231 if (!changed_command
&& !vc
.pan_to_next
&& vc
.vehicle
== INVALID_VEHICLE
) return;
233 /* Suppress panning commands, while user interacts with GUIs. */
234 if (!changed_command
&& suppress_panning
) return;
236 /* Reset the zoom level. */
237 if (changed_command
) FixTitleGameZoom(vc
.zoom_adjust
);
239 /* Calculate current command position (updates followed vehicle coordinates). */
240 Point pos
= vc
.PositionForViewport(vp
);
242 /* Calculate panning (linear interpolation between current and next command position). */
243 if (vc
.pan_to_next
) {
244 size_t next_command_index
= (this->cur_viewport_command_index
+ 1) % intro_viewport_commands
.size();
245 IntroGameViewportCommand
&nvc
= intro_viewport_commands
[next_command_index
];
246 Point pos2
= nvc
.PositionForViewport(vp
);
247 const double t
= this->cur_viewport_command_time
/ (double)vc
.delay
;
248 pos
.x
= pos
.x
+ (int)(t
* (pos2
.x
- pos
.x
));
249 pos
.y
= pos
.y
+ (int)(t
* (pos2
.y
- pos
.y
));
252 /* Update the viewport position. */
253 mw
->viewport
->dest_scrollpos_x
= mw
->viewport
->scrollpos_x
= pos
.x
;
254 mw
->viewport
->dest_scrollpos_y
= mw
->viewport
->scrollpos_y
= pos
.y
;
255 UpdateViewportPosition(mw
, delta_ms
);
256 mw
->SetDirty(); // Required during panning, otherwise logo graphics disappears
258 /* If there is only one command, we just executed it and don't need to do any more */
259 if (intro_viewport_commands
.size() == 1 && vc
.vehicle
== INVALID_VEHICLE
) intro_viewport_commands
.clear();
263 * Some data on this window has become invalid.
264 * @param data Information about the changed data.
265 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
267 void OnInvalidateData([[maybe_unused
]] int data
= 0, [[maybe_unused
]] bool gui_scope
= true) override
269 if (!gui_scope
) return;
270 this->SetWidgetLoweredState(WID_SGI_TEMPERATE_LANDSCAPE
, _settings_newgame
.game_creation
.landscape
== LT_TEMPERATE
);
271 this->SetWidgetLoweredState(WID_SGI_ARCTIC_LANDSCAPE
, _settings_newgame
.game_creation
.landscape
== LT_ARCTIC
);
272 this->SetWidgetLoweredState(WID_SGI_TROPIC_LANDSCAPE
, _settings_newgame
.game_creation
.landscape
== LT_TROPIC
);
273 this->SetWidgetLoweredState(WID_SGI_TOYLAND_LANDSCAPE
, _settings_newgame
.game_creation
.landscape
== LT_TOYLAND
);
276 void OnInit() override
278 bool missing_sprites
= _missing_extra_graphics
> 0 && !IsReleasedVersion();
279 this->GetWidget
<NWidgetStacked
>(WID_SGI_BASESET_SELECTION
)->SetDisplayedPlane(missing_sprites
? 0 : SZSP_NONE
);
281 bool missing_lang
= _current_language
->missing
>= _settings_client
.gui
.missing_strings_threshold
&& !IsReleasedVersion();
282 this->GetWidget
<NWidgetStacked
>(WID_SGI_TRANSLATION_SELECTION
)->SetDisplayedPlane(missing_lang
? 0 : SZSP_NONE
);
285 void DrawWidget(const Rect
&r
, WidgetID widget
) const override
288 case WID_SGI_BASESET
:
289 SetDParam(0, _missing_extra_graphics
);
290 DrawStringMultiLine(r
.left
, r
.right
, r
.top
, r
.bottom
, STR_INTRO_BASESET
, TC_FROMSTRING
, SA_CENTER
);
293 case WID_SGI_TRANSLATION
:
294 SetDParam(0, _current_language
->missing
);
295 DrawStringMultiLine(r
.left
, r
.right
, r
.top
, r
.bottom
, STR_INTRO_TRANSLATION
, TC_FROMSTRING
, SA_CENTER
);
300 void UpdateWidgetSize(WidgetID widget
, Dimension
*size
, [[maybe_unused
]] const Dimension
&padding
, [[maybe_unused
]] Dimension
*fill
, [[maybe_unused
]] Dimension
*resize
) override
303 case WID_SGI_TEMPERATE_LANDSCAPE
: case WID_SGI_ARCTIC_LANDSCAPE
:
304 case WID_SGI_TROPIC_LANDSCAPE
: case WID_SGI_TOYLAND_LANDSCAPE
:
305 size
->width
+= WidgetDimensions::scaled
.fullbevel
.Horizontal();
306 size
->height
+= WidgetDimensions::scaled
.fullbevel
.Vertical();
311 void OnResize() override
313 bool changed
= false;
315 if (NWidgetResizeBase
*wid
= this->GetWidget
<NWidgetResizeBase
>(WID_SGI_BASESET
); wid
!= nullptr && wid
->current_x
> 0) {
316 SetDParam(0, _missing_extra_graphics
);
317 changed
|= wid
->UpdateMultilineWidgetSize(GetString(STR_INTRO_BASESET
), 3);
320 if (NWidgetResizeBase
*wid
= this->GetWidget
<NWidgetResizeBase
>(WID_SGI_TRANSLATION
); wid
!= nullptr && wid
->current_x
> 0) {
321 SetDParam(0, _current_language
->missing
);
322 changed
|= wid
->UpdateMultilineWidgetSize(GetString(STR_INTRO_TRANSLATION
), 3);
325 if (changed
) this->ReInit(0, 0, this->flags
& WF_CENTERED
);
328 void OnClick([[maybe_unused
]] Point pt
, WidgetID widget
, [[maybe_unused
]] int click_count
) override
330 /* Do not create a network server when you (just) have closed one of the game
331 * creation/load windows for the network server. */
332 if (IsInsideMM(widget
, WID_SGI_GENERATE_GAME
, WID_SGI_EDIT_SCENARIO
+ 1)) _is_network_server
= false;
335 case WID_SGI_GENERATE_GAME
:
337 StartNewGameWithoutGUI(GENERATE_NEW_SEED
);
339 ShowGenerateLandscape();
343 case WID_SGI_LOAD_GAME
: ShowSaveLoadDialog(FT_SAVEGAME
, SLO_LOAD
); break;
344 case WID_SGI_PLAY_SCENARIO
: ShowSaveLoadDialog(FT_SCENARIO
, SLO_LOAD
); break;
345 case WID_SGI_PLAY_HEIGHTMAP
: ShowSaveLoadDialog(FT_HEIGHTMAP
,SLO_LOAD
); break;
346 case WID_SGI_EDIT_SCENARIO
: StartScenarioEditor(); break;
348 case WID_SGI_PLAY_NETWORK
:
349 if (!_network_available
) {
350 ShowErrorMessage(STR_NETWORK_ERROR_NOTAVAILABLE
, INVALID_STRING_ID
, WL_ERROR
);
352 ShowNetworkGameWindow();
356 case WID_SGI_TEMPERATE_LANDSCAPE
: case WID_SGI_ARCTIC_LANDSCAPE
:
357 case WID_SGI_TROPIC_LANDSCAPE
: case WID_SGI_TOYLAND_LANDSCAPE
:
358 SetNewLandscapeType(widget
- WID_SGI_TEMPERATE_LANDSCAPE
);
361 case WID_SGI_OPTIONS
: ShowGameOptions(); break;
362 case WID_SGI_HIGHSCORE
: ShowHighscoreTable(); break;
363 case WID_SGI_HELP
: ShowHelpWindow(); break;
364 case WID_SGI_SETTINGS_OPTIONS
:ShowGameSettings(); break;
365 case WID_SGI_GRF_SETTINGS
: ShowNewGRFSettings(true, true, false, &_grfconfig_newgame
); break;
366 case WID_SGI_CONTENT_DOWNLOAD
:
367 if (!_network_available
) {
368 ShowErrorMessage(STR_NETWORK_ERROR_NOTAVAILABLE
, INVALID_STRING_ID
, WL_ERROR
);
370 ShowNetworkContentListWindow();
373 case WID_SGI_AI_SETTINGS
: ShowAIConfigWindow(); break;
374 case WID_SGI_GS_SETTINGS
: ShowGSConfigWindow(); break;
375 case WID_SGI_EXIT
: HandleExitGameRequest(); break;
380 static constexpr NWidgetPart _nested_select_game_widgets
[] = {
381 NWidget(WWT_CAPTION
, COLOUR_BROWN
), SetDataTip(STR_INTRO_CAPTION
, STR_NULL
),
382 NWidget(WWT_PANEL
, COLOUR_BROWN
),
383 NWidget(NWID_VERTICAL
), SetPIP(0, WidgetDimensions::unscaled
.vsep_wide
, 0), SetPadding(WidgetDimensions::unscaled
.sparse
),
385 NWidget(NWID_VERTICAL
), SetPIP(0, WidgetDimensions::unscaled
.vsep_sparse
, 0),
386 /* 'New Game' and 'Load Game' buttons */
387 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
),
388 NWidget(WWT_PUSHTXTBTN
, COLOUR_ORANGE
, WID_SGI_GENERATE_GAME
), SetDataTip(STR_INTRO_NEW_GAME
, STR_INTRO_TOOLTIP_NEW_GAME
), SetFill(1, 0),
389 NWidget(WWT_PUSHTXTBTN
, COLOUR_ORANGE
, WID_SGI_LOAD_GAME
), SetDataTip(STR_INTRO_LOAD_GAME
, STR_INTRO_TOOLTIP_LOAD_GAME
), SetFill(1, 0),
392 /* 'Play Scenario' and 'Play Heightmap' buttons */
393 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
),
394 NWidget(WWT_PUSHTXTBTN
, COLOUR_ORANGE
, WID_SGI_PLAY_SCENARIO
), SetDataTip(STR_INTRO_PLAY_SCENARIO
, STR_INTRO_TOOLTIP_PLAY_SCENARIO
), SetFill(1, 0),
395 NWidget(WWT_PUSHTXTBTN
, COLOUR_ORANGE
, WID_SGI_PLAY_HEIGHTMAP
), SetDataTip(STR_INTRO_PLAY_HEIGHTMAP
, STR_INTRO_TOOLTIP_PLAY_HEIGHTMAP
), SetFill(1, 0),
398 /* 'Scenario Editor' and 'Multiplayer' buttons */
399 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
),
400 NWidget(WWT_PUSHTXTBTN
, COLOUR_ORANGE
, WID_SGI_EDIT_SCENARIO
), SetDataTip(STR_INTRO_SCENARIO_EDITOR
, STR_INTRO_TOOLTIP_SCENARIO_EDITOR
), SetFill(1, 0),
401 NWidget(WWT_PUSHTXTBTN
, COLOUR_ORANGE
, WID_SGI_PLAY_NETWORK
), SetDataTip(STR_INTRO_MULTIPLAYER
, STR_INTRO_TOOLTIP_MULTIPLAYER
), SetFill(1, 0),
405 /* Climate selection buttons */
406 NWidget(NWID_HORIZONTAL
), SetPIP(0, WidgetDimensions::unscaled
.hsep_wide
, 0), SetPIPRatio(1, 1, 1),
407 NWidget(WWT_IMGBTN_2
, COLOUR_ORANGE
, WID_SGI_TEMPERATE_LANDSCAPE
), SetDataTip(SPR_SELECT_TEMPERATE
, STR_INTRO_TOOLTIP_TEMPERATE
),
408 NWidget(WWT_IMGBTN_2
, COLOUR_ORANGE
, WID_SGI_ARCTIC_LANDSCAPE
), SetDataTip(SPR_SELECT_SUB_ARCTIC
, STR_INTRO_TOOLTIP_SUB_ARCTIC_LANDSCAPE
),
409 NWidget(WWT_IMGBTN_2
, COLOUR_ORANGE
, WID_SGI_TROPIC_LANDSCAPE
), SetDataTip(SPR_SELECT_SUB_TROPICAL
, STR_INTRO_TOOLTIP_SUB_TROPICAL_LANDSCAPE
),
410 NWidget(WWT_IMGBTN_2
, COLOUR_ORANGE
, WID_SGI_TOYLAND_LANDSCAPE
), SetDataTip(SPR_SELECT_TOYLAND
, STR_INTRO_TOOLTIP_TOYLAND_LANDSCAPE
),
413 NWidget(NWID_SELECTION
, INVALID_COLOUR
, WID_SGI_BASESET_SELECTION
),
414 NWidget(NWID_VERTICAL
),
415 NWidget(WWT_EMPTY
, COLOUR_ORANGE
, WID_SGI_BASESET
), SetFill(1, 0),
419 NWidget(NWID_SELECTION
, INVALID_COLOUR
, WID_SGI_TRANSLATION_SELECTION
),
420 NWidget(NWID_VERTICAL
),
421 NWidget(WWT_EMPTY
, COLOUR_ORANGE
, WID_SGI_TRANSLATION
), SetFill(1, 0),
425 NWidget(NWID_VERTICAL
), SetPIP(0, WidgetDimensions::unscaled
.vsep_sparse
, 0),
426 /* 'Game Options' and 'Settings' buttons */
427 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
),
428 NWidget(WWT_PUSHTXTBTN
, COLOUR_ORANGE
, WID_SGI_OPTIONS
), SetDataTip(STR_INTRO_GAME_OPTIONS
, STR_INTRO_TOOLTIP_GAME_OPTIONS
), SetFill(1, 0),
429 NWidget(WWT_PUSHTXTBTN
, COLOUR_ORANGE
, WID_SGI_SETTINGS_OPTIONS
), SetDataTip(STR_INTRO_CONFIG_SETTINGS_TREE
, STR_INTRO_TOOLTIP_CONFIG_SETTINGS_TREE
), SetFill(1, 0),
432 /* 'AI Settings' and 'Game Script Settings' buttons */
433 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
),
434 NWidget(WWT_PUSHTXTBTN
, COLOUR_ORANGE
, WID_SGI_AI_SETTINGS
), SetDataTip(STR_INTRO_AI_SETTINGS
, STR_INTRO_TOOLTIP_AI_SETTINGS
), SetFill(1, 0),
435 NWidget(WWT_PUSHTXTBTN
, COLOUR_ORANGE
, WID_SGI_GS_SETTINGS
), SetDataTip(STR_INTRO_GAMESCRIPT_SETTINGS
, STR_INTRO_TOOLTIP_GAMESCRIPT_SETTINGS
), SetFill(1, 0),
438 /* 'Check Online Content' and 'NewGRF Settings' buttons */
439 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
),
440 NWidget(WWT_PUSHTXTBTN
, COLOUR_ORANGE
, WID_SGI_CONTENT_DOWNLOAD
), SetDataTip(STR_INTRO_ONLINE_CONTENT
, STR_INTRO_TOOLTIP_ONLINE_CONTENT
), SetFill(1, 0),
441 NWidget(WWT_PUSHTXTBTN
, COLOUR_ORANGE
, WID_SGI_GRF_SETTINGS
), SetDataTip(STR_INTRO_NEWGRF_SETTINGS
, STR_INTRO_TOOLTIP_NEWGRF_SETTINGS
), SetFill(1, 0),
445 /* 'Help and Manuals' and 'Highscore Table' buttons */
446 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
),
447 NWidget(WWT_PUSHTXTBTN
, COLOUR_ORANGE
, WID_SGI_HELP
), SetDataTip(STR_INTRO_HELP
, STR_INTRO_TOOLTIP_HELP
), SetFill(1, 0),
448 NWidget(WWT_PUSHTXTBTN
, COLOUR_ORANGE
, WID_SGI_HIGHSCORE
), SetDataTip(STR_INTRO_HIGHSCORE
, STR_INTRO_TOOLTIP_HIGHSCORE
), SetFill(1, 0),
452 NWidget(NWID_HORIZONTAL
), SetPIPRatio(1, 0, 1),
453 NWidget(WWT_PUSHTXTBTN
, COLOUR_ORANGE
, WID_SGI_EXIT
), SetMinimalSize(128, 0), SetDataTip(STR_INTRO_QUIT
, STR_INTRO_TOOLTIP_QUIT
),
459 static WindowDesc
_select_game_desc(__FILE__
, __LINE__
,
460 WDP_CENTER
, nullptr, 0, 0,
461 WC_SELECT_GAME
, WC_NONE
,
463 std::begin(_nested_select_game_widgets
), std::end(_nested_select_game_widgets
)
466 void ShowSelectGameWindow()
468 new SelectGameWindow(&_select_game_desc
);
471 static void AskExitGameCallback(Window
*, bool confirmed
)
474 _survey
.Transmit(NetworkSurveyHandler::Reason::EXIT
, true);
483 STR_QUIT_ARE_YOU_SURE_YOU_WANT_TO_EXIT_OPENTTD
,
491 static void AskExitToGameMenuCallback(Window
*, bool confirmed
)
494 _switch_mode
= SM_MENU
;
495 ClearErrorMessages();
499 void AskExitToGameMenu()
502 STR_ABANDON_GAME_CAPTION
,
503 (_game_mode
!= GM_EDITOR
) ? STR_ABANDON_GAME_QUERY
: STR_ABANDON_SCENARIO_QUERY
,
505 AskExitToGameMenuCallback
,