1 /*****************************************************************************
2  * Copyright (c) 2014-2020 OpenRCT2 developers
3  *
4  * For a complete list of all authors, please refer to contributors.md
5  * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6  *
7  * OpenRCT2 is licensed under the GNU General Public License version 3.
8  *****************************************************************************/
9 
10 #include <openrct2-ui/interface/Dropdown.h>
11 #include <openrct2-ui/interface/Widget.h>
12 #include <openrct2-ui/windows/Window.h>
13 #include <openrct2/Game.h>
14 #include <openrct2/actions/NetworkModifyGroupAction.h>
15 #include <openrct2/config/Config.h>
16 #include <openrct2/drawing/Drawing.h>
17 #include <openrct2/localisation/Localisation.h>
18 #include <openrct2/network/network.h>
19 #include <openrct2/sprites.h>
20 #include <openrct2/util/Util.h>
21 
22 // clang-format off
23 enum {
24     WINDOW_MULTIPLAYER_PAGE_INFORMATION,
25     WINDOW_MULTIPLAYER_PAGE_PLAYERS,
26     WINDOW_MULTIPLAYER_PAGE_GROUPS,
27     WINDOW_MULTIPLAYER_PAGE_OPTIONS
28 };
29 
30 enum WINDOW_MULTIPLAYER_WIDGET_IDX {
31     WIDX_BACKGROUND,
32     WIDX_TITLE,
33     WIDX_CLOSE,
34     WIDX_CONTENT_PANEL,
35     WIDX_TAB1,
36     WIDX_TAB2,
37     WIDX_TAB3,
38     WIDX_TAB4,
39 
40     WIDX_HEADER_PLAYER = 8,
41     WIDX_HEADER_GROUP,
42     WIDX_HEADER_LAST_ACTION,
43     WIDX_HEADER_PING,
44     WIDX_LIST,
45 
46     WIDX_DEFAULT_GROUP = 8,
47     WIDX_DEFAULT_GROUP_DROPDOWN,
48     WIDX_ADD_GROUP,
49     WIDX_REMOVE_GROUP,
50     WIDX_RENAME_GROUP,
51     WIDX_SELECTED_GROUP,
52     WIDX_SELECTED_GROUP_DROPDOWN,
53     WIDX_PERMISSIONS_LIST,
54 
55     WIDX_LOG_CHAT_CHECKBOX = 8,
56     WIDX_LOG_SERVER_ACTIONS_CHECKBOX,
57     WIDX_KNOWN_KEYS_ONLY_CHECKBOX,
58 };
59 
60 #define MAIN_MULTIPLAYER_WIDGETS \
61     MakeWidget({  0,  0}, {340, 240}, WindowWidgetType::Frame,    WindowColour::Primary                                        ), /* panel / background */ \
62     MakeWidget({  1,  1}, {338,  14}, WindowWidgetType::Caption,  WindowColour::Primary,  STR_NONE,    STR_WINDOW_TITLE_TIP    ), /* title bar */ \
63     MakeWidget({327,  2}, { 11,  12}, WindowWidgetType::CloseBox, WindowColour::Primary,  STR_CLOSE_X, STR_CLOSE_WINDOW_TIP    ), /* close x button */ \
64     MakeWidget({  0, 43}, {340, 197}, WindowWidgetType::Resize,   WindowColour::Secondary                                      ), /* content panel */ \
65     MakeTab   ({  3, 17},                                                                STR_SHOW_SERVER_INFO_TIP), /* tab */ \
66     MakeTab   ({ 34, 17},                                                                STR_PLAYERS_TIP         ), /* tab */ \
67     MakeTab   ({ 65, 17},                                                                STR_GROUPS_TIP          ), /* tab */ \
68     MakeTab   ({ 96, 17},                                                                STR_OPTIONS_TIP         )  /* tab */
69 
70 static rct_widget window_multiplayer_information_widgets[] = {
71     MAIN_MULTIPLAYER_WIDGETS,
72     WIDGETS_END,
73 };
74 
75 static rct_widget window_multiplayer_players_widgets[] = {
76     MAIN_MULTIPLAYER_WIDGETS,
77     MakeWidget({  3, 46}, {173,  15}, WindowWidgetType::TableHeader, WindowColour::Primary  , STR_PLAYER     ), // Player name
78     MakeWidget({176, 46}, { 83,  15}, WindowWidgetType::TableHeader, WindowColour::Primary  , STR_GROUP      ), // Player name
79     MakeWidget({259, 46}, {100,  15}, WindowWidgetType::TableHeader, WindowColour::Primary  , STR_LAST_ACTION), // Player name
80     MakeWidget({359, 46}, { 42,  15}, WindowWidgetType::TableHeader, WindowColour::Primary  , STR_PING       ), // Player name
81     MakeWidget({  3, 60}, {334, 177}, WindowWidgetType::Scroll,       WindowColour::Secondary, SCROLL_VERTICAL), // list
82     WIDGETS_END,
83 };
84 
85 static rct_widget window_multiplayer_groups_widgets[] = {
86     MAIN_MULTIPLAYER_WIDGETS,
87     MakeWidget({141, 46}, {175,  12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary                    ), // default group
88     MakeWidget({305, 47}, { 11,  10}, WindowWidgetType::Button,   WindowColour::Secondary, STR_DROPDOWN_GLYPH),
89     MakeWidget({ 11, 65}, { 92,  12}, WindowWidgetType::Button,   WindowColour::Secondary, STR_ADD_GROUP     ), // add group button
90     MakeWidget({113, 65}, { 92,  12}, WindowWidgetType::Button,   WindowColour::Secondary, STR_REMOVE_GROUP  ), // remove group button
91     MakeWidget({215, 65}, { 92,  12}, WindowWidgetType::Button,   WindowColour::Secondary, STR_RENAME_GROUP  ), // rename group button
92     MakeWidget({ 72, 80}, {175,  12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary                    ), // selected group
93     MakeWidget({236, 81}, { 11,  10}, WindowWidgetType::Button,   WindowColour::Secondary, STR_DROPDOWN_GLYPH),
94     MakeWidget({  3, 94}, {314, 207}, WindowWidgetType::Scroll,   WindowColour::Secondary, SCROLL_VERTICAL   ), // permissions list
95     WIDGETS_END,
96 };
97 
98 static rct_widget window_multiplayer_options_widgets[] = {
99     MAIN_MULTIPLAYER_WIDGETS,
100     MakeWidget({3, 50}, {295, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_LOG_CHAT,              STR_LOG_CHAT_TIP             ),
101     MakeWidget({3, 64}, {295, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_LOG_SERVER_ACTIONS,    STR_LOG_SERVER_ACTIONS_TIP   ),
102     MakeWidget({3, 78}, {295, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_ALLOW_KNOWN_KEYS_ONLY, STR_ALLOW_KNOWN_KEYS_ONLY_TIP),
103     WIDGETS_END,
104 };
105 
106 static rct_widget *window_multiplayer_page_widgets[] = {
107     window_multiplayer_information_widgets,
108     window_multiplayer_players_widgets,
109     window_multiplayer_groups_widgets,
110     window_multiplayer_options_widgets,
111 };
112 
113 static constexpr const uint64_t window_multiplayer_page_enabled_widgets[] = {
114     (1ULL << WIDX_CLOSE) | (1ULL << WIDX_TAB1) | (1ULL << WIDX_TAB2) | (1ULL << WIDX_TAB3) | (1ULL << WIDX_TAB4),
115     (1ULL << WIDX_CLOSE) | (1ULL << WIDX_TAB1) | (1ULL << WIDX_TAB2) | (1ULL << WIDX_TAB3) | (1ULL << WIDX_TAB4),
116     (1ULL << WIDX_CLOSE) | (1ULL << WIDX_TAB1) | (1ULL << WIDX_TAB2) | (1ULL << WIDX_TAB3) | (1ULL << WIDX_TAB4) | (1ULL << WIDX_DEFAULT_GROUP) | (1ULL << WIDX_DEFAULT_GROUP_DROPDOWN) | (1ULL << WIDX_ADD_GROUP) | (1ULL << WIDX_REMOVE_GROUP) | (1ULL << WIDX_RENAME_GROUP) | (1ULL << WIDX_SELECTED_GROUP) | (1ULL << WIDX_SELECTED_GROUP_DROPDOWN),
117     (1ULL << WIDX_CLOSE) | (1ULL << WIDX_TAB1) | (1ULL << WIDX_TAB2) | (1ULL << WIDX_TAB3) | (1ULL << WIDX_TAB4) | (1ULL << WIDX_LOG_CHAT_CHECKBOX) | (1ULL << WIDX_LOG_SERVER_ACTIONS_CHECKBOX) | (1ULL << WIDX_KNOWN_KEYS_ONLY_CHECKBOX),
118 };
119 
120 static constexpr rct_string_id WindowMultiplayerPageTitles[] = {
121     STR_MULTIPLAYER_INFORMATION_TITLE,
122     STR_MULTIPLAYER_PLAYERS_TITLE,
123     STR_MULTIPLAYER_GROUPS_TITLE,
124     STR_MULTIPLAYER_OPTIONS_TITLE,
125 };
126 
127 static uint8_t _selectedGroup = 0;
128 
129 static void window_multiplayer_information_mouseup(rct_window *w, rct_widgetindex widgetIndex);
130 static void window_multiplayer_information_resize(rct_window *w);
131 static void window_multiplayer_information_update(rct_window *w);
132 static void window_multiplayer_information_invalidate(rct_window *w);
133 static void window_multiplayer_information_paint(rct_window *w, rct_drawpixelinfo *dpi);
134 
135 static void window_multiplayer_players_mouseup(rct_window *w, rct_widgetindex widgetIndex);
136 static void window_multiplayer_players_resize(rct_window *w);
137 static void window_multiplayer_players_update(rct_window *w);
138 static void window_multiplayer_players_scrollgetsize(rct_window *w, int32_t scrollIndex, int32_t *width, int32_t *height);
139 static void window_multiplayer_players_scrollmousedown(rct_window *w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords);
140 static void window_multiplayer_players_scrollmouseover(rct_window *w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords);
141 static void window_multiplayer_players_invalidate(rct_window *w);
142 static void window_multiplayer_players_paint(rct_window *w, rct_drawpixelinfo *dpi);
143 static void window_multiplayer_players_scrollpaint(rct_window *w, rct_drawpixelinfo *dpi, int32_t scrollIndex);
144 
145 static void window_multiplayer_groups_mouseup(rct_window *w, rct_widgetindex widgetIndex);
146 static void window_multiplayer_groups_resize(rct_window *w);
147 static void window_multiplayer_groups_mousedown(rct_window *w, rct_widgetindex widgetIndex, rct_widget* widget);
148 static void window_multiplayer_groups_dropdown(rct_window *w, rct_widgetindex widgetIndex, int32_t dropdownIndex);
149 static void window_multiplayer_groups_update(rct_window *w);
150 static void window_multiplayer_groups_scrollgetsize(rct_window *w, int32_t scrollIndex, int32_t *width, int32_t *height);
151 static void window_multiplayer_groups_scrollmousedown(rct_window *w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords);
152 static void window_multiplayer_groups_scrollmouseover(rct_window *w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords);
153 static void window_multiplayer_groups_text_input(rct_window *w, rct_widgetindex widgetIndex, char *text);
154 static void window_multiplayer_groups_invalidate(rct_window *w);
155 static void window_multiplayer_groups_paint(rct_window *w, rct_drawpixelinfo *dpi);
156 static void window_multiplayer_groups_scrollpaint(rct_window *w, rct_drawpixelinfo *dpi, int32_t scrollIndex);
157 
158 static void window_multiplayer_options_mouseup(rct_window *w, rct_widgetindex widgetIndex);
159 static void window_multiplayer_options_resize(rct_window *w);
160 static void window_multiplayer_options_update(rct_window *w);
161 static void window_multiplayer_options_invalidate(rct_window *w);
162 static void window_multiplayer_options_paint(rct_window *w, rct_drawpixelinfo *dpi);
163 
164 static rct_window_event_list window_multiplayer_information_events([](auto& events)
__anon834249df0202(auto& events) 165 {
166     events.mouse_up = &window_multiplayer_information_mouseup;
167     events.resize = &window_multiplayer_information_resize;
168     events.update = &window_multiplayer_information_update;
169     events.invalidate = &window_multiplayer_information_invalidate;
170     events.paint = &window_multiplayer_information_paint;
171 });
172 
173 static rct_window_event_list window_multiplayer_players_events([](auto& events)
__anon834249df0302(auto& events) 174 {
175     events.mouse_up = &window_multiplayer_players_mouseup;
176     events.resize = &window_multiplayer_players_resize;
177     events.update = &window_multiplayer_players_update;
178     events.get_scroll_size = &window_multiplayer_players_scrollgetsize;
179     events.scroll_mousedown = &window_multiplayer_players_scrollmousedown;
180     events.scroll_mouseover = &window_multiplayer_players_scrollmouseover;
181     events.invalidate = &window_multiplayer_players_invalidate;
182     events.paint = &window_multiplayer_players_paint;
183     events.scroll_paint = &window_multiplayer_players_scrollpaint;
184 });
185 
186 static rct_window_event_list window_multiplayer_groups_events([](auto& events)
__anon834249df0402(auto& events) 187 {
188     events.mouse_up = &window_multiplayer_groups_mouseup;
189     events.resize = &window_multiplayer_groups_resize;
190     events.mouse_down = &window_multiplayer_groups_mousedown;
191     events.dropdown = &window_multiplayer_groups_dropdown;
192     events.update = &window_multiplayer_groups_update;
193     events.get_scroll_size = &window_multiplayer_groups_scrollgetsize;
194     events.scroll_mousedown = &window_multiplayer_groups_scrollmousedown;
195     events.scroll_mouseover = &window_multiplayer_groups_scrollmouseover;
196     events.text_input = &window_multiplayer_groups_text_input;
197     events.invalidate = &window_multiplayer_groups_invalidate;
198     events.paint = &window_multiplayer_groups_paint;
199     events.scroll_paint = &window_multiplayer_groups_scrollpaint;
200 });
201 
202 static rct_window_event_list window_multiplayer_options_events([](auto& events)
__anon834249df0502(auto& events) 203 {
204     events.mouse_up = &window_multiplayer_options_mouseup;
205     events.resize = &window_multiplayer_options_resize;
206     events.update = &window_multiplayer_options_update;
207     events.invalidate = &window_multiplayer_options_invalidate;
208     events.paint = &window_multiplayer_options_paint;
209 });
210 
211 static rct_window_event_list *window_multiplayer_page_events[] = {
212     &window_multiplayer_information_events,
213     &window_multiplayer_players_events,
214     &window_multiplayer_groups_events,
215     &window_multiplayer_options_events,
216 };
217 // clang-format on
218 
219 static constexpr const int32_t window_multiplayer_animation_divisor[] = {
220     4,
221     4,
222     2,
223     2,
224 };
225 static constexpr const int32_t window_multiplayer_animation_frames[] = {
226     8,
227     8,
228     7,
229     4,
230 };
231 
232 static void window_multiplayer_draw_tab_images(rct_window* w, rct_drawpixelinfo* dpi);
233 static void window_multiplayer_set_page(rct_window* w, int32_t page);
234 
235 static bool _windowInformationSizeDirty;
236 static ScreenCoordsXY _windowInformationSize;
237 
window_multiplayer_open()238 rct_window* window_multiplayer_open()
239 {
240     // Check if window is already open
241     rct_window* window = window_bring_to_front_by_class(WC_MULTIPLAYER);
242     if (window == nullptr)
243     {
244         window = WindowCreateAutoPos(320, 144, &window_multiplayer_players_events, WC_MULTIPLAYER, WF_10 | WF_RESIZABLE);
245         window_multiplayer_set_page(window, WINDOW_MULTIPLAYER_PAGE_INFORMATION);
246     }
247 
248     return window;
249 }
250 
window_multiplayer_set_page(rct_window * w,int32_t page)251 static void window_multiplayer_set_page(rct_window* w, int32_t page)
252 {
253     _windowInformationSizeDirty = true;
254 
255     w->page = page;
256     w->frame_no = 0;
257     w->no_list_items = 0;
258     w->selected_list_item = -1;
259 
260     w->enabled_widgets = window_multiplayer_page_enabled_widgets[page];
261     w->hold_down_widgets = 0;
262     w->event_handlers = window_multiplayer_page_events[page];
263     w->pressed_widgets = 0;
264     w->widgets = window_multiplayer_page_widgets[page];
265     w->widgets[WIDX_TITLE].text = WindowMultiplayerPageTitles[page];
266 
267     window_event_resize_call(w);
268     window_event_invalidate_call(w);
269     WindowInitScrollWidgets(w);
270     w->Invalidate();
271 }
272 
window_multiplayer_anchor_border_widgets(rct_window * w)273 static void window_multiplayer_anchor_border_widgets(rct_window* w)
274 {
275     w->widgets[WIDX_BACKGROUND].right = w->width - 1;
276     w->widgets[WIDX_BACKGROUND].bottom = w->height - 1;
277     w->widgets[WIDX_TITLE].right = w->width - 2;
278     w->widgets[WIDX_CONTENT_PANEL].right = w->width - 1;
279     w->widgets[WIDX_CONTENT_PANEL].bottom = w->height - 1;
280     w->widgets[WIDX_CLOSE].left = w->width - 13;
281     w->widgets[WIDX_CLOSE].right = w->width - 3;
282 }
283 
window_multiplayer_set_pressed_tab(rct_window * w)284 static void window_multiplayer_set_pressed_tab(rct_window* w)
285 {
286     for (int32_t i = 0; i < 2; i++)
287     {
288         w->pressed_widgets &= ~(1 << (WIDX_TAB1 + i));
289     }
290     w->pressed_widgets |= 1LL << (WIDX_TAB1 + w->page);
291 }
292 
window_multiplayer_groups_show_group_dropdown(rct_window * w,rct_widget * widget)293 static void window_multiplayer_groups_show_group_dropdown(rct_window* w, rct_widget* widget)
294 {
295     rct_widget* dropdownWidget;
296     int32_t numItems, i;
297 
298     dropdownWidget = widget - 1;
299 
300     numItems = network_get_num_groups();
301 
302     WindowDropdownShowTextCustomWidth(
303         { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
304         w->colours[1], 0, 0, numItems, widget->right - dropdownWidget->left);
305 
306     for (i = 0; i < network_get_num_groups(); i++)
307     {
308         gDropdownItemsFormat[i] = STR_OPTIONS_DROPDOWN_ITEM;
309         gDropdownItemsArgs[i] = reinterpret_cast<uintptr_t>(network_get_group_name(i));
310     }
311     if (widget == &window_multiplayer_groups_widgets[WIDX_DEFAULT_GROUP_DROPDOWN])
312     {
313         Dropdown::SetChecked(network_get_group_index(network_get_default_group()), true);
314     }
315     else if (widget == &window_multiplayer_groups_widgets[WIDX_SELECTED_GROUP_DROPDOWN])
316     {
317         Dropdown::SetChecked(network_get_group_index(_selectedGroup), true);
318     }
319 }
320 
321 #pragma region Information page
322 
window_multiplayer_information_mouseup(rct_window * w,rct_widgetindex widgetIndex)323 static void window_multiplayer_information_mouseup(rct_window* w, rct_widgetindex widgetIndex)
324 {
325     switch (widgetIndex)
326     {
327         case WIDX_CLOSE:
328             window_close(w);
329             break;
330         case WIDX_TAB1:
331         case WIDX_TAB2:
332         case WIDX_TAB3:
333         case WIDX_TAB4:
334             if (w->page != widgetIndex - WIDX_TAB1)
335             {
336                 window_multiplayer_set_page(w, widgetIndex - WIDX_TAB1);
337             }
338             break;
339     }
340 }
341 
window_multiplayer_information_get_size()342 static ScreenCoordsXY window_multiplayer_information_get_size()
343 {
344     if (!_windowInformationSizeDirty)
345     {
346         return _windowInformationSize;
347     }
348 
349     int32_t lineHeight = font_get_line_height(FontSpriteBase::MEDIUM);
350 
351     // Base dimensions.
352     const int32_t width = 450;
353     int32_t height = 55;
354     int32_t numLines;
355 
356     // Server name is displayed word-wrapped, so figure out how high it will be.
357     {
358         utf8* buffer = _strdup(network_get_server_name());
359         gfx_wrap_string(buffer, width, FontSpriteBase::MEDIUM, &numLines);
360         free(buffer);
361         height += ++numLines * lineHeight + (LIST_ROW_HEIGHT / 2);
362     }
363 
364     // Likewise, for the optional server description -- which can be a little longer.
365     const utf8* descString = network_get_server_description();
366     if (!str_is_null_or_empty(descString))
367     {
368         utf8* buffer = _strdup(descString);
369         gfx_wrap_string(buffer, width, FontSpriteBase::MEDIUM, &numLines);
370         free(buffer);
371         height += ++numLines * lineHeight + (LIST_ROW_HEIGHT / 2);
372     }
373 
374     // Finally, account for provider info, if present.
375     {
376         const utf8* providerName = network_get_server_provider_name();
377         if (!str_is_null_or_empty(providerName))
378             height += LIST_ROW_HEIGHT;
379 
380         const utf8* providerEmail = network_get_server_provider_email();
381         if (!str_is_null_or_empty(providerEmail))
382             height += LIST_ROW_HEIGHT;
383 
384         const utf8* providerWebsite = network_get_server_provider_website();
385         if (!str_is_null_or_empty(providerWebsite))
386             height += LIST_ROW_HEIGHT;
387     }
388 
389     _windowInformationSizeDirty = false;
390     _windowInformationSize = { static_cast<int16_t>(width), static_cast<int16_t>(height) };
391     return _windowInformationSize;
392 }
393 
window_multiplayer_information_resize(rct_window * w)394 static void window_multiplayer_information_resize(rct_window* w)
395 {
396     auto size = window_multiplayer_information_get_size();
397     window_set_resize(w, size.x, size.y, size.x, size.y);
398 }
399 
window_multiplayer_information_update(rct_window * w)400 static void window_multiplayer_information_update(rct_window* w)
401 {
402     w->frame_no++;
403     widget_invalidate(w, WIDX_TAB1 + w->page);
404 }
405 
window_multiplayer_information_invalidate(rct_window * w)406 static void window_multiplayer_information_invalidate(rct_window* w)
407 {
408     window_multiplayer_set_pressed_tab(w);
409     window_multiplayer_anchor_border_widgets(w);
410     window_align_tabs(w, WIDX_TAB1, WIDX_TAB4);
411 }
412 
window_multiplayer_information_paint(rct_window * w,rct_drawpixelinfo * dpi)413 static void window_multiplayer_information_paint(rct_window* w, rct_drawpixelinfo* dpi)
414 {
415     WindowDrawWidgets(w, dpi);
416     window_multiplayer_draw_tab_images(w, dpi);
417 
418     rct_drawpixelinfo clippedDPI;
419     if (clip_drawpixelinfo(&clippedDPI, dpi, w->windowPos, w->width, w->height))
420     {
421         dpi = &clippedDPI;
422 
423         auto screenCoords = ScreenCoordsXY{ 3, 50 };
424         int32_t width = w->width - 6;
425 
426         const utf8* name = network_get_server_name();
427         {
428             auto ft = Formatter();
429             ft.Add<const char*>(name);
430             screenCoords.y += DrawTextWrapped(dpi, screenCoords, width, STR_STRING, ft, { w->colours[1] });
431             screenCoords.y += LIST_ROW_HEIGHT / 2;
432         }
433 
434         const utf8* description = network_get_server_description();
435         if (!str_is_null_or_empty(description))
436         {
437             auto ft = Formatter();
438             ft.Add<const char*>(description);
439             screenCoords.y += DrawTextWrapped(dpi, screenCoords, width, STR_STRING, ft, { w->colours[1] });
440             screenCoords.y += LIST_ROW_HEIGHT / 2;
441         }
442 
443         const utf8* providerName = network_get_server_provider_name();
444         if (!str_is_null_or_empty(providerName))
445         {
446             auto ft = Formatter();
447             ft.Add<const char*>(providerName);
448             DrawTextBasic(dpi, screenCoords, STR_PROVIDER_NAME, ft);
449             screenCoords.y += LIST_ROW_HEIGHT;
450         }
451 
452         const utf8* providerEmail = network_get_server_provider_email();
453         if (!str_is_null_or_empty(providerEmail))
454         {
455             auto ft = Formatter();
456             ft.Add<const char*>(providerEmail);
457             DrawTextBasic(dpi, screenCoords, STR_PROVIDER_EMAIL, ft);
458             screenCoords.y += LIST_ROW_HEIGHT;
459         }
460 
461         const utf8* providerWebsite = network_get_server_provider_website();
462         if (!str_is_null_or_empty(providerWebsite))
463         {
464             auto ft = Formatter();
465             ft.Add<const char*>(providerWebsite);
466             DrawTextBasic(dpi, screenCoords, STR_PROVIDER_WEBSITE, ft);
467         }
468     }
469 }
470 
471 #pragma endregion
472 
473 #pragma region Players page
474 
window_multiplayer_players_mouseup(rct_window * w,rct_widgetindex widgetIndex)475 static void window_multiplayer_players_mouseup(rct_window* w, rct_widgetindex widgetIndex)
476 {
477     switch (widgetIndex)
478     {
479         case WIDX_CLOSE:
480             window_close(w);
481             break;
482         case WIDX_TAB1:
483         case WIDX_TAB2:
484         case WIDX_TAB3:
485         case WIDX_TAB4:
486             if (w->page != widgetIndex - WIDX_TAB1)
487             {
488                 window_multiplayer_set_page(w, widgetIndex - WIDX_TAB1);
489             }
490             break;
491     }
492 }
493 
window_multiplayer_players_resize(rct_window * w)494 static void window_multiplayer_players_resize(rct_window* w)
495 {
496     window_set_resize(w, 420, 124, 500, 450);
497 
498     w->no_list_items = network_get_num_players();
499     w->list_item_positions[0] = 0;
500 
501     w->widgets[WIDX_HEADER_PING].right = w->width - 5;
502 
503     w->selected_list_item = -1;
504     w->Invalidate();
505 }
506 
window_multiplayer_players_update(rct_window * w)507 static void window_multiplayer_players_update(rct_window* w)
508 {
509     w->frame_no++;
510     widget_invalidate(w, WIDX_TAB1 + w->page);
511 }
512 
window_multiplayer_players_scrollgetsize(rct_window * w,int32_t scrollIndex,int32_t * width,int32_t * height)513 static void window_multiplayer_players_scrollgetsize(rct_window* w, int32_t scrollIndex, int32_t* width, int32_t* height)
514 {
515     int32_t i;
516 
517     if (w->selected_list_item != -1)
518     {
519         w->selected_list_item = -1;
520         w->Invalidate();
521     }
522 
523     *height = network_get_num_players() * SCROLLABLE_ROW_HEIGHT;
524     i = *height - window_multiplayer_players_widgets[WIDX_LIST].bottom + window_multiplayer_players_widgets[WIDX_LIST].top + 21;
525     if (i < 0)
526         i = 0;
527     if (i < w->scrolls[0].v_top)
528     {
529         w->scrolls[0].v_top = i;
530         w->Invalidate();
531     }
532 }
533 
window_multiplayer_players_scrollmousedown(rct_window * w,int32_t scrollIndex,const ScreenCoordsXY & screenCoords)534 static void window_multiplayer_players_scrollmousedown(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
535 {
536     int32_t index;
537 
538     index = screenCoords.y / SCROLLABLE_ROW_HEIGHT;
539     if (index >= w->no_list_items)
540         return;
541 
542     w->selected_list_item = index;
543     w->Invalidate();
544 
545     window_player_open(network_get_player_id(index));
546 }
547 
window_multiplayer_players_scrollmouseover(rct_window * w,int32_t scrollIndex,const ScreenCoordsXY & screenCoords)548 static void window_multiplayer_players_scrollmouseover(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
549 {
550     int32_t index;
551 
552     index = screenCoords.y / SCROLLABLE_ROW_HEIGHT;
553     if (index >= w->no_list_items)
554         return;
555 
556     w->selected_list_item = index;
557     w->Invalidate();
558 }
559 
window_multiplayer_players_invalidate(rct_window * w)560 static void window_multiplayer_players_invalidate(rct_window* w)
561 {
562     window_multiplayer_set_pressed_tab(w);
563     window_multiplayer_anchor_border_widgets(w);
564     window_multiplayer_players_widgets[WIDX_LIST].right = w->width - 4;
565     window_multiplayer_players_widgets[WIDX_LIST].bottom = w->height - 0x0F;
566     window_align_tabs(w, WIDX_TAB1, WIDX_TAB4);
567 }
568 
window_multiplayer_players_paint(rct_window * w,rct_drawpixelinfo * dpi)569 static void window_multiplayer_players_paint(rct_window* w, rct_drawpixelinfo* dpi)
570 {
571     rct_string_id stringId;
572 
573     WindowDrawWidgets(w, dpi);
574     window_multiplayer_draw_tab_images(w, dpi);
575 
576     // Number of players
577     stringId = w->no_list_items == 1 ? STR_MULTIPLAYER_PLAYER_COUNT : STR_MULTIPLAYER_PLAYER_COUNT_PLURAL;
578     auto screenCoords = w->windowPos + ScreenCoordsXY{ 4, w->widgets[WIDX_LIST].bottom + 2 };
579     auto ft = Formatter();
580     ft.Add<uint16_t>(w->no_list_items);
581     DrawTextBasic(dpi, screenCoords, stringId, ft, { w->colours[2] });
582 }
583 
window_multiplayer_players_scrollpaint(rct_window * w,rct_drawpixelinfo * dpi,int32_t scrollIndex)584 static void window_multiplayer_players_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex)
585 {
586     ScreenCoordsXY screenCoords;
587     screenCoords.y = 0;
588     for (int32_t i = 0; i < network_get_num_players(); i++)
589     {
590         if (screenCoords.y > dpi->y + dpi->height)
591         {
592             break;
593         }
594 
595         if (screenCoords.y + SCROLLABLE_ROW_HEIGHT + 1 >= dpi->y)
596         {
597             thread_local std::string buffer;
598             buffer.reserve(512);
599             buffer.clear();
600 
601             // Draw player name
602             colour_t colour = COLOUR_BLACK;
603             if (i == w->selected_list_item)
604             {
605                 gfx_filter_rect(
606                     dpi, { 0, screenCoords.y, 800, screenCoords.y + SCROLLABLE_ROW_HEIGHT - 1 },
607                     FilterPaletteID::PaletteDarken1);
608                 buffer += network_get_player_name(i);
609                 colour = w->colours[2];
610             }
611             else
612             {
613                 if (network_get_player_flags(i) & NETWORK_PLAYER_FLAG_ISSERVER)
614                 {
615                     buffer += "{BABYBLUE}";
616                 }
617                 else
618                 {
619                     buffer += "{BLACK}";
620                 }
621                 buffer += network_get_player_name(i);
622             }
623             screenCoords.x = 0;
624             gfx_clip_string(buffer.data(), 230, FontSpriteBase::MEDIUM);
625             gfx_draw_string(dpi, screenCoords, buffer.c_str(), { colour });
626 
627             // Draw group name
628             buffer.resize(0);
629             int32_t group = network_get_group_index(network_get_player_group(i));
630             if (group != -1)
631             {
632                 buffer += "{BLACK}";
633                 screenCoords.x = 173;
634                 buffer += network_get_group_name(group);
635                 gfx_clip_string(buffer.data(), 80, FontSpriteBase::MEDIUM);
636                 gfx_draw_string(dpi, screenCoords, buffer.c_str(), { colour });
637             }
638 
639             // Draw last action
640             int32_t action = network_get_player_last_action(i, 2000);
641             auto ft = Formatter();
642             if (action != -999)
643             {
644                 ft.Add<rct_string_id>(network_get_action_name_string_id(action));
645             }
646             else
647             {
648                 ft.Add<rct_string_id>(STR_ACTION_NA);
649             }
650             DrawTextEllipsised(dpi, { 256, screenCoords.y }, 100, STR_BLACK_STRING, ft);
651 
652             // Draw ping
653             buffer.resize(0);
654             int32_t ping = network_get_player_ping(i);
655             if (ping <= 100)
656             {
657                 buffer += "{GREEN}";
658             }
659             else if (ping <= 250)
660             {
661                 buffer += "{YELLOW}";
662             }
663             else
664             {
665                 buffer += "{RED}";
666             }
667 
668             char pingBuffer[64]{};
669             snprintf(pingBuffer, sizeof(pingBuffer), "%d ms", ping);
670             buffer += pingBuffer;
671 
672             screenCoords.x = 356;
673             gfx_draw_string(dpi, screenCoords, buffer.c_str(), { colour });
674         }
675         screenCoords.y += SCROLLABLE_ROW_HEIGHT;
676     }
677 }
678 
679 #pragma endregion
680 
681 #pragma region Groups page
682 
window_multiplayer_groups_mouseup(rct_window * w,rct_widgetindex widgetIndex)683 static void window_multiplayer_groups_mouseup(rct_window* w, rct_widgetindex widgetIndex)
684 {
685     switch (widgetIndex)
686     {
687         case WIDX_CLOSE:
688             window_close(w);
689             break;
690         case WIDX_TAB1:
691         case WIDX_TAB2:
692         case WIDX_TAB3:
693         case WIDX_TAB4:
694             if (w->page != widgetIndex - WIDX_TAB1)
695             {
696                 window_multiplayer_set_page(w, widgetIndex - WIDX_TAB1);
697             }
698             break;
699         case WIDX_ADD_GROUP:
700         {
701             auto networkModifyGroup = NetworkModifyGroupAction(ModifyGroupType::AddGroup);
702             GameActions::Execute(&networkModifyGroup);
703         }
704         break;
705         case WIDX_REMOVE_GROUP:
706         {
707             auto networkModifyGroup = NetworkModifyGroupAction(ModifyGroupType::RemoveGroup, _selectedGroup);
708             GameActions::Execute(&networkModifyGroup);
709         }
710         break;
711         case WIDX_RENAME_GROUP:;
712             int32_t groupIndex = network_get_group_index(_selectedGroup);
713             const utf8* groupName = network_get_group_name(groupIndex);
714             window_text_input_raw_open(w, widgetIndex, STR_GROUP_NAME, STR_ENTER_NEW_NAME_FOR_THIS_GROUP, {}, groupName, 32);
715             break;
716     }
717 }
718 
window_multiplayer_groups_resize(rct_window * w)719 static void window_multiplayer_groups_resize(rct_window* w)
720 {
721     window_set_resize(w, 320, 200, 320, 500);
722 
723     w->no_list_items = network_get_num_actions();
724     w->list_item_positions[0] = 0;
725 
726     w->selected_list_item = -1;
727     w->Invalidate();
728 }
729 
window_multiplayer_groups_mousedown(rct_window * w,rct_widgetindex widgetIndex,rct_widget * widget)730 static void window_multiplayer_groups_mousedown(rct_window* w, rct_widgetindex widgetIndex, rct_widget* widget)
731 {
732     switch (widgetIndex)
733     {
734         case WIDX_DEFAULT_GROUP_DROPDOWN:
735             window_multiplayer_groups_show_group_dropdown(w, widget);
736             break;
737         case WIDX_SELECTED_GROUP_DROPDOWN:
738             window_multiplayer_groups_show_group_dropdown(w, widget);
739             break;
740     }
741 }
742 
window_multiplayer_groups_dropdown(rct_window * w,rct_widgetindex widgetIndex,int32_t dropdownIndex)743 static void window_multiplayer_groups_dropdown(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex)
744 {
745     if (dropdownIndex == -1)
746     {
747         return;
748     }
749 
750     switch (widgetIndex)
751     {
752         case WIDX_DEFAULT_GROUP_DROPDOWN:
753         {
754             auto networkModifyGroup = NetworkModifyGroupAction(
755                 ModifyGroupType::SetDefault, network_get_group_id(dropdownIndex));
756             GameActions::Execute(&networkModifyGroup);
757         }
758         break;
759         case WIDX_SELECTED_GROUP_DROPDOWN:
760             _selectedGroup = network_get_group_id(dropdownIndex);
761             break;
762     }
763 
764     w->Invalidate();
765 }
766 
window_multiplayer_groups_update(rct_window * w)767 static void window_multiplayer_groups_update(rct_window* w)
768 {
769     w->frame_no++;
770     widget_invalidate(w, WIDX_TAB1 + w->page);
771 }
772 
window_multiplayer_groups_scrollgetsize(rct_window * w,int32_t scrollIndex,int32_t * width,int32_t * height)773 static void window_multiplayer_groups_scrollgetsize(rct_window* w, int32_t scrollIndex, int32_t* width, int32_t* height)
774 {
775     int32_t i;
776 
777     if (w->selected_list_item != -1)
778     {
779         w->selected_list_item = -1;
780         w->Invalidate();
781     }
782 
783     *height = network_get_num_actions() * SCROLLABLE_ROW_HEIGHT;
784     i = *height - window_multiplayer_groups_widgets[WIDX_LIST].bottom + window_multiplayer_groups_widgets[WIDX_LIST].top + 21;
785     if (i < 0)
786         i = 0;
787     if (i < w->scrolls[0].v_top)
788     {
789         w->scrolls[0].v_top = i;
790         w->Invalidate();
791     }
792 }
793 
window_multiplayer_groups_scrollmousedown(rct_window * w,int32_t scrollIndex,const ScreenCoordsXY & screenCoords)794 static void window_multiplayer_groups_scrollmousedown(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
795 {
796     int32_t index;
797 
798     index = screenCoords.y / SCROLLABLE_ROW_HEIGHT;
799     if (index >= w->no_list_items)
800         return;
801 
802     w->selected_list_item = index;
803     w->Invalidate();
804 
805     auto networkModifyGroup = NetworkModifyGroupAction(
806         ModifyGroupType::SetPermissions, _selectedGroup, "", index, PermissionState::Toggle);
807     GameActions::Execute(&networkModifyGroup);
808 }
809 
window_multiplayer_groups_scrollmouseover(rct_window * w,int32_t scrollIndex,const ScreenCoordsXY & screenCoords)810 static void window_multiplayer_groups_scrollmouseover(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
811 {
812     int32_t index;
813 
814     index = screenCoords.y / SCROLLABLE_ROW_HEIGHT;
815     if (index >= w->no_list_items)
816         return;
817 
818     w->selected_list_item = index;
819     w->Invalidate();
820 }
821 
window_multiplayer_groups_text_input(rct_window * w,rct_widgetindex widgetIndex,char * text)822 static void window_multiplayer_groups_text_input(rct_window* w, rct_widgetindex widgetIndex, char* text)
823 {
824     if (widgetIndex != WIDX_RENAME_GROUP)
825         return;
826 
827     if (text == nullptr)
828         return;
829 
830     auto networkModifyGroup = NetworkModifyGroupAction(ModifyGroupType::SetName, _selectedGroup, text);
831     GameActions::Execute(&networkModifyGroup);
832 }
833 
window_multiplayer_groups_invalidate(rct_window * w)834 static void window_multiplayer_groups_invalidate(rct_window* w)
835 {
836     window_multiplayer_set_pressed_tab(w);
837     window_multiplayer_anchor_border_widgets(w);
838     window_multiplayer_groups_widgets[WIDX_PERMISSIONS_LIST].right = w->width - 4;
839     window_multiplayer_groups_widgets[WIDX_PERMISSIONS_LIST].bottom = w->height - 0x0F;
840     window_align_tabs(w, WIDX_TAB1, WIDX_TAB4);
841 
842     // select other group if one is removed
843     while (network_get_group_index(_selectedGroup) == -1 && _selectedGroup > 0)
844     {
845         _selectedGroup--;
846     }
847 }
848 
window_multiplayer_groups_paint(rct_window * w,rct_drawpixelinfo * dpi)849 static void window_multiplayer_groups_paint(rct_window* w, rct_drawpixelinfo* dpi)
850 {
851     thread_local std::string buffer;
852 
853     WindowDrawWidgets(w, dpi);
854     window_multiplayer_draw_tab_images(w, dpi);
855 
856     rct_widget* widget = &window_multiplayer_groups_widgets[WIDX_DEFAULT_GROUP];
857     int32_t group = network_get_group_index(network_get_default_group());
858     if (group != -1)
859     {
860         buffer.assign("{WINDOW_COLOUR_2}");
861         buffer += network_get_group_name(group);
862 
863         auto ft = Formatter();
864         ft.Add<const char*>(buffer.c_str());
865         DrawTextEllipsised(
866             dpi, w->windowPos + ScreenCoordsXY{ widget->midX() - 5, widget->top }, widget->width() - 8, STR_STRING, ft,
867             { TextAlignment::CENTRE });
868     }
869 
870     auto screenPos = w->windowPos
871         + ScreenCoordsXY{ window_multiplayer_groups_widgets[WIDX_CONTENT_PANEL].left + 4,
872                           window_multiplayer_groups_widgets[WIDX_CONTENT_PANEL].top + 4 };
873 
874     DrawTextBasic(dpi, screenPos, STR_DEFAULT_GROUP, {}, { w->colours[2] });
875 
876     screenPos.y += 20;
877 
878     gfx_fill_rect_inset(
879         dpi, { screenPos - ScreenCoordsXY{ 0, 6 }, screenPos + ScreenCoordsXY{ 310, -5 } }, w->colours[1],
880         INSET_RECT_FLAG_BORDER_INSET);
881 
882     widget = &window_multiplayer_groups_widgets[WIDX_SELECTED_GROUP];
883     group = network_get_group_index(_selectedGroup);
884     if (group != -1)
885     {
886         buffer.assign("{WINDOW_COLOUR_2}");
887         buffer += network_get_group_name(group);
888         auto ft = Formatter();
889         ft.Add<const char*>(buffer.c_str());
890         DrawTextEllipsised(
891             dpi, w->windowPos + ScreenCoordsXY{ widget->midX() - 5, widget->top }, widget->width() - 8, STR_STRING, ft,
892             { TextAlignment::CENTRE });
893     }
894 }
895 
window_multiplayer_groups_scrollpaint(rct_window * w,rct_drawpixelinfo * dpi,int32_t scrollIndex)896 static void window_multiplayer_groups_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex)
897 {
898     auto screenCoords = ScreenCoordsXY{ 0, 0 };
899 
900     auto dpiCoords = ScreenCoordsXY{ dpi->x, dpi->y };
901     gfx_fill_rect(
902         dpi, { dpiCoords, dpiCoords + ScreenCoordsXY{ dpi->width - 1, dpi->height - 1 } }, ColourMapA[w->colours[1]].mid_light);
903 
904     for (int32_t i = 0; i < network_get_num_actions(); i++)
905     {
906         if (i == w->selected_list_item)
907         {
908             gfx_filter_rect(
909                 dpi, { 0, screenCoords.y, 800, screenCoords.y + SCROLLABLE_ROW_HEIGHT - 1 }, FilterPaletteID::PaletteDarken1);
910         }
911         if (screenCoords.y > dpi->y + dpi->height)
912         {
913             break;
914         }
915 
916         if (screenCoords.y + SCROLLABLE_ROW_HEIGHT + 1 >= dpi->y)
917         {
918             int32_t groupindex = network_get_group_index(_selectedGroup);
919             if (groupindex != -1)
920             {
921                 if (network_can_perform_action(groupindex, static_cast<NetworkPermission>(i)))
922                 {
923                     screenCoords.x = 0;
924                     gfx_draw_string(dpi, screenCoords, u8"{WINDOW_COLOUR_2}✓", {});
925                 }
926             }
927 
928             // Draw action name
929             auto ft = Formatter();
930             ft.Add<uint16_t>(network_get_action_name_string_id(i));
931             DrawTextBasic(dpi, { 10, screenCoords.y }, STR_WINDOW_COLOUR_2_STRINGID, ft);
932         }
933         screenCoords.y += SCROLLABLE_ROW_HEIGHT;
934     }
935 }
936 
937 #pragma endregion
938 
939 #pragma region Options page
940 
window_multiplayer_options_mouseup(rct_window * w,rct_widgetindex widgetIndex)941 static void window_multiplayer_options_mouseup(rct_window* w, rct_widgetindex widgetIndex)
942 {
943     switch (widgetIndex)
944     {
945         case WIDX_CLOSE:
946             window_close(w);
947             break;
948         case WIDX_TAB1:
949         case WIDX_TAB2:
950         case WIDX_TAB3:
951         case WIDX_TAB4:
952             if (w->page != widgetIndex - WIDX_TAB1)
953             {
954                 window_multiplayer_set_page(w, widgetIndex - WIDX_TAB1);
955             }
956             break;
957         case WIDX_LOG_CHAT_CHECKBOX:
958             gConfigNetwork.log_chat = !gConfigNetwork.log_chat;
959             config_save_default();
960             break;
961         case WIDX_LOG_SERVER_ACTIONS_CHECKBOX:
962             gConfigNetwork.log_server_actions = !gConfigNetwork.log_server_actions;
963             config_save_default();
964             break;
965         case WIDX_KNOWN_KEYS_ONLY_CHECKBOX:
966             gConfigNetwork.known_keys_only = !gConfigNetwork.known_keys_only;
967             config_save_default();
968             break;
969     }
970 }
971 
window_multiplayer_options_resize(rct_window * w)972 static void window_multiplayer_options_resize(rct_window* w)
973 {
974     window_set_resize(w, 300, 100, 300, 100);
975 }
976 
window_multiplayer_options_update(rct_window * w)977 static void window_multiplayer_options_update(rct_window* w)
978 {
979     w->frame_no++;
980     widget_invalidate(w, WIDX_TAB1 + w->page);
981 }
982 
window_multiplayer_options_invalidate(rct_window * w)983 static void window_multiplayer_options_invalidate(rct_window* w)
984 {
985     window_multiplayer_set_pressed_tab(w);
986     window_multiplayer_anchor_border_widgets(w);
987     window_align_tabs(w, WIDX_TAB1, WIDX_TAB4);
988 
989     if (network_get_mode() == NETWORK_MODE_CLIENT)
990     {
991         w->widgets[WIDX_KNOWN_KEYS_ONLY_CHECKBOX].type = WindowWidgetType::Empty;
992     }
993 
994     WidgetSetCheckboxValue(w, WIDX_LOG_CHAT_CHECKBOX, gConfigNetwork.log_chat);
995     WidgetSetCheckboxValue(w, WIDX_LOG_SERVER_ACTIONS_CHECKBOX, gConfigNetwork.log_server_actions);
996     WidgetSetCheckboxValue(w, WIDX_KNOWN_KEYS_ONLY_CHECKBOX, gConfigNetwork.known_keys_only);
997 }
998 
window_multiplayer_options_paint(rct_window * w,rct_drawpixelinfo * dpi)999 static void window_multiplayer_options_paint(rct_window* w, rct_drawpixelinfo* dpi)
1000 {
1001     WindowDrawWidgets(w, dpi);
1002     window_multiplayer_draw_tab_images(w, dpi);
1003 }
1004 
1005 #pragma endregion
1006 
window_multiplayer_draw_tab_image(rct_window * w,rct_drawpixelinfo * dpi,int32_t page,int32_t spriteIndex)1007 static void window_multiplayer_draw_tab_image(rct_window* w, rct_drawpixelinfo* dpi, int32_t page, int32_t spriteIndex)
1008 {
1009     rct_widgetindex widgetIndex = WIDX_TAB1 + page;
1010 
1011     if (!WidgetIsDisabled(w, widgetIndex))
1012     {
1013         if (w->page == page)
1014         {
1015             int32_t numFrames = window_multiplayer_animation_frames[w->page];
1016             if (numFrames > 1)
1017             {
1018                 int32_t frame = w->frame_no / window_multiplayer_animation_divisor[w->page];
1019                 spriteIndex += (frame % numFrames);
1020             }
1021         }
1022 
1023         gfx_draw_sprite(
1024             dpi, ImageId(spriteIndex),
1025             w->windowPos + ScreenCoordsXY{ w->widgets[widgetIndex].left, w->widgets[widgetIndex].top });
1026     }
1027 }
1028 
window_multiplayer_draw_tab_images(rct_window * w,rct_drawpixelinfo * dpi)1029 static void window_multiplayer_draw_tab_images(rct_window* w, rct_drawpixelinfo* dpi)
1030 {
1031     window_multiplayer_draw_tab_image(w, dpi, WINDOW_MULTIPLAYER_PAGE_INFORMATION, SPR_TAB_KIOSKS_AND_FACILITIES_0);
1032     window_multiplayer_draw_tab_image(w, dpi, WINDOW_MULTIPLAYER_PAGE_PLAYERS, SPR_TAB_GUESTS_0);
1033     window_multiplayer_draw_tab_image(w, dpi, WINDOW_MULTIPLAYER_PAGE_GROUPS, SPR_TAB_STAFF_OPTIONS_0);
1034     window_multiplayer_draw_tab_image(w, dpi, WINDOW_MULTIPLAYER_PAGE_OPTIONS, SPR_TAB_GEARS_0);
1035 }
1036