Fix #10490: Allow ships to exit depots if another is not moving at the exit point...
[openttd-github.git] / src / bootstrap_gui.cpp
blob4a1b72d9c5e2d9a6697bef70f06c3f72a046e5b2
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 bootstrap_gui.cpp Barely used user interface for bootstrapping OpenTTD, i.e. downloading the required content. */
10 #include "stdafx.h"
11 #include "base_media_base.h"
12 #include "blitter/factory.hpp"
13 #include "error_func.h"
15 #if defined(WITH_FREETYPE) || defined(WITH_UNISCRIBE) || defined(WITH_COCOA)
17 #include "core/geometry_func.hpp"
18 #include "error.h"
19 #include "fontcache.h"
20 #include "gfx_func.h"
21 #include "network/network.h"
22 #include "network/network_content_gui.h"
23 #include "openttd.h"
24 #include "strings_func.h"
25 #include "video/video_driver.hpp"
26 #include "window_func.h"
28 #include "widgets/bootstrap_widget.h"
30 #include "table/strings.h"
32 #include "safeguards.h"
34 /** Widgets for the background window to prevent smearing. */
35 static constexpr NWidgetPart _background_widgets[] = {
36 NWidget(WWT_PANEL, COLOUR_DARK_BLUE, WID_BB_BACKGROUND), SetResize(1, 1),
37 EndContainer(),
40 /**
41 * Window description for the background window to prevent smearing.
43 static WindowDesc _background_desc(__FILE__, __LINE__,
44 WDP_MANUAL, nullptr, 0, 0,
45 WC_BOOTSTRAP, WC_NONE,
46 WDF_NO_CLOSE,
47 std::begin(_background_widgets), std::end(_background_widgets)
50 /** The background for the game. */
51 class BootstrapBackground : public Window {
52 public:
53 BootstrapBackground() : Window(&_background_desc)
55 this->InitNested(0);
56 CLRBITS(this->flags, WF_WHITE_BORDER);
57 ResizeWindow(this, _screen.width, _screen.height);
60 void DrawWidget(const Rect &r, WidgetID) const override
62 GfxFillRect(r.left, r.top, r.right, r.bottom, 4, FILLRECT_OPAQUE);
63 GfxFillRect(r.left, r.top, r.right, r.bottom, 0, FILLRECT_CHECKER);
67 /** Nested widgets for the error window. */
68 static constexpr NWidgetPart _nested_bootstrap_errmsg_widgets[] = {
69 NWidget(NWID_HORIZONTAL),
70 NWidget(WWT_CAPTION, COLOUR_GREY, WID_BEM_CAPTION), SetDataTip(STR_MISSING_GRAPHICS_ERROR_TITLE, STR_NULL),
71 EndContainer(),
72 NWidget(WWT_PANEL, COLOUR_GREY, WID_BEM_MESSAGE), EndContainer(),
73 NWidget(NWID_HORIZONTAL),
74 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BEM_QUIT), SetDataTip(STR_MISSING_GRAPHICS_ERROR_QUIT, STR_NULL), SetFill(1, 0),
75 EndContainer(),
78 /** Window description for the error window. */
79 static WindowDesc _bootstrap_errmsg_desc(__FILE__, __LINE__,
80 WDP_CENTER, nullptr, 0, 0,
81 WC_BOOTSTRAP, WC_NONE,
82 WDF_MODAL | WDF_NO_CLOSE,
83 std::begin(_nested_bootstrap_errmsg_widgets), std::end(_nested_bootstrap_errmsg_widgets)
86 /** The window for a failed bootstrap. */
87 class BootstrapErrorWindow : public Window {
88 public:
89 BootstrapErrorWindow() : Window(&_bootstrap_errmsg_desc)
91 this->InitNested(1);
94 void Close([[maybe_unused]] int data = 0) override
96 _exit_game = true;
97 this->Window::Close();
100 void UpdateWidgetSize(WidgetID widget, Dimension *size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension *fill, [[maybe_unused]] Dimension *resize) override
102 if (widget == WID_BEM_MESSAGE) {
103 *size = GetStringBoundingBox(STR_MISSING_GRAPHICS_ERROR);
104 size->width += WidgetDimensions::scaled.frametext.Horizontal();
105 size->height += WidgetDimensions::scaled.frametext.Vertical();
109 void DrawWidget(const Rect &r, WidgetID widget) const override
111 if (widget == WID_BEM_MESSAGE) {
112 DrawStringMultiLine(r.Shrink(WidgetDimensions::scaled.frametext), STR_MISSING_GRAPHICS_ERROR, TC_FROMSTRING, SA_CENTER);
116 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
118 if (widget == WID_BEM_QUIT) {
119 _exit_game = true;
124 /** Nested widgets for the download window. */
125 static constexpr NWidgetPart _nested_bootstrap_download_status_window_widgets[] = {
126 NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_CONTENT_DOWNLOAD_TITLE, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
127 NWidget(WWT_PANEL, COLOUR_GREY),
128 NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_wide, 0), SetPadding(WidgetDimensions::unscaled.modalpopup),
129 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_NCDS_PROGRESS_BAR), SetFill(1, 0),
130 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_NCDS_PROGRESS_TEXT), SetFill(1, 0), SetMinimalSize(350, 0),
131 EndContainer(),
132 EndContainer(),
135 /** Window description for the download window */
136 static WindowDesc _bootstrap_download_status_window_desc(__FILE__, __LINE__,
137 WDP_CENTER, nullptr, 0, 0,
138 WC_NETWORK_STATUS_WINDOW, WC_NONE,
139 WDF_MODAL | WDF_NO_CLOSE,
140 std::begin(_nested_bootstrap_download_status_window_widgets), std::end(_nested_bootstrap_download_status_window_widgets)
144 /** Window for showing the download status of content */
145 struct BootstrapContentDownloadStatusWindow : public BaseNetworkContentDownloadStatusWindow {
146 public:
147 /** Simple call the constructor of the superclass. */
148 BootstrapContentDownloadStatusWindow() : BaseNetworkContentDownloadStatusWindow(&_bootstrap_download_status_window_desc)
152 void Close([[maybe_unused]] int data = 0) override
154 /* If we are not set to exit the game, it means the bootstrap failed. */
155 if (!_exit_game) {
156 new BootstrapErrorWindow();
158 this->BaseNetworkContentDownloadStatusWindow::Close();
161 void OnDownloadComplete(ContentID) override
163 /* We have completed downloading. We can trigger finding the right set now. */
164 BaseGraphics::FindSets();
166 /* And continue going into the menu. */
167 _game_mode = GM_MENU;
169 /* _exit_game is used to break out of the outer video driver's MainLoop. */
170 _exit_game = true;
171 this->Close();
175 /** The widgets for the query. It has no close box as that sprite does not exist yet. */
176 static constexpr NWidgetPart _bootstrap_query_widgets[] = {
177 NWidget(NWID_HORIZONTAL),
178 NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_MISSING_GRAPHICS_SET_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
179 EndContainer(),
180 NWidget(WWT_PANEL, COLOUR_GREY, WID_BAFD_QUESTION), EndContainer(),
181 NWidget(NWID_HORIZONTAL),
182 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BAFD_YES), SetDataTip(STR_MISSING_GRAPHICS_YES_DOWNLOAD, STR_NULL),
183 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BAFD_NO), SetDataTip(STR_MISSING_GRAPHICS_NO_QUIT, STR_NULL),
184 EndContainer(),
187 /** The window description for the query. */
188 static WindowDesc _bootstrap_query_desc(__FILE__, __LINE__,
189 WDP_CENTER, nullptr, 0, 0,
190 WC_CONFIRM_POPUP_QUERY, WC_NONE,
191 WDF_NO_CLOSE,
192 std::begin(_bootstrap_query_widgets), std::end(_bootstrap_query_widgets)
195 /** The window for the query. It can't use the generic query window as that uses sprites that don't exist yet. */
196 class BootstrapAskForDownloadWindow : public Window, ContentCallback {
197 Dimension button_size; ///< The dimension of the button
199 public:
200 /** Start listening to the content client events. */
201 BootstrapAskForDownloadWindow() : Window(&_bootstrap_query_desc)
203 this->InitNested(WN_CONFIRM_POPUP_QUERY_BOOTSTRAP);
204 _network_content_client.AddCallback(this);
207 /** Stop listening to the content client events. */
208 void Close([[maybe_unused]] int data = 0) override
210 _network_content_client.RemoveCallback(this);
211 this->Window::Close();
214 void UpdateWidgetSize(WidgetID widget, Dimension *size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension *fill, [[maybe_unused]] Dimension *resize) override
216 /* We cache the button size. This is safe as no reinit can happen here. */
217 if (this->button_size.width == 0) {
218 this->button_size = maxdim(GetStringBoundingBox(STR_MISSING_GRAPHICS_YES_DOWNLOAD), GetStringBoundingBox(STR_MISSING_GRAPHICS_NO_QUIT));
219 this->button_size.width += WidgetDimensions::scaled.frametext.Horizontal();
220 this->button_size.height += WidgetDimensions::scaled.frametext.Vertical();
223 switch (widget) {
224 case WID_BAFD_QUESTION:
225 /* The question is twice as wide as the buttons, and determine the height based on the width. */
226 size->width = this->button_size.width * 2;
227 size->height = GetStringHeight(STR_MISSING_GRAPHICS_SET_MESSAGE, size->width - WidgetDimensions::scaled.frametext.Horizontal()) + WidgetDimensions::scaled.frametext.Vertical();
228 break;
230 case WID_BAFD_YES:
231 case WID_BAFD_NO:
232 *size = this->button_size;
233 break;
237 void DrawWidget(const Rect &r, WidgetID widget) const override
239 if (widget != WID_BAFD_QUESTION) return;
241 DrawStringMultiLine(r.Shrink(WidgetDimensions::scaled.frametext), STR_MISSING_GRAPHICS_SET_MESSAGE, TC_FROMSTRING, SA_CENTER);
244 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
246 switch (widget) {
247 case WID_BAFD_YES:
248 /* We got permission to connect! Yay! */
249 _network_content_client.Connect();
250 break;
252 case WID_BAFD_NO:
253 _exit_game = true;
254 break;
256 default:
257 break;
261 void OnConnect(bool success) override
263 if (!success) {
264 UserError("Failed to connect to content server. Please acquire a graphics set for OpenTTD. See section 1.4 of README.md.");
265 /* _exit_game is used to break out of the outer video driver's MainLoop. */
266 _exit_game = true;
267 this->Close();
268 return;
271 /* Once connected, request the metadata. */
272 _network_content_client.RequestContentList(CONTENT_TYPE_BASE_GRAPHICS);
275 void OnReceiveContentInfo(const ContentInfo *ci) override
277 /* And once the meta data is received, start downloading it. */
278 _network_content_client.Select(ci->id);
279 new BootstrapContentDownloadStatusWindow();
280 this->Close();
284 #endif /* defined(WITH_FREETYPE) */
286 #if defined(__EMSCRIPTEN__)
287 # include <emscripten.h>
288 # include "network/network.h"
289 # include "network/network_content.h"
290 # include "openttd.h"
291 # include "video/video_driver.hpp"
293 class BootstrapEmscripten : public ContentCallback {
294 bool downloading{false};
295 uint total_files{0};
296 uint total_bytes{0};
297 uint downloaded_bytes{0};
299 public:
300 BootstrapEmscripten()
302 _network_content_client.AddCallback(this);
303 _network_content_client.Connect();
306 ~BootstrapEmscripten()
308 _network_content_client.RemoveCallback(this);
311 void OnConnect(bool success) override
313 if (!success) {
314 EM_ASM({ if (window["openttd_bootstrap_failed"]) openttd_bootstrap_failed(); });
315 return;
318 /* Once connected, request the metadata. */
319 _network_content_client.RequestContentList(CONTENT_TYPE_BASE_GRAPHICS);
322 void OnReceiveContentInfo(const ContentInfo *ci) override
324 if (this->downloading) return;
326 /* And once the metadata is received, start downloading it. */
327 _network_content_client.Select(ci->id);
328 _network_content_client.DownloadSelectedContent(this->total_files, this->total_bytes);
329 this->downloading = true;
331 EM_ASM({ if (window["openttd_bootstrap"]) openttd_bootstrap($0, $1); }, this->downloaded_bytes, this->total_bytes);
334 void OnDownloadProgress(const ContentInfo *, int bytes) override
336 /* A negative value means we are resetting; for example, when retrying or using a fallback. */
337 if (bytes < 0) {
338 this->downloaded_bytes = 0;
339 } else {
340 this->downloaded_bytes += bytes;
343 EM_ASM({ if (window["openttd_bootstrap"]) openttd_bootstrap($0, $1); }, this->downloaded_bytes, this->total_bytes);
346 void OnDownloadComplete(ContentID) override
348 /* _exit_game is used to break out of the outer video driver's MainLoop. */
349 _exit_game = true;
351 delete this;
354 #endif /* __EMSCRIPTEN__ */
357 * Handle all procedures for bootstrapping OpenTTD without a base graphics set.
358 * This requires all kinds of trickery that is needed to avoid the use of
359 * sprites from the base graphics set which are pretty interwoven.
360 * @return True if a base set exists, otherwise false.
362 bool HandleBootstrap()
364 if (BaseGraphics::GetUsedSet() != nullptr) return true;
366 /* No user interface, bail out with an error. */
367 if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 0) goto failure;
369 /* If there is no network or no non-sprite font, then there is nothing we can do. Go straight to failure. */
370 #if defined(__EMSCRIPTEN__) || (defined(_WIN32) && defined(WITH_UNISCRIBE)) || (defined(WITH_FREETYPE) && (defined(WITH_FONTCONFIG) || defined(__APPLE__))) || defined(WITH_COCOA)
371 if (!_network_available) goto failure;
373 /* First tell the game we're bootstrapping. */
374 _game_mode = GM_BOOTSTRAP;
376 #if defined(__EMSCRIPTEN__)
377 new BootstrapEmscripten();
378 #else
379 /* Initialise the font cache. */
380 InitializeUnicodeGlyphMap();
381 /* Next "force" finding a suitable non-sprite font as the local font is missing. */
382 CheckForMissingGlyphs(false);
384 /* Initialise the palette. The biggest step is 'faking' some recolour sprites.
385 * This way the mauve and gray colours work and we can show the user interface. */
386 GfxInitPalettes();
387 static const int offsets[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x80, 0, 0, 0, 0x04, 0x08 };
388 for (uint i = 0; i != 16; i++) {
389 for (int j = 0; j < 8; j++) {
390 _colour_gradient[i][j] = offsets[i] + j;
394 /* Finally ask the question. */
395 new BootstrapBackground();
396 new BootstrapAskForDownloadWindow();
397 #endif /* __EMSCRIPTEN__ */
399 /* Process the user events. */
400 VideoDriver::GetInstance()->MainLoop();
402 /* _exit_game is used to get out of the video driver's main loop.
403 * In case GM_BOOTSTRAP is still set we did not exit it via the
404 * "download complete" event, so it was a manual exit. Obey it. */
405 _exit_game = _game_mode == GM_BOOTSTRAP;
406 if (_exit_game) return false;
408 /* Try to probe the graphics. Should work this time. */
409 if (!BaseGraphics::SetSet({})) goto failure;
411 /* Finally we can continue heading for the menu. */
412 _game_mode = GM_MENU;
413 return true;
414 #endif
416 /* Failure to get enough working to get a graphics set. */
417 failure:
418 UserError("Failed to find a graphics set. Please acquire a graphics set for OpenTTD. See section 1.4 of README.md.");
419 return false;