Fix #10490: Allow ships to exit depots if another is not moving at the exit point...
[openttd-github.git] / src / intro_gui.cpp
blob2ffe2d40976a08e3d40fe14b37be03797d524c29
1 /*
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/>.
6 */
8 /** @file intro_gui.cpp The main menu GUI. */
10 #include "stdafx.h"
11 #include "error.h"
12 #include "gui.h"
13 #include "window_gui.h"
14 #include "window_func.h"
15 #include "textbuf_gui.h"
16 #include "help_gui.h"
17 #include "network/network.h"
18 #include "genworld.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"
25 #include "fios.h"
26 #include "ai/ai_gui.hpp"
27 #include "game/game_gui.hpp"
28 #include "gfx_func.h"
29 #include "core/geometry_func.hpp"
30 #include "language.h"
31 #include "rev.h"
32 #include "highscore.h"
33 #include "signs_base.h"
34 #include "viewport_func.h"
35 #include "vehicle_base.h"
36 #include <regex>
38 #include "widgets/intro_widget.h"
40 #include "table/strings.h"
41 #include "table/sprites.h"
43 #include "safeguards.h"
46 /**
47 * A viewport command for the main menu background (intro game).
49 struct IntroGameViewportCommand {
50 /** Horizontal alignment value. */
51 enum AlignmentH : byte {
52 LEFT,
53 CENTRE,
54 RIGHT,
56 /** Vertical alignment value. */
57 enum AlignmentV : byte {
58 TOP,
59 MIDDLE,
60 BOTTOM,
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.
72 /**
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);
85 Point p;
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;
96 return p;
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()) {
127 std::smatch match;
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. */
138 enum IdType {
139 ID_NONE, ID_VEHICLE
140 } id_type = ID_NONE;
141 for (char c : match[2].str()) {
142 if (isdigit(c)) {
143 if (id_type == ID_VEHICLE) {
144 vc.vehicle = vc.vehicle * 10 + (c - '0');
146 } else {
147 id_type = ID_NONE;
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;
205 } else {
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;
216 } else {
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
287 switch (widget) {
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);
291 break;
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);
296 break;
300 void UpdateWidgetSize(WidgetID widget, Dimension *size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension *fill, [[maybe_unused]] Dimension *resize) override
302 switch (widget) {
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();
307 break;
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;
334 switch (widget) {
335 case WID_SGI_GENERATE_GAME:
336 if (_ctrl_pressed) {
337 StartNewGameWithoutGUI(GENERATE_NEW_SEED);
338 } else {
339 ShowGenerateLandscape();
341 break;
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);
351 } else {
352 ShowNetworkGameWindow();
354 break;
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);
359 break;
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);
369 } else {
370 ShowNetworkContentListWindow();
372 break;
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),
390 EndContainer(),
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),
396 EndContainer(),
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),
402 EndContainer(),
403 EndContainer(),
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),
411 EndContainer(),
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),
416 EndContainer(),
417 EndContainer(),
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),
422 EndContainer(),
423 EndContainer(),
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),
430 EndContainer(),
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),
436 EndContainer(),
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),
442 EndContainer(),
443 EndContainer(),
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),
449 EndContainer(),
451 /* 'Exit' button */
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),
454 EndContainer(),
455 EndContainer(),
456 EndContainer(),
459 static WindowDesc _select_game_desc(__FILE__, __LINE__,
460 WDP_CENTER, nullptr, 0, 0,
461 WC_SELECT_GAME, WC_NONE,
462 WDF_NO_CLOSE,
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)
473 if (confirmed) {
474 _survey.Transmit(NetworkSurveyHandler::Reason::EXIT, true);
475 _exit_game = true;
479 void AskExitGame()
481 ShowQuery(
482 STR_QUIT_CAPTION,
483 STR_QUIT_ARE_YOU_SURE_YOU_WANT_TO_EXIT_OPENTTD,
484 nullptr,
485 AskExitGameCallback,
486 true
491 static void AskExitToGameMenuCallback(Window *, bool confirmed)
493 if (confirmed) {
494 _switch_mode = SM_MENU;
495 ClearErrorMessages();
499 void AskExitToGameMenu()
501 ShowQuery(
502 STR_ABANDON_GAME_CAPTION,
503 (_game_mode != GM_EDITOR) ? STR_ABANDON_GAME_QUERY : STR_ABANDON_SCENARIO_QUERY,
504 nullptr,
505 AskExitToGameMenuCallback,
506 true