Fix #8316: Make sort industries by production and transported with a cargo filter...
[openttd-github.git] / src / network / network_gui.cpp
blob076b3d46ec3c6841d0a2febbfdc0faad2dfc059f
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 network_gui.cpp Implementation of the Network related GUIs. */
10 #include "../stdafx.h"
11 #include "../strings_func.h"
12 #include "../date_func.h"
13 #include "../fios.h"
14 #include "network_client.h"
15 #include "network_gui.h"
16 #include "network_gamelist.h"
17 #include "network.h"
18 #include "network_base.h"
19 #include "network_content.h"
20 #include "network_server.h"
21 #include "network_coordinator.h"
22 #include "../gui.h"
23 #include "network_udp.h"
24 #include "../window_func.h"
25 #include "../gfx_func.h"
26 #include "../widgets/dropdown_type.h"
27 #include "../widgets/dropdown_func.h"
28 #include "../querystring_gui.h"
29 #include "../sortlist_type.h"
30 #include "../company_func.h"
31 #include "../command_func.h"
32 #include "../core/geometry_func.hpp"
33 #include "../genworld.h"
34 #include "../map_type.h"
35 #include "../guitimer_func.h"
36 #include "../zoom_func.h"
37 #include "../sprite.h"
38 #include "../settings_internal.h"
40 #include "../widgets/network_widget.h"
42 #include "table/strings.h"
43 #include "../table/sprites.h"
45 #include "../stringfilter_type.h"
47 #ifdef __EMSCRIPTEN__
48 # include <emscripten.h>
49 #endif
51 #include <map>
53 #include "../safeguards.h"
55 static void ShowNetworkStartServerWindow();
56 static void ShowNetworkLobbyWindow(NetworkGameList *ngl);
58 static const int NETWORK_LIST_REFRESH_DELAY = 30; ///< Time, in seconds, between updates of the network list.
60 static ClientID _admin_client_id = INVALID_CLIENT_ID; ///< For what client a confirmation window is open.
61 static CompanyID _admin_company_id = INVALID_COMPANY; ///< For what company a confirmation window is open.
63 /**
64 * Update the network new window because a new server is
65 * found on the network.
67 void UpdateNetworkGameWindow()
69 InvalidateWindowData(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_GAME, 0);
72 static DropDownList BuildVisibilityDropDownList()
74 DropDownList list;
76 list.emplace_back(new DropDownListStringItem(STR_NETWORK_SERVER_VISIBILITY_LOCAL, SERVER_GAME_TYPE_LOCAL, false));
77 list.emplace_back(new DropDownListStringItem(STR_NETWORK_SERVER_VISIBILITY_INVITE_ONLY, SERVER_GAME_TYPE_INVITE_ONLY, false));
78 list.emplace_back(new DropDownListStringItem(STR_NETWORK_SERVER_VISIBILITY_PUBLIC, SERVER_GAME_TYPE_PUBLIC, false));
80 return list;
83 typedef GUIList<NetworkGameList*, StringFilter&> GUIGameServerList;
84 typedef int ServerListPosition;
85 static const ServerListPosition SLP_INVALID = -1;
87 /** Full blown container to make it behave exactly as we want :) */
88 class NWidgetServerListHeader : public NWidgetContainer {
89 static const uint MINIMUM_NAME_WIDTH_BEFORE_NEW_HEADER = 150; ///< Minimum width before adding a new header
90 bool visible[6]; ///< The visible headers
91 public:
92 NWidgetServerListHeader() : NWidgetContainer(NWID_HORIZONTAL)
94 NWidgetLeaf *leaf = new NWidgetLeaf(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_NAME, STR_NETWORK_SERVER_LIST_GAME_NAME, STR_NETWORK_SERVER_LIST_GAME_NAME_TOOLTIP);
95 leaf->SetResize(1, 0);
96 leaf->SetFill(1, 0);
97 this->Add(leaf);
99 this->Add(new NWidgetLeaf(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_CLIENTS, STR_NETWORK_SERVER_LIST_CLIENTS_CAPTION, STR_NETWORK_SERVER_LIST_CLIENTS_CAPTION_TOOLTIP));
100 this->Add(new NWidgetLeaf(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_MAPSIZE, STR_NETWORK_SERVER_LIST_MAP_SIZE_CAPTION, STR_NETWORK_SERVER_LIST_MAP_SIZE_CAPTION_TOOLTIP));
101 this->Add(new NWidgetLeaf(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_DATE, STR_NETWORK_SERVER_LIST_DATE_CAPTION, STR_NETWORK_SERVER_LIST_DATE_CAPTION_TOOLTIP));
102 this->Add(new NWidgetLeaf(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_YEARS, STR_NETWORK_SERVER_LIST_YEARS_CAPTION, STR_NETWORK_SERVER_LIST_YEARS_CAPTION_TOOLTIP));
104 leaf = new NWidgetLeaf(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_INFO, STR_EMPTY, STR_NETWORK_SERVER_LIST_INFO_ICONS_TOOLTIP);
105 leaf->SetMinimalSize(14 + GetSpriteSize(SPR_LOCK, nullptr, ZOOM_LVL_OUT_4X).width
106 + GetSpriteSize(SPR_BLOT, nullptr, ZOOM_LVL_OUT_4X).width
107 + GetSpriteSize(SPR_FLAGS_BASE, nullptr, ZOOM_LVL_OUT_4X).width, 12);
108 leaf->SetFill(0, 1);
109 this->Add(leaf);
111 /* First and last are always visible, the rest is implicitly zeroed */
112 this->visible[0] = true;
113 *lastof(this->visible) = true;
116 void SetupSmallestSize(Window *w, bool init_array) override
118 /* Oh yeah, we ought to be findable! */
119 w->nested_array[WID_NG_HEADER] = this;
121 this->smallest_y = 0; // Biggest child.
122 this->fill_x = 1;
123 this->fill_y = 0;
124 this->resize_x = 1; // We only resize in this direction
125 this->resize_y = 0; // We never resize in this direction
127 /* First initialise some variables... */
128 for (NWidgetBase *child_wid = this->head; child_wid != nullptr; child_wid = child_wid->next) {
129 child_wid->SetupSmallestSize(w, init_array);
130 this->smallest_y = std::max(this->smallest_y, child_wid->smallest_y + child_wid->padding_top + child_wid->padding_bottom);
133 /* ... then in a second pass make sure the 'current' sizes are set. Won't change for most widgets. */
134 for (NWidgetBase *child_wid = this->head; child_wid != nullptr; child_wid = child_wid->next) {
135 child_wid->current_x = child_wid->smallest_x;
136 child_wid->current_y = this->smallest_y;
139 this->smallest_x = this->head->smallest_x + this->tail->smallest_x; // First and last are always shown, rest not
142 void AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl) override
144 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
146 this->pos_x = x;
147 this->pos_y = y;
148 this->current_x = given_width;
149 this->current_y = given_height;
151 given_width -= this->tail->smallest_x;
152 NWidgetBase *child_wid = this->head->next;
153 /* The first and last widget are always visible, determine which other should be visible */
154 for (uint i = 1; i < lengthof(this->visible) - 1; i++) {
155 if (given_width > MINIMUM_NAME_WIDTH_BEFORE_NEW_HEADER + child_wid->smallest_x && this->visible[i - 1]) {
156 this->visible[i] = true;
157 given_width -= child_wid->smallest_x;
158 } else {
159 this->visible[i] = false;
161 child_wid = child_wid->next;
164 /* All remaining space goes to the first (name) widget */
165 this->head->current_x = given_width;
167 /* Now assign the widgets to their rightful place */
168 uint position = 0; // Place to put next child relative to origin of the container.
169 uint i = rtl ? lengthof(this->visible) - 1 : 0;
170 child_wid = rtl ? this->tail : this->head;
171 while (child_wid != nullptr) {
172 if (this->visible[i]) {
173 child_wid->AssignSizePosition(sizing, x + position, y, child_wid->current_x, this->current_y, rtl);
174 position += child_wid->current_x;
177 child_wid = rtl ? child_wid->prev : child_wid->next;
178 i += rtl ? -1 : 1;
182 void Draw(const Window *w) override
184 int i = 0;
185 for (NWidgetBase *child_wid = this->head; child_wid != nullptr; child_wid = child_wid->next) {
186 if (!this->visible[i++]) continue;
188 child_wid->Draw(w);
192 NWidgetCore *GetWidgetFromPos(int x, int y) override
194 if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return nullptr;
196 int i = 0;
197 for (NWidgetBase *child_wid = this->head; child_wid != nullptr; child_wid = child_wid->next) {
198 if (!this->visible[i++]) continue;
199 NWidgetCore *nwid = child_wid->GetWidgetFromPos(x, y);
200 if (nwid != nullptr) return nwid;
202 return nullptr;
206 * Checks whether the given widget is actually visible.
207 * @param widget the widget to check for visibility
208 * @return true iff the widget is visible.
210 bool IsWidgetVisible(NetworkGameWidgets widget) const
212 assert((uint)(widget - WID_NG_NAME) < lengthof(this->visible));
213 return this->visible[widget - WID_NG_NAME];
217 class NetworkGameWindow : public Window {
218 protected:
219 /* Runtime saved values */
220 static Listing last_sorting;
222 /* Constants for sorting servers */
223 static GUIGameServerList::SortFunction * const sorter_funcs[];
224 static GUIGameServerList::FilterFunction * const filter_funcs[];
226 NetworkGameList *server; ///< Selected server.
227 NetworkGameList *last_joined; ///< The last joined server.
228 GUIGameServerList servers; ///< List with game servers.
229 ServerListPosition list_pos; ///< Position of the selected server.
230 Scrollbar *vscroll; ///< Vertical scrollbar of the list of servers.
231 QueryString name_editbox; ///< Client name editbox.
232 QueryString filter_editbox; ///< Editbox for filter on servers.
233 GUITimer requery_timer; ///< Timer for network requery.
234 bool searched_internet = false; ///< Did we ever press "Search Internet" button?
236 int lock_offset; ///< Left offset for lock icon.
237 int blot_offset; ///< Left offset for green/yellow/red compatibility icon.
238 int flag_offset; ///< Left offset for language flag icon.
241 * (Re)build the GUI network game list (a.k.a. this->servers) as some
242 * major change has occurred. It ensures appropriate filtering and
243 * sorting, if both or either one is enabled.
245 void BuildGUINetworkGameList()
247 if (!this->servers.NeedRebuild()) return;
249 /* Create temporary array of games to use for listing */
250 this->servers.clear();
252 bool found_current_server = false;
253 for (NetworkGameList *ngl = _network_game_list; ngl != nullptr; ngl = ngl->next) {
254 this->servers.push_back(ngl);
255 if (ngl == this->server) {
256 found_current_server = true;
259 /* A refresh can cause the current server to be delete; so unselect. */
260 if (!found_current_server) {
261 if (this->server == this->last_joined) this->last_joined = nullptr;
262 this->server = nullptr;
263 this->list_pos = SLP_INVALID;
266 /* Apply the filter condition immediately, if a search string has been provided. */
267 StringFilter sf;
268 sf.SetFilterTerm(this->filter_editbox.text.buf);
270 if (!sf.IsEmpty()) {
271 this->servers.SetFilterState(true);
272 this->servers.Filter(sf);
273 } else {
274 this->servers.SetFilterState(false);
277 this->servers.shrink_to_fit();
278 this->servers.RebuildDone();
279 this->vscroll->SetCount((int)this->servers.size());
281 /* Sort the list of network games as requested. */
282 this->servers.Sort();
283 this->UpdateListPos();
286 /** Sort servers by name. */
287 static bool NGameNameSorter(NetworkGameList * const &a, NetworkGameList * const &b)
289 int r = strnatcmp(a->info.server_name.c_str(), b->info.server_name.c_str(), true); // Sort by name (natural sorting).
290 if (r == 0) r = a->connection_string.compare(b->connection_string);
292 return r < 0;
296 * Sort servers by the amount of clients online on a
297 * server. If the two servers have the same amount, the one with the
298 * higher maximum is preferred.
300 static bool NGameClientSorter(NetworkGameList * const &a, NetworkGameList * const &b)
302 /* Reverse as per default we are interested in most-clients first */
303 int r = a->info.clients_on - b->info.clients_on;
305 if (r == 0) r = a->info.clients_max - b->info.clients_max;
306 if (r == 0) return NGameNameSorter(a, b);
308 return r < 0;
311 /** Sort servers by map size */
312 static bool NGameMapSizeSorter(NetworkGameList * const &a, NetworkGameList * const &b)
314 /* Sort by the area of the map. */
315 int r = (a->info.map_height) * (a->info.map_width) - (b->info.map_height) * (b->info.map_width);
317 if (r == 0) r = a->info.map_width - b->info.map_width;
318 return (r != 0) ? r < 0 : NGameClientSorter(a, b);
321 /** Sort servers by current date */
322 static bool NGameDateSorter(NetworkGameList * const &a, NetworkGameList * const &b)
324 int r = a->info.game_date - b->info.game_date;
325 return (r != 0) ? r < 0 : NGameClientSorter(a, b);
328 /** Sort servers by the number of days the game is running */
329 static bool NGameYearsSorter(NetworkGameList * const &a, NetworkGameList * const &b)
331 int r = a->info.game_date - a->info.start_date - b->info.game_date + b->info.start_date;
332 return (r != 0) ? r < 0: NGameDateSorter(a, b);
336 * Sort servers by joinability. If both servers are the
337 * same, prefer the non-passworded server first.
339 static bool NGameAllowedSorter(NetworkGameList * const &a, NetworkGameList * const &b)
341 /* The servers we do not know anything about (the ones that did not reply) should be at the bottom) */
342 int r = a->info.server_revision.empty() - b->info.server_revision.empty();
344 /* Reverse default as we are interested in version-compatible clients first */
345 if (r == 0) r = b->info.version_compatible - a->info.version_compatible;
346 /* The version-compatible ones are then sorted with NewGRF compatible first, incompatible last */
347 if (r == 0) r = b->info.compatible - a->info.compatible;
348 /* Passworded servers should be below unpassworded servers */
349 if (r == 0) r = a->info.use_password - b->info.use_password;
351 /* Finally sort on the number of clients of the server in reverse order. */
352 return (r != 0) ? r < 0 : NGameClientSorter(b, a);
355 /** Sort the server list */
356 void SortNetworkGameList()
358 if (this->servers.Sort()) this->UpdateListPos();
361 /** Set this->list_pos to match this->server */
362 void UpdateListPos()
364 this->list_pos = SLP_INVALID;
365 for (uint i = 0; i != this->servers.size(); i++) {
366 if (this->servers[i] == this->server) {
367 this->list_pos = i;
368 break;
373 static bool CDECL NGameSearchFilter(NetworkGameList * const *item, StringFilter &sf)
375 assert(item != nullptr);
376 assert((*item) != nullptr);
378 sf.ResetState();
379 sf.AddLine((*item)->info.server_name.c_str());
380 return sf.GetState();
384 * Draw a single server line.
385 * @param cur_item the server to draw.
386 * @param y from where to draw?
387 * @param highlight does the line need to be highlighted?
389 void DrawServerLine(const NetworkGameList *cur_item, uint y, bool highlight) const
391 const NWidgetBase *nwi_name = this->GetWidget<NWidgetBase>(WID_NG_NAME);
392 const NWidgetBase *nwi_info = this->GetWidget<NWidgetBase>(WID_NG_INFO);
394 /* show highlighted item with a different colour */
395 if (highlight) GfxFillRect(nwi_name->pos_x + 1, y + 1, nwi_info->pos_x + nwi_info->current_x - 2, y + this->resize.step_height - 2, PC_GREY);
397 /* offsets to vertically centre text and icons */
398 int text_y_offset = (this->resize.step_height - FONT_HEIGHT_NORMAL) / 2 + 1;
399 int icon_y_offset = (this->resize.step_height - GetSpriteSize(SPR_BLOT).height) / 2;
400 int lock_y_offset = (this->resize.step_height - GetSpriteSize(SPR_LOCK).height) / 2;
402 DrawString(nwi_name->pos_x + WD_FRAMERECT_LEFT, nwi_name->pos_x + nwi_name->current_x - WD_FRAMERECT_RIGHT, y + text_y_offset, cur_item->info.server_name, TC_BLACK);
404 /* only draw details if the server is online */
405 if (cur_item->online) {
406 const NWidgetServerListHeader *nwi_header = this->GetWidget<NWidgetServerListHeader>(WID_NG_HEADER);
408 if (nwi_header->IsWidgetVisible(WID_NG_CLIENTS)) {
409 const NWidgetBase *nwi_clients = this->GetWidget<NWidgetBase>(WID_NG_CLIENTS);
410 SetDParam(0, cur_item->info.clients_on);
411 SetDParam(1, cur_item->info.clients_max);
412 SetDParam(2, cur_item->info.companies_on);
413 SetDParam(3, cur_item->info.companies_max);
414 DrawString(nwi_clients->pos_x, nwi_clients->pos_x + nwi_clients->current_x - 1, y + text_y_offset, STR_NETWORK_SERVER_LIST_GENERAL_ONLINE, TC_FROMSTRING, SA_HOR_CENTER);
417 if (nwi_header->IsWidgetVisible(WID_NG_MAPSIZE)) {
418 /* map size */
419 const NWidgetBase *nwi_mapsize = this->GetWidget<NWidgetBase>(WID_NG_MAPSIZE);
420 SetDParam(0, cur_item->info.map_width);
421 SetDParam(1, cur_item->info.map_height);
422 DrawString(nwi_mapsize->pos_x, nwi_mapsize->pos_x + nwi_mapsize->current_x - 1, y + text_y_offset, STR_NETWORK_SERVER_LIST_MAP_SIZE_SHORT, TC_FROMSTRING, SA_HOR_CENTER);
425 if (nwi_header->IsWidgetVisible(WID_NG_DATE)) {
426 /* current date */
427 const NWidgetBase *nwi_date = this->GetWidget<NWidgetBase>(WID_NG_DATE);
428 YearMonthDay ymd;
429 ConvertDateToYMD(cur_item->info.game_date, &ymd);
430 SetDParam(0, ymd.year);
431 DrawString(nwi_date->pos_x, nwi_date->pos_x + nwi_date->current_x - 1, y + text_y_offset, STR_JUST_INT, TC_BLACK, SA_HOR_CENTER);
434 if (nwi_header->IsWidgetVisible(WID_NG_YEARS)) {
435 /* number of years the game is running */
436 const NWidgetBase *nwi_years = this->GetWidget<NWidgetBase>(WID_NG_YEARS);
437 YearMonthDay ymd_cur, ymd_start;
438 ConvertDateToYMD(cur_item->info.game_date, &ymd_cur);
439 ConvertDateToYMD(cur_item->info.start_date, &ymd_start);
440 SetDParam(0, ymd_cur.year - ymd_start.year);
441 DrawString(nwi_years->pos_x, nwi_years->pos_x + nwi_years->current_x - 1, y + text_y_offset, STR_JUST_INT, TC_BLACK, SA_HOR_CENTER);
444 /* draw a lock if the server is password protected */
445 if (cur_item->info.use_password) DrawSprite(SPR_LOCK, PAL_NONE, nwi_info->pos_x + this->lock_offset, y + lock_y_offset);
447 /* draw red or green icon, depending on compatibility with server */
448 DrawSprite(SPR_BLOT, (cur_item->info.compatible ? PALETTE_TO_GREEN : (cur_item->info.version_compatible ? PALETTE_TO_YELLOW : PALETTE_TO_RED)), nwi_info->pos_x + this->blot_offset, y + icon_y_offset + 1);
453 * Scroll the list up or down to the currently selected server.
454 * If the server is below the currently displayed servers, it will
455 * scroll down an amount so that the server appears at the bottom.
456 * If the server is above the currently displayed servers, it will
457 * scroll up so that the server appears at the top.
459 void ScrollToSelectedServer()
461 if (this->list_pos == SLP_INVALID) return; // no server selected
462 this->vscroll->ScrollTowards(this->list_pos);
465 public:
466 NetworkGameWindow(WindowDesc *desc) : Window(desc), name_editbox(NETWORK_CLIENT_NAME_LENGTH), filter_editbox(120)
468 this->list_pos = SLP_INVALID;
469 this->server = nullptr;
471 this->lock_offset = 5;
472 this->blot_offset = this->lock_offset + 3 + GetSpriteSize(SPR_LOCK).width;
473 this->flag_offset = this->blot_offset + 2 + GetSpriteSize(SPR_BLOT).width;
475 this->CreateNestedTree();
476 this->vscroll = this->GetScrollbar(WID_NG_SCROLLBAR);
477 this->FinishInitNested(WN_NETWORK_WINDOW_GAME);
479 this->querystrings[WID_NG_CLIENT] = &this->name_editbox;
480 this->name_editbox.text.Assign(_settings_client.network.client_name.c_str());
482 this->querystrings[WID_NG_FILTER] = &this->filter_editbox;
483 this->filter_editbox.cancel_button = QueryString::ACTION_CLEAR;
484 this->SetFocusedWidget(WID_NG_FILTER);
486 /* As the Game Coordinator doesn't support "websocket" servers yet, we
487 * let "os/emscripten/pre.js" hardcode a list of servers people can
488 * join. This means the serverlist is curated for now, but it is the
489 * best we can offer. */
490 #ifdef __EMSCRIPTEN__
491 EM_ASM(if (window["openttd_server_list"]) openttd_server_list());
492 #endif
494 this->last_joined = NetworkAddServer(_settings_client.network.last_joined, false);
495 this->server = this->last_joined;
497 this->requery_timer.SetInterval(NETWORK_LIST_REFRESH_DELAY * 1000);
499 this->servers.SetListing(this->last_sorting);
500 this->servers.SetSortFuncs(this->sorter_funcs);
501 this->servers.SetFilterFuncs(this->filter_funcs);
502 this->servers.ForceRebuild();
505 ~NetworkGameWindow()
507 this->last_sorting = this->servers.GetListing();
510 void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
512 switch (widget) {
513 case WID_NG_MATRIX:
514 resize->height = WD_MATRIX_TOP + std::max(GetSpriteSize(SPR_BLOT).height, (uint)FONT_HEIGHT_NORMAL) + WD_MATRIX_BOTTOM;
515 fill->height = resize->height;
516 size->height = 12 * resize->height;
517 break;
519 case WID_NG_LASTJOINED:
520 size->height = WD_MATRIX_TOP + std::max(GetSpriteSize(SPR_BLOT).height, (uint)FONT_HEIGHT_NORMAL) + WD_MATRIX_BOTTOM;
521 break;
523 case WID_NG_LASTJOINED_SPACER:
524 size->width = NWidgetScrollbar::GetVerticalDimension().width;
525 break;
527 case WID_NG_NAME:
528 size->width += 2 * Window::SortButtonWidth(); // Make space for the arrow
529 break;
531 case WID_NG_CLIENTS:
532 size->width += 2 * Window::SortButtonWidth(); // Make space for the arrow
533 SetDParamMaxValue(0, MAX_CLIENTS);
534 SetDParamMaxValue(1, MAX_CLIENTS);
535 SetDParamMaxValue(2, MAX_COMPANIES);
536 SetDParamMaxValue(3, MAX_COMPANIES);
537 *size = maxdim(*size, GetStringBoundingBox(STR_NETWORK_SERVER_LIST_GENERAL_ONLINE));
538 break;
540 case WID_NG_MAPSIZE:
541 size->width += 2 * Window::SortButtonWidth(); // Make space for the arrow
542 SetDParamMaxValue(0, MAX_MAP_SIZE);
543 SetDParamMaxValue(1, MAX_MAP_SIZE);
544 *size = maxdim(*size, GetStringBoundingBox(STR_NETWORK_SERVER_LIST_MAP_SIZE_SHORT));
545 break;
547 case WID_NG_DATE:
548 case WID_NG_YEARS:
549 size->width += 2 * Window::SortButtonWidth(); // Make space for the arrow
550 SetDParamMaxValue(0, 5);
551 *size = maxdim(*size, GetStringBoundingBox(STR_JUST_INT));
552 break;
556 void DrawWidget(const Rect &r, int widget) const override
558 switch (widget) {
559 case WID_NG_MATRIX: {
560 uint16 y = r.top;
562 const int max = std::min(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), (int)this->servers.size());
564 for (int i = this->vscroll->GetPosition(); i < max; ++i) {
565 const NetworkGameList *ngl = this->servers[i];
566 this->DrawServerLine(ngl, y, ngl == this->server);
567 y += this->resize.step_height;
569 break;
572 case WID_NG_LASTJOINED:
573 /* Draw the last joined server, if any */
574 if (this->last_joined != nullptr) this->DrawServerLine(this->last_joined, r.top, this->last_joined == this->server);
575 break;
577 case WID_NG_DETAILS:
578 this->DrawDetails(r);
579 break;
581 case WID_NG_NAME:
582 case WID_NG_CLIENTS:
583 case WID_NG_MAPSIZE:
584 case WID_NG_DATE:
585 case WID_NG_YEARS:
586 case WID_NG_INFO:
587 if (widget - WID_NG_NAME == this->servers.SortType()) this->DrawSortButtonState(widget, this->servers.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
588 break;
593 void OnPaint() override
595 if (this->servers.NeedRebuild()) {
596 this->BuildGUINetworkGameList();
598 if (this->servers.NeedResort()) {
599 this->SortNetworkGameList();
602 NetworkGameList *sel = this->server;
603 /* 'Refresh' button invisible if no server selected */
604 this->SetWidgetDisabledState(WID_NG_REFRESH, sel == nullptr);
605 /* 'Join' button disabling conditions */
606 this->SetWidgetDisabledState(WID_NG_JOIN, sel == nullptr || // no Selected Server
607 !sel->online || // Server offline
608 sel->info.clients_on >= sel->info.clients_max || // Server full
609 !sel->info.compatible); // Revision mismatch
611 /* 'NewGRF Settings' button invisible if no NewGRF is used */
612 this->GetWidget<NWidgetStacked>(WID_NG_NEWGRF_SEL)->SetDisplayedPlane(sel == nullptr || !sel->online || sel->info.grfconfig == nullptr);
613 this->GetWidget<NWidgetStacked>(WID_NG_NEWGRF_MISSING_SEL)->SetDisplayedPlane(sel == nullptr || !sel->online || sel->info.grfconfig == nullptr || !sel->info.version_compatible || sel->info.compatible);
615 #ifdef __EMSCRIPTEN__
616 this->SetWidgetDisabledState(WID_NG_SEARCH_INTERNET, true);
617 this->SetWidgetDisabledState(WID_NG_SEARCH_LAN, true);
618 this->SetWidgetDisabledState(WID_NG_ADD, true);
619 this->SetWidgetDisabledState(WID_NG_START, true);
620 #endif
622 this->DrawWidgets();
625 void DrawDetails(const Rect &r) const
627 NetworkGameList *sel = this->server;
629 const int detail_height = 6 + 8 + 6 + 3 * FONT_HEIGHT_NORMAL;
631 /* Draw the right menu */
632 GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.top + detail_height - 1, PC_DARK_BLUE);
633 if (sel == nullptr) {
634 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + 6 + 4 + FONT_HEIGHT_NORMAL, STR_NETWORK_SERVER_LIST_GAME_INFO, TC_FROMSTRING, SA_HOR_CENTER);
635 } else if (!sel->online) {
636 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + 6 + 4 + FONT_HEIGHT_NORMAL, sel->info.server_name, TC_ORANGE, SA_HOR_CENTER); // game name
638 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + detail_height + 4, STR_NETWORK_SERVER_LIST_SERVER_OFFLINE, TC_FROMSTRING, SA_HOR_CENTER); // server offline
639 } else { // show game info
641 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + 6, STR_NETWORK_SERVER_LIST_GAME_INFO, TC_FROMSTRING, SA_HOR_CENTER);
642 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + 6 + 4 + FONT_HEIGHT_NORMAL, sel->info.server_name, TC_ORANGE, SA_HOR_CENTER); // game name
644 uint16 y = r.top + detail_height + 4;
646 SetDParam(0, sel->info.clients_on);
647 SetDParam(1, sel->info.clients_max);
648 SetDParam(2, sel->info.companies_on);
649 SetDParam(3, sel->info.companies_max);
650 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_CLIENTS);
651 y += FONT_HEIGHT_NORMAL;
653 SetDParam(0, STR_CHEAT_SWITCH_CLIMATE_TEMPERATE_LANDSCAPE + sel->info.landscape);
654 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_LANDSCAPE); // landscape
655 y += FONT_HEIGHT_NORMAL;
657 SetDParam(0, sel->info.map_width);
658 SetDParam(1, sel->info.map_height);
659 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_MAP_SIZE); // map size
660 y += FONT_HEIGHT_NORMAL;
662 SetDParamStr(0, sel->info.server_revision);
663 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_SERVER_VERSION); // server version
664 y += FONT_HEIGHT_NORMAL;
666 SetDParamStr(0, sel->connection_string);
667 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_SERVER_ADDRESS); // server address
668 y += FONT_HEIGHT_NORMAL;
670 SetDParam(0, sel->info.start_date);
671 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_START_DATE); // start date
672 y += FONT_HEIGHT_NORMAL;
674 SetDParam(0, sel->info.game_date);
675 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_CURRENT_DATE); // current date
676 y += FONT_HEIGHT_NORMAL;
678 if (sel->info.gamescript_version != -1) {
679 SetDParamStr(0, sel->info.gamescript_name);
680 SetDParam(1, sel->info.gamescript_version);
681 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_GAMESCRIPT); // gamescript name and version
682 y += FONT_HEIGHT_NORMAL;
685 y += WD_PAR_VSEP_NORMAL;
687 if (!sel->info.compatible) {
688 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, sel->info.version_compatible ? STR_NETWORK_SERVER_LIST_GRF_MISMATCH : STR_NETWORK_SERVER_LIST_VERSION_MISMATCH, TC_FROMSTRING, SA_HOR_CENTER); // server mismatch
689 } else if (sel->info.clients_on == sel->info.clients_max) {
690 /* Show: server full, when clients_on == max_clients */
691 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_SERVER_FULL, TC_FROMSTRING, SA_HOR_CENTER); // server full
692 } else if (sel->info.use_password) {
693 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_PASSWORD, TC_FROMSTRING, SA_HOR_CENTER); // password warning
698 void OnClick(Point pt, int widget, int click_count) override
700 switch (widget) {
701 case WID_NG_CANCEL: // Cancel button
702 CloseWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_GAME);
703 break;
705 case WID_NG_NAME: // Sort by name
706 case WID_NG_CLIENTS: // Sort by connected clients
707 case WID_NG_MAPSIZE: // Sort by map size
708 case WID_NG_DATE: // Sort by date
709 case WID_NG_YEARS: // Sort by years
710 case WID_NG_INFO: // Connectivity (green dot)
711 if (this->servers.SortType() == widget - WID_NG_NAME) {
712 this->servers.ToggleSortOrder();
713 if (this->list_pos != SLP_INVALID) this->list_pos = (ServerListPosition)this->servers.size() - this->list_pos - 1;
714 } else {
715 this->servers.SetSortType(widget - WID_NG_NAME);
716 this->servers.ForceResort();
717 this->SortNetworkGameList();
719 this->ScrollToSelectedServer();
720 this->SetDirty();
721 break;
723 case WID_NG_MATRIX: { // Show available network games
724 uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NG_MATRIX);
725 this->server = (id_v < this->servers.size()) ? this->servers[id_v] : nullptr;
726 this->list_pos = (server == nullptr) ? SLP_INVALID : id_v;
727 this->SetDirty();
729 /* FIXME the disabling should go into some InvalidateData, which is called instead of the SetDirty */
730 if (click_count > 1 && !this->IsWidgetDisabled(WID_NG_JOIN)) this->OnClick(pt, WID_NG_JOIN, 1);
731 break;
734 case WID_NG_LASTJOINED: {
735 if (this->last_joined != nullptr) {
736 this->server = this->last_joined;
738 /* search the position of the newly selected server */
739 this->UpdateListPos();
740 this->ScrollToSelectedServer();
741 this->SetDirty();
743 /* FIXME the disabling should go into some InvalidateData, which is called instead of the SetDirty */
744 if (click_count > 1 && !this->IsWidgetDisabled(WID_NG_JOIN)) this->OnClick(pt, WID_NG_JOIN, 1);
746 break;
749 case WID_NG_SEARCH_INTERNET:
750 _network_coordinator_client.GetListing();
751 this->searched_internet = true;
752 break;
754 case WID_NG_SEARCH_LAN:
755 NetworkUDPSearchGame();
756 break;
758 case WID_NG_ADD: // Add a server
759 SetDParamStr(0, _settings_client.network.connect_to_ip);
760 ShowQueryString(
761 STR_JUST_RAW_STRING,
762 STR_NETWORK_SERVER_LIST_ENTER_SERVER_ADDRESS,
763 NETWORK_HOSTNAME_PORT_LENGTH, // maximum number of characters including '\0'
764 this, CS_ALPHANUMERAL, QSF_ACCEPT_UNCHANGED);
765 break;
767 case WID_NG_START: // Start server
768 ShowNetworkStartServerWindow();
769 break;
771 case WID_NG_JOIN: // Join Game
772 if (this->server != nullptr) {
773 ShowNetworkLobbyWindow(this->server);
775 break;
777 case WID_NG_REFRESH: // Refresh
778 if (this->server != nullptr) NetworkQueryServer(this->server->connection_string);
779 break;
781 case WID_NG_NEWGRF: // NewGRF Settings
782 if (this->server != nullptr) ShowNewGRFSettings(false, false, false, &this->server->info.grfconfig);
783 break;
785 case WID_NG_NEWGRF_MISSING: // Find missing content online
786 if (this->server != nullptr) ShowMissingContentWindow(this->server->info.grfconfig);
787 break;
792 * Some data on this window has become invalid.
793 * @param data Information about the changed data.
794 * @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.
796 void OnInvalidateData(int data = 0, bool gui_scope = true) override
798 this->servers.ForceRebuild();
799 this->SetDirty();
802 EventState OnKeyPress(WChar key, uint16 keycode) override
804 EventState state = ES_NOT_HANDLED;
806 /* handle up, down, pageup, pagedown, home and end */
807 if (this->vscroll->UpdateListPositionOnKeyPress(this->list_pos, keycode) == ES_HANDLED) {
808 if (this->list_pos == SLP_INVALID) return ES_HANDLED;
810 this->server = this->servers[this->list_pos];
812 /* Scroll to the new server if it is outside the current range. */
813 this->ScrollToSelectedServer();
815 /* redraw window */
816 this->SetDirty();
817 return ES_HANDLED;
820 if (this->server != nullptr) {
821 if (keycode == WKC_DELETE) { // Press 'delete' to remove servers
822 NetworkGameListRemoveItem(this->server);
823 if (this->server == this->last_joined) this->last_joined = nullptr;
824 this->server = nullptr;
825 this->list_pos = SLP_INVALID;
829 return state;
832 void OnEditboxChanged(int wid) override
834 switch (wid) {
835 case WID_NG_FILTER: {
836 this->servers.ForceRebuild();
837 this->BuildGUINetworkGameList();
838 this->ScrollToSelectedServer();
839 this->SetDirty();
840 break;
843 case WID_NG_CLIENT:
844 /* Validation of the name will happen once the user tries to join or start a game, as getting
845 * error messages while typing (e.g. when you clear the name) defeats the purpose of the check. */
846 _settings_client.network.client_name = this->name_editbox.text.buf;
847 break;
851 void OnQueryTextFinished(char *str) override
853 if (!StrEmpty(str)) {
854 _settings_client.network.connect_to_ip = str;
855 NetworkAddServer(str);
856 NetworkRebuildHostList();
860 void OnResize() override
862 this->vscroll->SetCapacityFromWidget(this, WID_NG_MATRIX);
865 void OnRealtimeTick(uint delta_ms) override
867 if (!this->searched_internet) return;
868 if (!this->requery_timer.Elapsed(delta_ms)) return;
869 this->requery_timer.SetInterval(NETWORK_LIST_REFRESH_DELAY * 1000);
871 _network_coordinator_client.GetListing();
875 Listing NetworkGameWindow::last_sorting = {false, 5};
876 GUIGameServerList::SortFunction * const NetworkGameWindow::sorter_funcs[] = {
877 &NGameNameSorter,
878 &NGameClientSorter,
879 &NGameMapSizeSorter,
880 &NGameDateSorter,
881 &NGameYearsSorter,
882 &NGameAllowedSorter
885 GUIGameServerList::FilterFunction * const NetworkGameWindow::filter_funcs[] = {
886 &NGameSearchFilter
889 static NWidgetBase *MakeResizableHeader(int *biggest_index)
891 *biggest_index = std::max<int>(*biggest_index, WID_NG_INFO);
892 return new NWidgetServerListHeader();
895 static const NWidgetPart _nested_network_game_widgets[] = {
896 /* TOP */
897 NWidget(NWID_HORIZONTAL),
898 NWidget(WWT_CLOSEBOX, COLOUR_LIGHT_BLUE),
899 NWidget(WWT_CAPTION, COLOUR_LIGHT_BLUE), SetDataTip(STR_NETWORK_SERVER_LIST_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
900 NWidget(WWT_DEFSIZEBOX, COLOUR_LIGHT_BLUE),
901 EndContainer(),
902 NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, WID_NG_MAIN),
903 NWidget(NWID_VERTICAL), SetPIP(10, 7, 0),
904 NWidget(NWID_HORIZONTAL), SetPIP(10, 7, 10),
905 /* LEFT SIDE */
906 NWidget(NWID_VERTICAL), SetPIP(0, 7, 0),
907 NWidget(NWID_HORIZONTAL), SetPIP(0, 7, 0),
908 NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, WID_NG_FILTER_LABEL), SetDataTip(STR_LIST_FILTER_TITLE, STR_NULL),
909 NWidget(WWT_EDITBOX, COLOUR_LIGHT_BLUE, WID_NG_FILTER), SetMinimalSize(251, 12), SetFill(1, 0), SetResize(1, 0),
910 SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
911 EndContainer(),
912 NWidget(NWID_HORIZONTAL),
913 NWidget(NWID_VERTICAL),
914 NWidgetFunction(MakeResizableHeader),
915 NWidget(WWT_MATRIX, COLOUR_LIGHT_BLUE, WID_NG_MATRIX), SetResize(1, 1), SetFill(1, 0),
916 SetMatrixDataTip(1, 0, STR_NETWORK_SERVER_LIST_CLICK_GAME_TO_SELECT), SetScrollbar(WID_NG_SCROLLBAR),
917 EndContainer(),
918 NWidget(NWID_VSCROLLBAR, COLOUR_LIGHT_BLUE, WID_NG_SCROLLBAR),
919 EndContainer(),
920 NWidget(NWID_VERTICAL),
921 NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, WID_NG_LASTJOINED_LABEL), SetFill(1, 0),
922 SetDataTip(STR_NETWORK_SERVER_LIST_LAST_JOINED_SERVER, STR_NULL), SetResize(1, 0),
923 NWidget(NWID_HORIZONTAL),
924 NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, WID_NG_LASTJOINED), SetFill(1, 0), SetResize(1, 0),
925 SetDataTip(0x0, STR_NETWORK_SERVER_LIST_CLICK_TO_SELECT_LAST),
926 EndContainer(),
927 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_NG_LASTJOINED_SPACER), SetFill(0, 0),
928 EndContainer(),
929 EndContainer(),
930 EndContainer(),
931 /* RIGHT SIDE */
932 NWidget(NWID_VERTICAL), SetPIP(0, 7, 0),
933 NWidget(NWID_HORIZONTAL), SetPIP(0, 7, 0),
934 NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, WID_NG_CLIENT_LABEL), SetDataTip(STR_NETWORK_SERVER_LIST_PLAYER_NAME, STR_NULL),
935 NWidget(WWT_EDITBOX, COLOUR_LIGHT_BLUE, WID_NG_CLIENT), SetMinimalSize(151, 12), SetFill(1, 0), SetResize(1, 0),
936 SetDataTip(STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE, STR_NETWORK_SERVER_LIST_ENTER_NAME_TOOLTIP),
937 EndContainer(),
938 NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, WID_NG_DETAILS),
939 NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(5, 5, 5),
940 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_NG_DETAILS_SPACER), SetMinimalSize(140, 0), SetMinimalTextLines(15, 24 + WD_PAR_VSEP_NORMAL), SetResize(0, 1), SetFill(1, 1), // Make sure it's at least this wide
941 NWidget(NWID_HORIZONTAL, NC_NONE), SetPIP(5, 5, 5),
942 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_NG_NEWGRF_MISSING_SEL),
943 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_NEWGRF_MISSING), SetFill(1, 0), SetDataTip(STR_NEWGRF_SETTINGS_FIND_MISSING_CONTENT_BUTTON, STR_NEWGRF_SETTINGS_FIND_MISSING_CONTENT_TOOLTIP),
944 NWidget(NWID_SPACER), SetFill(1, 0),
945 EndContainer(),
946 EndContainer(),
947 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(5, 5, 5),
948 NWidget(NWID_SPACER), SetFill(1, 0),
949 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_NG_NEWGRF_SEL),
950 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_NEWGRF), SetFill(1, 0), SetDataTip(STR_INTRO_NEWGRF_SETTINGS, STR_NULL),
951 NWidget(NWID_SPACER), SetFill(1, 0),
952 EndContainer(),
953 EndContainer(),
954 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(5, 5, 5),
955 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_JOIN), SetFill(1, 0), SetDataTip(STR_NETWORK_SERVER_LIST_JOIN_GAME, STR_NULL),
956 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_REFRESH), SetFill(1, 0), SetDataTip(STR_NETWORK_SERVER_LIST_REFRESH, STR_NETWORK_SERVER_LIST_REFRESH_TOOLTIP),
957 EndContainer(),
958 EndContainer(),
959 EndContainer(),
960 EndContainer(),
961 EndContainer(),
962 /* BOTTOM */
963 NWidget(NWID_HORIZONTAL),
964 NWidget(NWID_VERTICAL),
965 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 7, 4),
966 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_SEARCH_INTERNET), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_NETWORK_SERVER_LIST_SEARCH_SERVER_INTERNET, STR_NETWORK_SERVER_LIST_SEARCH_SERVER_INTERNET_TOOLTIP),
967 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_SEARCH_LAN), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_NETWORK_SERVER_LIST_SEARCH_SERVER_LAN, STR_NETWORK_SERVER_LIST_SEARCH_SERVER_LAN_TOOLTIP),
968 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_ADD), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_NETWORK_SERVER_LIST_ADD_SERVER, STR_NETWORK_SERVER_LIST_ADD_SERVER_TOOLTIP),
969 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_START), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_NETWORK_SERVER_LIST_START_SERVER, STR_NETWORK_SERVER_LIST_START_SERVER_TOOLTIP),
970 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_CANCEL), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_BUTTON_CANCEL, STR_NULL),
971 EndContainer(),
972 NWidget(NWID_SPACER), SetMinimalSize(0, 6), SetResize(1, 0), SetFill(1, 0),
973 EndContainer(),
974 NWidget(NWID_VERTICAL),
975 NWidget(NWID_SPACER), SetFill(0, 1),
976 NWidget(WWT_RESIZEBOX, COLOUR_LIGHT_BLUE),
977 EndContainer(),
978 EndContainer(),
979 EndContainer(),
980 EndContainer(),
983 static WindowDesc _network_game_window_desc(
984 WDP_CENTER, "list_servers", 1000, 730,
985 WC_NETWORK_WINDOW, WC_NONE,
987 _nested_network_game_widgets, lengthof(_nested_network_game_widgets)
990 void ShowNetworkGameWindow()
992 static bool first = true;
993 CloseWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_LOBBY);
994 CloseWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_START);
996 /* Only show once */
997 if (first) {
998 first = false;
999 /* Add all servers from the config file to our list. */
1000 for (const auto &iter : _network_host_list) {
1001 NetworkAddServer(iter);
1005 new NetworkGameWindow(&_network_game_window_desc);
1008 struct NetworkStartServerWindow : public Window {
1009 byte widget_id; ///< The widget that has the pop-up input menu
1010 QueryString name_editbox; ///< Server name editbox.
1012 NetworkStartServerWindow(WindowDesc *desc) : Window(desc), name_editbox(NETWORK_NAME_LENGTH)
1014 this->InitNested(WN_NETWORK_WINDOW_START);
1016 this->querystrings[WID_NSS_GAMENAME] = &this->name_editbox;
1017 this->name_editbox.text.Assign(_settings_client.network.server_name.c_str());
1019 this->SetFocusedWidget(WID_NSS_GAMENAME);
1022 void SetStringParameters(int widget) const override
1024 switch (widget) {
1025 case WID_NSS_CONNTYPE_BTN:
1026 SetDParam(0, STR_NETWORK_SERVER_VISIBILITY_LOCAL + _settings_client.network.server_game_type);
1027 break;
1029 case WID_NSS_CLIENTS_TXT:
1030 SetDParam(0, _settings_client.network.max_clients);
1031 break;
1033 case WID_NSS_COMPANIES_TXT:
1034 SetDParam(0, _settings_client.network.max_companies);
1035 break;
1039 void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
1041 switch (widget) {
1042 case WID_NSS_CONNTYPE_BTN:
1043 *size = maxdim(maxdim(GetStringBoundingBox(STR_NETWORK_SERVER_VISIBILITY_LOCAL), GetStringBoundingBox(STR_NETWORK_SERVER_VISIBILITY_PUBLIC)), GetStringBoundingBox(STR_NETWORK_SERVER_VISIBILITY_INVITE_ONLY));
1044 size->width += padding.width;
1045 size->height += padding.height;
1046 break;
1050 void DrawWidget(const Rect &r, int widget) const override
1052 switch (widget) {
1053 case WID_NSS_SETPWD:
1054 /* If password is set, draw red '*' next to 'Set password' button. */
1055 if (!_settings_client.network.server_password.empty()) DrawString(r.right + WD_FRAMERECT_LEFT, this->width - WD_FRAMERECT_RIGHT, r.top, "*", TC_RED);
1059 void OnClick(Point pt, int widget, int click_count) override
1061 switch (widget) {
1062 case WID_NSS_CANCEL: // Cancel button
1063 ShowNetworkGameWindow();
1064 break;
1066 case WID_NSS_SETPWD: // Set password button
1067 this->widget_id = WID_NSS_SETPWD;
1068 SetDParamStr(0, _settings_client.network.server_password);
1069 ShowQueryString(STR_JUST_RAW_STRING, STR_NETWORK_START_SERVER_SET_PASSWORD, 20, this, CS_ALPHANUMERAL, QSF_NONE);
1070 break;
1072 case WID_NSS_CONNTYPE_BTN: // Connection type
1073 ShowDropDownList(this, BuildVisibilityDropDownList(), _settings_client.network.server_game_type, WID_NSS_CONNTYPE_BTN);
1074 break;
1076 case WID_NSS_CLIENTS_BTND: case WID_NSS_CLIENTS_BTNU: // Click on up/down button for number of clients
1077 case WID_NSS_COMPANIES_BTND: case WID_NSS_COMPANIES_BTNU: // Click on up/down button for number of companies
1078 /* Don't allow too fast scrolling. */
1079 if (!(this->flags & WF_TIMEOUT) || this->timeout_timer <= 1) {
1080 this->HandleButtonClick(widget);
1081 this->SetDirty();
1082 switch (widget) {
1083 default: NOT_REACHED();
1084 case WID_NSS_CLIENTS_BTND: case WID_NSS_CLIENTS_BTNU:
1085 _settings_client.network.max_clients = Clamp(_settings_client.network.max_clients + widget - WID_NSS_CLIENTS_TXT, 2, MAX_CLIENTS);
1086 break;
1087 case WID_NSS_COMPANIES_BTND: case WID_NSS_COMPANIES_BTNU:
1088 _settings_client.network.max_companies = Clamp(_settings_client.network.max_companies + widget - WID_NSS_COMPANIES_TXT, 1, MAX_COMPANIES);
1089 break;
1092 _left_button_clicked = false;
1093 break;
1095 case WID_NSS_CLIENTS_TXT: // Click on number of clients
1096 this->widget_id = WID_NSS_CLIENTS_TXT;
1097 SetDParam(0, _settings_client.network.max_clients);
1098 ShowQueryString(STR_JUST_INT, STR_NETWORK_START_SERVER_NUMBER_OF_CLIENTS, 4, this, CS_NUMERAL, QSF_NONE);
1099 break;
1101 case WID_NSS_COMPANIES_TXT: // Click on number of companies
1102 this->widget_id = WID_NSS_COMPANIES_TXT;
1103 SetDParam(0, _settings_client.network.max_companies);
1104 ShowQueryString(STR_JUST_INT, STR_NETWORK_START_SERVER_NUMBER_OF_COMPANIES, 3, this, CS_NUMERAL, QSF_NONE);
1105 break;
1107 case WID_NSS_GENERATE_GAME: // Start game
1108 if (!CheckServerName()) return;
1109 _is_network_server = true;
1110 if (_ctrl_pressed) {
1111 StartNewGameWithoutGUI(GENERATE_NEW_SEED);
1112 } else {
1113 ShowGenerateLandscape();
1115 break;
1117 case WID_NSS_LOAD_GAME:
1118 if (!CheckServerName()) return;
1119 _is_network_server = true;
1120 ShowSaveLoadDialog(FT_SAVEGAME, SLO_LOAD);
1121 break;
1123 case WID_NSS_PLAY_SCENARIO:
1124 if (!CheckServerName()) return;
1125 _is_network_server = true;
1126 ShowSaveLoadDialog(FT_SCENARIO, SLO_LOAD);
1127 break;
1129 case WID_NSS_PLAY_HEIGHTMAP:
1130 if (!CheckServerName()) return;
1131 _is_network_server = true;
1132 ShowSaveLoadDialog(FT_HEIGHTMAP,SLO_LOAD);
1133 break;
1137 void OnDropdownSelect(int widget, int index) override
1139 switch (widget) {
1140 case WID_NSS_CONNTYPE_BTN:
1141 _settings_client.network.server_game_type = (ServerGameType)index;
1142 break;
1143 default:
1144 NOT_REACHED();
1147 this->SetDirty();
1150 bool CheckServerName()
1152 std::string str = this->name_editbox.text.buf;
1153 if (!NetworkValidateServerName(str)) return false;
1155 SetSettingValue(GetSettingFromName("network.server_name")->AsStringSetting(), str);
1156 return true;
1159 void OnTimeout() override
1161 static const int raise_widgets[] = {WID_NSS_CLIENTS_BTND, WID_NSS_CLIENTS_BTNU, WID_NSS_COMPANIES_BTND, WID_NSS_COMPANIES_BTNU, WIDGET_LIST_END};
1162 for (const int *widget = raise_widgets; *widget != WIDGET_LIST_END; widget++) {
1163 if (this->IsWidgetLowered(*widget)) {
1164 this->RaiseWidget(*widget);
1165 this->SetWidgetDirty(*widget);
1170 void OnQueryTextFinished(char *str) override
1172 if (str == nullptr) return;
1174 if (this->widget_id == WID_NSS_SETPWD) {
1175 _settings_client.network.server_password = str;
1176 } else {
1177 int32 value = atoi(str);
1178 this->SetWidgetDirty(this->widget_id);
1179 switch (this->widget_id) {
1180 default: NOT_REACHED();
1181 case WID_NSS_CLIENTS_TXT: _settings_client.network.max_clients = Clamp(value, 2, MAX_CLIENTS); break;
1182 case WID_NSS_COMPANIES_TXT: _settings_client.network.max_companies = Clamp(value, 1, MAX_COMPANIES); break;
1186 this->SetDirty();
1190 static const NWidgetPart _nested_network_start_server_window_widgets[] = {
1191 NWidget(NWID_HORIZONTAL),
1192 NWidget(WWT_CLOSEBOX, COLOUR_LIGHT_BLUE),
1193 NWidget(WWT_CAPTION, COLOUR_LIGHT_BLUE), SetDataTip(STR_NETWORK_START_SERVER_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1194 EndContainer(),
1195 NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, WID_NSS_BACKGROUND),
1196 NWidget(NWID_VERTICAL), SetPIP(10, 6, 10),
1197 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 6, 10),
1198 NWidget(NWID_VERTICAL), SetPIP(0, 1, 0),
1199 /* Game name widgets */
1200 NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, WID_NSS_GAMENAME_LABEL), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_NEW_GAME_NAME, STR_NULL),
1201 NWidget(WWT_EDITBOX, COLOUR_LIGHT_BLUE, WID_NSS_GAMENAME), SetMinimalSize(10, 12), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_NEW_GAME_NAME_OSKTITLE, STR_NETWORK_START_SERVER_NEW_GAME_NAME_TOOLTIP),
1202 EndContainer(),
1203 EndContainer(),
1205 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 6, 10),
1206 NWidget(NWID_VERTICAL), SetPIP(0, 1, 0),
1207 NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, WID_NSS_CONNTYPE_LABEL), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_VISIBILITY_LABEL, STR_NULL),
1208 NWidget(WWT_DROPDOWN, COLOUR_LIGHT_BLUE, WID_NSS_CONNTYPE_BTN), SetFill(1, 0), SetDataTip(STR_BLACK_STRING, STR_NETWORK_START_SERVER_VISIBILITY_TOOLTIP),
1209 EndContainer(),
1210 NWidget(NWID_VERTICAL), SetPIP(0, 1, 0),
1211 NWidget(NWID_SPACER), SetFill(1, 1),
1212 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NSS_SETPWD), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_SET_PASSWORD, STR_NETWORK_START_SERVER_PASSWORD_TOOLTIP),
1213 EndContainer(),
1214 EndContainer(),
1216 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 6, 10),
1217 NWidget(NWID_VERTICAL), SetPIP(0, 1, 0),
1218 NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, WID_NSS_CLIENTS_LABEL), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_NUMBER_OF_CLIENTS, STR_NULL),
1219 NWidget(NWID_HORIZONTAL),
1220 NWidget(WWT_IMGBTN, COLOUR_LIGHT_BLUE, WID_NSS_CLIENTS_BTND), SetMinimalSize(12, 12), SetFill(0, 1), SetDataTip(SPR_ARROW_DOWN, STR_NETWORK_START_SERVER_NUMBER_OF_CLIENTS_TOOLTIP),
1221 NWidget(WWT_PUSHTXTBTN, COLOUR_LIGHT_BLUE, WID_NSS_CLIENTS_TXT), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_CLIENTS_SELECT, STR_NETWORK_START_SERVER_NUMBER_OF_CLIENTS_TOOLTIP),
1222 NWidget(WWT_IMGBTN, COLOUR_LIGHT_BLUE, WID_NSS_CLIENTS_BTNU), SetMinimalSize(12, 12), SetFill(0, 1), SetDataTip(SPR_ARROW_UP, STR_NETWORK_START_SERVER_NUMBER_OF_CLIENTS_TOOLTIP),
1223 EndContainer(),
1224 EndContainer(),
1226 NWidget(NWID_VERTICAL), SetPIP(0, 1, 0),
1227 NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, WID_NSS_COMPANIES_LABEL), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_NUMBER_OF_COMPANIES, STR_NULL),
1228 NWidget(NWID_HORIZONTAL),
1229 NWidget(WWT_IMGBTN, COLOUR_LIGHT_BLUE, WID_NSS_COMPANIES_BTND), SetMinimalSize(12, 12), SetFill(0, 1), SetDataTip(SPR_ARROW_DOWN, STR_NETWORK_START_SERVER_NUMBER_OF_COMPANIES_TOOLTIP),
1230 NWidget(WWT_PUSHTXTBTN, COLOUR_LIGHT_BLUE, WID_NSS_COMPANIES_TXT), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_COMPANIES_SELECT, STR_NETWORK_START_SERVER_NUMBER_OF_COMPANIES_TOOLTIP),
1231 NWidget(WWT_IMGBTN, COLOUR_LIGHT_BLUE, WID_NSS_COMPANIES_BTNU), SetMinimalSize(12, 12), SetFill(0, 1), SetDataTip(SPR_ARROW_UP, STR_NETWORK_START_SERVER_NUMBER_OF_COMPANIES_TOOLTIP),
1232 EndContainer(),
1233 EndContainer(),
1234 EndContainer(),
1236 /* 'generate game' and 'load game' buttons */
1237 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 6, 10),
1238 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NSS_GENERATE_GAME), SetDataTip(STR_INTRO_NEW_GAME, STR_INTRO_TOOLTIP_NEW_GAME), SetFill(1, 0),
1239 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NSS_LOAD_GAME), SetDataTip(STR_INTRO_LOAD_GAME, STR_INTRO_TOOLTIP_LOAD_GAME), SetFill(1, 0),
1240 EndContainer(),
1242 /* 'play scenario' and 'play heightmap' buttons */
1243 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 6, 10),
1244 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NSS_PLAY_SCENARIO), SetDataTip(STR_INTRO_PLAY_SCENARIO, STR_INTRO_TOOLTIP_PLAY_SCENARIO), SetFill(1, 0),
1245 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NSS_PLAY_HEIGHTMAP), SetDataTip(STR_INTRO_PLAY_HEIGHTMAP, STR_INTRO_TOOLTIP_PLAY_HEIGHTMAP), SetFill(1, 0),
1246 EndContainer(),
1248 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 0, 10),
1249 NWidget(NWID_SPACER), SetFill(1, 0),
1250 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NSS_CANCEL), SetDataTip(STR_BUTTON_CANCEL, STR_NULL), SetMinimalSize(128, 12),
1251 NWidget(NWID_SPACER), SetFill(1, 0),
1252 EndContainer(),
1253 EndContainer(),
1254 EndContainer(),
1257 static WindowDesc _network_start_server_window_desc(
1258 WDP_CENTER, nullptr, 0, 0,
1259 WC_NETWORK_WINDOW, WC_NONE,
1261 _nested_network_start_server_window_widgets, lengthof(_nested_network_start_server_window_widgets)
1264 static void ShowNetworkStartServerWindow()
1266 if (!NetworkValidateOurClientName()) return;
1268 CloseWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_GAME);
1269 CloseWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_LOBBY);
1271 new NetworkStartServerWindow(&_network_start_server_window_desc);
1274 struct NetworkLobbyWindow : public Window {
1275 CompanyID company; ///< Selected company
1276 NetworkGameList *server; ///< Selected server
1277 NetworkCompanyInfo company_info[MAX_COMPANIES];
1278 Scrollbar *vscroll;
1280 NetworkLobbyWindow(WindowDesc *desc, NetworkGameList *ngl) :
1281 Window(desc), company(INVALID_COMPANY), server(ngl)
1283 this->CreateNestedTree();
1284 this->vscroll = this->GetScrollbar(WID_NL_SCROLLBAR);
1285 this->FinishInitNested(WN_NETWORK_WINDOW_LOBBY);
1288 CompanyID NetworkLobbyFindCompanyIndex(byte pos) const
1290 /* Scroll through all this->company_info and get the 'pos' item that is not empty. */
1291 for (CompanyID i = COMPANY_FIRST; i < MAX_COMPANIES; i++) {
1292 if (!this->company_info[i].company_name.empty()) {
1293 if (pos-- == 0) return i;
1297 return COMPANY_FIRST;
1300 void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
1302 switch (widget) {
1303 case WID_NL_HEADER:
1304 size->height = WD_MATRIX_TOP + FONT_HEIGHT_NORMAL + WD_MATRIX_BOTTOM;
1305 break;
1307 case WID_NL_MATRIX:
1308 resize->height = WD_MATRIX_TOP + std::max<uint>(std::max(GetSpriteSize(SPR_LOCK).height, GetSpriteSize(SPR_PROFIT_LOT).height), FONT_HEIGHT_NORMAL) + WD_MATRIX_BOTTOM;
1309 size->height = 10 * resize->height;
1310 break;
1312 case WID_NL_DETAILS:
1313 size->height = 30 + 11 * FONT_HEIGHT_NORMAL;
1314 break;
1318 void SetStringParameters(int widget) const override
1320 switch (widget) {
1321 case WID_NL_TEXT:
1322 SetDParamStr(0, this->server->info.server_name);
1323 break;
1327 void DrawWidget(const Rect &r, int widget) const override
1329 switch (widget) {
1330 case WID_NL_DETAILS:
1331 this->DrawDetails(r);
1332 break;
1334 case WID_NL_MATRIX:
1335 this->DrawMatrix(r);
1336 break;
1340 void OnPaint() override
1342 const NetworkGameInfo *gi = &this->server->info;
1344 /* Join button is disabled when no company is selected and for AI companies. */
1345 this->SetWidgetDisabledState(WID_NL_JOIN, this->company == INVALID_COMPANY || GetLobbyCompanyInfo(this->company)->ai);
1346 /* Cannot start new company if there are too many. */
1347 this->SetWidgetDisabledState(WID_NL_NEW, gi->companies_on >= gi->companies_max);
1349 this->vscroll->SetCount(gi->companies_on);
1351 /* Draw window widgets */
1352 this->DrawWidgets();
1355 void DrawMatrix(const Rect &r) const
1357 bool rtl = _current_text_dir == TD_RTL;
1358 uint left = r.left + WD_FRAMERECT_LEFT;
1359 uint right = r.right - WD_FRAMERECT_RIGHT;
1360 uint text_offset = (this->resize.step_height - WD_MATRIX_TOP - WD_MATRIX_BOTTOM - FONT_HEIGHT_NORMAL) / 2 + WD_MATRIX_TOP;
1362 Dimension lock_size = GetSpriteSize(SPR_LOCK);
1363 int lock_width = lock_size.width;
1364 int lock_y_offset = (this->resize.step_height - WD_MATRIX_TOP - WD_MATRIX_BOTTOM - lock_size.height) / 2 + WD_MATRIX_TOP;
1366 Dimension profit_size = GetSpriteSize(SPR_PROFIT_LOT);
1367 int profit_width = lock_size.width;
1368 int profit_y_offset = (this->resize.step_height - WD_MATRIX_TOP - WD_MATRIX_BOTTOM - profit_size.height) / 2 + WD_MATRIX_TOP;
1370 uint text_left = left + (rtl ? lock_width + profit_width + 4 : 0);
1371 uint text_right = right - (rtl ? 0 : lock_width + profit_width + 4);
1372 uint profit_left = rtl ? left : right - profit_width;
1373 uint lock_left = rtl ? left + profit_width + 2 : right - profit_width - lock_width - 2;
1375 int y = r.top;
1376 /* Draw company list */
1377 int pos = this->vscroll->GetPosition();
1378 while (pos < this->server->info.companies_on) {
1379 byte company = NetworkLobbyFindCompanyIndex(pos);
1380 bool income = false;
1381 if (this->company == company) {
1382 GfxFillRect(r.left + WD_BEVEL_LEFT, y + 1, r.right - WD_BEVEL_RIGHT, y + this->resize.step_height - 2, PC_GREY); // show highlighted item with a different colour
1385 DrawString(text_left, text_right, y + text_offset, this->company_info[company].company_name, TC_BLACK);
1386 if (this->company_info[company].use_password != 0) DrawSprite(SPR_LOCK, PAL_NONE, lock_left, y + lock_y_offset);
1388 /* If the company's income was positive puts a green dot else a red dot */
1389 if (this->company_info[company].income >= 0) income = true;
1390 DrawSprite(income ? SPR_PROFIT_LOT : SPR_PROFIT_NEGATIVE, PAL_NONE, profit_left, y + profit_y_offset);
1392 pos++;
1393 y += this->resize.step_height;
1394 if (pos >= this->vscroll->GetPosition() + this->vscroll->GetCapacity()) break;
1398 void DrawDetails(const Rect &r) const
1400 const int detail_height = 12 + FONT_HEIGHT_NORMAL + 12;
1401 /* Draw info about selected company when it is selected in the left window. */
1402 GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.top + detail_height - 1, PC_DARK_BLUE);
1403 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + 12, STR_NETWORK_GAME_LOBBY_COMPANY_INFO, TC_FROMSTRING, SA_HOR_CENTER);
1405 if (this->company == INVALID_COMPANY || this->company_info[this->company].company_name.empty()) return;
1407 int y = r.top + detail_height + 4;
1408 const NetworkGameInfo *gi = &this->server->info;
1410 SetDParam(0, gi->clients_on);
1411 SetDParam(1, gi->clients_max);
1412 SetDParam(2, gi->companies_on);
1413 SetDParam(3, gi->companies_max);
1414 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_CLIENTS);
1415 y += FONT_HEIGHT_NORMAL;
1417 SetDParamStr(0, this->company_info[this->company].company_name);
1418 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_COMPANY_NAME);
1419 y += FONT_HEIGHT_NORMAL;
1421 SetDParam(0, this->company_info[this->company].inaugurated_year);
1422 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_INAUGURATION_YEAR); // inauguration year
1423 y += FONT_HEIGHT_NORMAL;
1425 SetDParam(0, this->company_info[this->company].company_value);
1426 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_VALUE); // company value
1427 y += FONT_HEIGHT_NORMAL;
1429 SetDParam(0, this->company_info[this->company].money);
1430 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_CURRENT_BALANCE); // current balance
1431 y += FONT_HEIGHT_NORMAL;
1433 SetDParam(0, this->company_info[this->company].income);
1434 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_LAST_YEARS_INCOME); // last year's income
1435 y += FONT_HEIGHT_NORMAL;
1437 SetDParam(0, this->company_info[this->company].performance);
1438 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_PERFORMANCE); // performance
1439 y += FONT_HEIGHT_NORMAL;
1441 SetDParam(0, this->company_info[this->company].num_vehicle[NETWORK_VEH_TRAIN]);
1442 SetDParam(1, this->company_info[this->company].num_vehicle[NETWORK_VEH_LORRY]);
1443 SetDParam(2, this->company_info[this->company].num_vehicle[NETWORK_VEH_BUS]);
1444 SetDParam(3, this->company_info[this->company].num_vehicle[NETWORK_VEH_SHIP]);
1445 SetDParam(4, this->company_info[this->company].num_vehicle[NETWORK_VEH_PLANE]);
1446 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_VEHICLES); // vehicles
1447 y += FONT_HEIGHT_NORMAL;
1449 SetDParam(0, this->company_info[this->company].num_station[NETWORK_VEH_TRAIN]);
1450 SetDParam(1, this->company_info[this->company].num_station[NETWORK_VEH_LORRY]);
1451 SetDParam(2, this->company_info[this->company].num_station[NETWORK_VEH_BUS]);
1452 SetDParam(3, this->company_info[this->company].num_station[NETWORK_VEH_SHIP]);
1453 SetDParam(4, this->company_info[this->company].num_station[NETWORK_VEH_PLANE]);
1454 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_STATIONS); // stations
1455 y += FONT_HEIGHT_NORMAL;
1457 SetDParamStr(0, this->company_info[this->company].clients);
1458 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_PLAYERS); // players
1461 void OnClick(Point pt, int widget, int click_count) override
1463 switch (widget) {
1464 case WID_NL_CANCEL: // Cancel button
1465 ShowNetworkGameWindow();
1466 break;
1468 case WID_NL_MATRIX: { // Company list
1469 uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NL_MATRIX);
1470 this->company = (id_v >= this->server->info.companies_on) ? INVALID_COMPANY : NetworkLobbyFindCompanyIndex(id_v);
1471 this->SetDirty();
1473 /* FIXME the disabling should go into some InvalidateData, which is called instead of the SetDirty */
1474 if (click_count > 1 && !this->IsWidgetDisabled(WID_NL_JOIN)) this->OnClick(pt, WID_NL_JOIN, 1);
1475 break;
1478 case WID_NL_JOIN: // Join company
1479 /* Button can be clicked only when it is enabled. */
1480 NetworkClientConnectGame(this->server->connection_string, this->company);
1481 break;
1483 case WID_NL_NEW: // New company
1484 NetworkClientConnectGame(this->server->connection_string, COMPANY_NEW_COMPANY);
1485 break;
1487 case WID_NL_SPECTATE: // Spectate game
1488 NetworkClientConnectGame(this->server->connection_string, COMPANY_SPECTATOR);
1489 break;
1491 case WID_NL_REFRESH: // Refresh
1492 /* Clear the information so removed companies don't remain */
1493 for (auto &company : this->company_info) company = {};
1495 NetworkQueryLobbyServer(this->server->connection_string);
1496 break;
1500 void OnResize() override
1502 this->vscroll->SetCapacityFromWidget(this, WID_NL_MATRIX);
1506 static const NWidgetPart _nested_network_lobby_window_widgets[] = {
1507 NWidget(NWID_HORIZONTAL),
1508 NWidget(WWT_CLOSEBOX, COLOUR_LIGHT_BLUE),
1509 NWidget(WWT_CAPTION, COLOUR_LIGHT_BLUE), SetDataTip(STR_NETWORK_GAME_LOBBY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1510 EndContainer(),
1511 NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, WID_NL_BACKGROUND),
1512 NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, WID_NL_TEXT), SetDataTip(STR_NETWORK_GAME_LOBBY_PREPARE_TO_JOIN, STR_NULL), SetResize(1, 0), SetPadding(10, 10, 0, 10),
1513 NWidget(NWID_SPACER), SetMinimalSize(0, 3),
1514 NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 10),
1515 /* Company list. */
1516 NWidget(NWID_VERTICAL),
1517 NWidget(WWT_PANEL, COLOUR_WHITE, WID_NL_HEADER), SetMinimalSize(146, 0), SetResize(1, 0), SetFill(1, 0), EndContainer(),
1518 NWidget(WWT_MATRIX, COLOUR_LIGHT_BLUE, WID_NL_MATRIX), SetMinimalSize(146, 0), SetResize(1, 1), SetFill(1, 1), SetMatrixDataTip(1, 0, STR_NETWORK_GAME_LOBBY_COMPANY_LIST_TOOLTIP), SetScrollbar(WID_NL_SCROLLBAR),
1519 EndContainer(),
1520 NWidget(NWID_VSCROLLBAR, COLOUR_LIGHT_BLUE, WID_NL_SCROLLBAR),
1521 NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetResize(0, 1),
1522 /* Company info. */
1523 NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, WID_NL_DETAILS), SetMinimalSize(232, 0), SetResize(1, 1), SetFill(1, 1), EndContainer(),
1524 EndContainer(),
1525 NWidget(NWID_SPACER), SetMinimalSize(0, 9),
1526 /* Buttons. */
1527 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 3, 10),
1528 NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, 3, 0),
1529 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NL_JOIN), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_NETWORK_GAME_LOBBY_JOIN_COMPANY, STR_NETWORK_GAME_LOBBY_JOIN_COMPANY_TOOLTIP),
1530 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NL_NEW), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_NETWORK_GAME_LOBBY_NEW_COMPANY, STR_NETWORK_GAME_LOBBY_NEW_COMPANY_TOOLTIP),
1531 EndContainer(),
1532 NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, 3, 0),
1533 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NL_SPECTATE), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_NETWORK_GAME_LOBBY_SPECTATE_GAME, STR_NETWORK_GAME_LOBBY_SPECTATE_GAME_TOOLTIP),
1534 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NL_REFRESH), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_NETWORK_SERVER_LIST_REFRESH, STR_NETWORK_SERVER_LIST_REFRESH_TOOLTIP),
1535 EndContainer(),
1536 NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, 3, 0),
1537 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NL_CANCEL), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_BUTTON_CANCEL, STR_NULL),
1538 NWidget(NWID_SPACER), SetFill(1, 1),
1539 EndContainer(),
1540 EndContainer(),
1541 NWidget(NWID_SPACER), SetMinimalSize(0, 8),
1542 EndContainer(),
1545 static WindowDesc _network_lobby_window_desc(
1546 WDP_CENTER, nullptr, 0, 0,
1547 WC_NETWORK_WINDOW, WC_NONE,
1549 _nested_network_lobby_window_widgets, lengthof(_nested_network_lobby_window_widgets)
1553 * Show the networklobbywindow with the selected server.
1554 * @param ngl Selected game pointer which is passed to the new window.
1556 static void ShowNetworkLobbyWindow(NetworkGameList *ngl)
1558 if (!NetworkValidateOurClientName()) return;
1560 CloseWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_START);
1561 CloseWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_GAME);
1563 _settings_client.network.last_joined = ngl->connection_string;
1565 NetworkQueryLobbyServer(ngl->connection_string);
1567 new NetworkLobbyWindow(&_network_lobby_window_desc, ngl);
1571 * Get the company information of a given company to fill for the lobby.
1572 * @param company the company to get the company info struct from.
1573 * @return the company info struct to write the (downloaded) data to.
1575 NetworkCompanyInfo *GetLobbyCompanyInfo(CompanyID company)
1577 NetworkLobbyWindow *lobby = dynamic_cast<NetworkLobbyWindow*>(FindWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_LOBBY));
1578 return (lobby != nullptr && company < MAX_COMPANIES) ? &lobby->company_info[company] : nullptr;
1582 * Get the game information for the lobby.
1583 * @return the game info struct to write the (downloaded) data to.
1585 NetworkGameList *GetLobbyGameInfo()
1587 NetworkLobbyWindow *lobby = dynamic_cast<NetworkLobbyWindow *>(FindWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_LOBBY));
1588 return lobby != nullptr ? lobby->server : nullptr;
1591 /* The window below gives information about the connected clients
1592 * and also makes able to kick them (if server) and stuff like that. */
1594 extern void DrawCompanyIcon(CompanyID cid, int x, int y);
1596 static const NWidgetPart _nested_client_list_widgets[] = {
1597 NWidget(NWID_HORIZONTAL),
1598 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
1599 NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_NETWORK_CLIENT_LIST_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1600 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
1601 NWidget(WWT_STICKYBOX, COLOUR_GREY),
1602 EndContainer(),
1603 NWidget(WWT_PANEL, COLOUR_GREY),
1604 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_CL_SERVER_SELECTOR),
1605 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER, STR_NULL), SetPadding(4, 4, 0, 4), SetPIP(0, 2, 0),
1606 NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0),
1607 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER_NAME, STR_NULL),
1608 NWidget(NWID_SPACER), SetMinimalSize(10, 0),
1609 NWidget(WWT_TEXT, COLOUR_GREY, WID_CL_SERVER_NAME), SetFill(1, 0), SetMinimalTextLines(1, 0), SetResize(1, 0), SetDataTip(STR_BLACK_RAW_STRING, STR_NETWORK_CLIENT_LIST_SERVER_NAME_TOOLTIP), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
1610 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_CL_SERVER_NAME_EDIT), SetMinimalSize(12, 14), SetDataTip(SPR_RENAME, STR_NETWORK_CLIENT_LIST_SERVER_NAME_EDIT_TOOLTIP),
1611 EndContainer(),
1612 NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0),
1613 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY, STR_NULL),
1614 NWidget(NWID_SPACER), SetMinimalSize(10, 0), SetFill(1, 0), SetResize(1, 0),
1615 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_CL_SERVER_VISIBILITY), SetDataTip(STR_BLACK_STRING, STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY_TOOLTIP),
1616 EndContainer(),
1617 NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0),
1618 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE, STR_NULL),
1619 NWidget(NWID_SPACER), SetMinimalSize(10, 0),
1620 NWidget(WWT_TEXT, COLOUR_GREY, WID_CL_SERVER_INVITE_CODE), SetFill(1, 0), SetMinimalTextLines(1, 0), SetResize(1, 0), SetDataTip(STR_BLACK_RAW_STRING, STR_NETWORK_CLIENT_LIST_SERVER_INVITE_CODE_TOOLTIP), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
1621 EndContainer(),
1622 NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0),
1623 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE, STR_NULL),
1624 NWidget(NWID_SPACER), SetMinimalSize(10, 0),
1625 NWidget(WWT_TEXT, COLOUR_GREY, WID_CL_SERVER_CONNECTION_TYPE), SetFill(1, 0), SetMinimalTextLines(1, 0), SetResize(1, 0), SetDataTip(STR_BLACK_STRING, STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_TOOLTIP), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
1626 EndContainer(),
1627 EndContainer(),
1628 EndContainer(),
1629 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_NETWORK_CLIENT_LIST_PLAYER, STR_NULL), SetPadding(4, 4, 4, 4), SetPIP(0, 2, 0),
1630 NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0),
1631 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_PLAYER_NAME, STR_NULL),
1632 NWidget(NWID_SPACER), SetMinimalSize(10, 0),
1633 NWidget(WWT_TEXT, COLOUR_GREY, WID_CL_CLIENT_NAME), SetFill(1, 0), SetMinimalTextLines(1, 0), SetResize(1, 0), SetDataTip(STR_BLACK_RAW_STRING, STR_NETWORK_CLIENT_LIST_PLAYER_NAME_TOOLTIP), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
1634 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_CL_CLIENT_NAME_EDIT), SetMinimalSize(12, 14), SetDataTip(SPR_RENAME, STR_NETWORK_CLIENT_LIST_PLAYER_NAME_EDIT_TOOLTIP),
1635 EndContainer(),
1636 EndContainer(),
1637 NWidget(NWID_HORIZONTAL),
1638 NWidget(NWID_VERTICAL),
1639 NWidget(WWT_MATRIX, COLOUR_GREY, WID_CL_MATRIX), SetMinimalSize(180, 0), SetResize(1, 1), SetFill(1, 1), SetMatrixDataTip(1, 0, STR_NULL), SetScrollbar(WID_CL_SCROLLBAR),
1640 NWidget(WWT_TEXT, COLOUR_GREY, WID_CL_CLIENT_COMPANY_COUNT), SetFill(1, 0), SetMinimalTextLines(1, 0), SetResize(1, 0), SetPadding(2, 1, 2, 1), SetAlignment(SA_CENTER), SetDataTip(STR_NETWORK_CLIENT_LIST_CLIENT_COMPANY_COUNT, STR_NULL),
1641 EndContainer(),
1642 NWidget(NWID_VERTICAL),
1643 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_CL_SCROLLBAR),
1644 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
1645 EndContainer(),
1646 EndContainer(),
1647 EndContainer(),
1650 static WindowDesc _client_list_desc(
1651 WDP_AUTO, "list_clients", 220, 300,
1652 WC_CLIENT_LIST, WC_NONE,
1654 _nested_client_list_widgets, lengthof(_nested_client_list_widgets)
1658 * The possibly entries in a DropDown for an admin.
1659 * Client and companies are mixed; they just have to be unique.
1661 enum DropDownAdmin {
1662 DD_CLIENT_ADMIN_KICK,
1663 DD_CLIENT_ADMIN_BAN,
1664 DD_COMPANY_ADMIN_RESET,
1665 DD_COMPANY_ADMIN_UNLOCK,
1669 * Callback function for admin command to kick client.
1670 * @param w The window which initiated the confirmation dialog.
1671 * @param confirmed Iff the user pressed Yes.
1673 static void AdminClientKickCallback(Window *w, bool confirmed)
1675 if (confirmed) NetworkServerKickClient(_admin_client_id, {});
1679 * Callback function for admin command to ban client.
1680 * @param w The window which initiated the confirmation dialog.
1681 * @param confirmed Iff the user pressed Yes.
1683 static void AdminClientBanCallback(Window *w, bool confirmed)
1685 if (confirmed) NetworkServerKickOrBanIP(_admin_client_id, true, {});
1689 * Callback function for admin command to reset company.
1690 * @param w The window which initiated the confirmation dialog.
1691 * @param confirmed Iff the user pressed Yes.
1693 static void AdminCompanyResetCallback(Window *w, bool confirmed)
1695 if (confirmed) {
1696 if (NetworkCompanyHasClients(_admin_company_id)) return;
1697 DoCommandP(0, CCA_DELETE | _admin_company_id << 16 | CRR_MANUAL << 24, 0, CMD_COMPANY_CTRL);
1702 * Callback function for admin command to unlock company.
1703 * @param w The window which initiated the confirmation dialog.
1704 * @param confirmed Iff the user pressed Yes.
1706 static void AdminCompanyUnlockCallback(Window *w, bool confirmed)
1708 if (confirmed) NetworkServerSetCompanyPassword(_admin_company_id, "", false);
1712 * Button shown for either a company or client in the client-list.
1714 * These buttons are dynamic and strongly depends on which company/client
1715 * what buttons are available. This class allows dynamically creating them
1716 * as the current Widget system does not.
1718 class ButtonCommon {
1719 public:
1720 SpriteID sprite; ///< The sprite to use on the button.
1721 StringID tooltip; ///< The tooltip of the button.
1722 Colours colour; ///< The colour of the button.
1723 bool disabled; ///< Is the button disabled?
1724 uint height; ///< Calculated height of the button.
1725 uint width; ///< Calculated width of the button.
1727 ButtonCommon(SpriteID sprite, StringID tooltip, Colours colour, bool disabled = false) :
1728 sprite(sprite),
1729 tooltip(tooltip),
1730 colour(colour),
1731 disabled(disabled)
1733 Dimension d = GetSpriteSize(sprite);
1734 this->height = d.height + ScaleGUITrad(WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM);
1735 this->width = d.width + ScaleGUITrad(WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT);
1737 virtual ~ButtonCommon() {}
1740 * OnClick handler for when the button is pressed.
1742 virtual void OnClick(struct NetworkClientListWindow *w, Point pt) = 0;
1746 * Template version of Button, with callback support.
1748 template<typename T>
1749 class Button : public ButtonCommon {
1750 private:
1751 typedef void (*ButtonCallback)(struct NetworkClientListWindow *w, Point pt, T id); ///< Callback function to call on click.
1752 T id; ///< ID this button belongs to.
1753 ButtonCallback proc; ///< Callback proc to call when button is pressed.
1755 public:
1756 Button(SpriteID sprite, StringID tooltip, Colours colour, T id, ButtonCallback proc, bool disabled = false) :
1757 ButtonCommon(sprite, tooltip, colour, disabled),
1758 id(id),
1759 proc(proc)
1761 assert(proc != nullptr);
1764 void OnClick(struct NetworkClientListWindow *w, Point pt) override
1766 if (this->disabled) return;
1768 this->proc(w, pt, this->id);
1772 using CompanyButton = Button<CompanyID>;
1773 using ClientButton = Button<ClientID>;
1776 * Main handle for clientlist
1778 struct NetworkClientListWindow : Window {
1779 private:
1780 ClientListWidgets query_widget; ///< During a query this tracks what widget caused the query.
1781 CompanyID join_company; ///< During query for company password, this stores what company we wanted to join.
1783 ClientID dd_client_id; ///< During admin dropdown, track which client this was for.
1784 CompanyID dd_company_id; ///< During admin dropdown, track which company this was for.
1786 Scrollbar *vscroll; ///< Vertical scrollbar of this window.
1787 uint line_height; ///< Current lineheight of each entry in the matrix.
1788 uint line_count; ///< Amount of lines in the matrix.
1789 int hover_index; ///< Index of the current line we are hovering over, or -1 if none.
1790 int player_self_index; ///< The line the current player is on.
1791 int player_host_index; ///< The line the host is on.
1793 std::map<uint, std::vector<std::unique_ptr<ButtonCommon>>> buttons; ///< Per line which buttons are available.
1795 static const int CLIENT_OFFSET_LEFT = 12; ///< Offset of client entries compared to company entries.
1798 * Chat button on a Company is clicked.
1799 * @param w The instance of this window.
1800 * @param pt The point where this button was clicked.
1801 * @param company_id The company this button was assigned to.
1803 static void OnClickCompanyChat(NetworkClientListWindow *w, Point pt, CompanyID company_id)
1805 ShowNetworkChatQueryWindow(DESTTYPE_TEAM, company_id);
1809 * Join button on a Company is clicked.
1810 * @param w The instance of this window.
1811 * @param pt The point where this button was clicked.
1812 * @param company_id The company this button was assigned to.
1814 static void OnClickCompanyJoin(NetworkClientListWindow *w, Point pt, CompanyID company_id)
1816 if (_network_server) {
1817 NetworkServerDoMove(CLIENT_ID_SERVER, company_id);
1818 MarkWholeScreenDirty();
1819 } else if (NetworkCompanyIsPassworded(company_id)) {
1820 w->query_widget = WID_CL_COMPANY_JOIN;
1821 w->join_company = company_id;
1822 ShowQueryString(STR_EMPTY, STR_NETWORK_NEED_COMPANY_PASSWORD_CAPTION, NETWORK_PASSWORD_LENGTH, w, CS_ALPHANUMERAL, QSF_PASSWORD);
1823 } else {
1824 NetworkClientRequestMove(company_id);
1829 * Crete new company button is clicked.
1830 * @param w The instance of this window.
1831 * @param pt The point where this button was clicked.
1832 * @param company_id The company this button was assigned to.
1834 static void OnClickCompanyNew(NetworkClientListWindow *w, Point pt, CompanyID company_id)
1836 if (_network_server) {
1837 DoCommandP(0, CCA_NEW, _network_own_client_id, CMD_COMPANY_CTRL);
1838 } else {
1839 NetworkSendCommand(0, CCA_NEW, 0, CMD_COMPANY_CTRL, nullptr, {}, _local_company);
1844 * Admin button on a Client is clicked.
1845 * @param w The instance of this window.
1846 * @param pt The point where this button was clicked.
1847 * @param client_id The client this button was assigned to.
1849 static void OnClickClientAdmin(NetworkClientListWindow *w, Point pt, ClientID client_id)
1851 DropDownList list;
1852 list.emplace_back(new DropDownListStringItem(STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK, DD_CLIENT_ADMIN_KICK, false));
1853 list.emplace_back(new DropDownListStringItem(STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN, DD_CLIENT_ADMIN_BAN, false));
1855 Rect wi_rect;
1856 wi_rect.left = pt.x;
1857 wi_rect.right = pt.x;
1858 wi_rect.top = pt.y;
1859 wi_rect.bottom = pt.y;
1861 w->dd_client_id = client_id;
1862 ShowDropDownListAt(w, std::move(list), -1, WID_CL_MATRIX, wi_rect, COLOUR_GREY, true, true);
1866 * Admin button on a Company is clicked.
1867 * @param w The instance of this window.
1868 * @param pt The point where this button was clicked.
1869 * @param company_id The company this button was assigned to.
1871 static void OnClickCompanyAdmin(NetworkClientListWindow *w, Point pt, CompanyID company_id)
1873 DropDownList list;
1874 list.emplace_back(new DropDownListStringItem(STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET, DD_COMPANY_ADMIN_RESET, NetworkCompanyHasClients(company_id)));
1875 list.emplace_back(new DropDownListStringItem(STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_UNLOCK, DD_COMPANY_ADMIN_UNLOCK, !NetworkCompanyIsPassworded(company_id)));
1877 Rect wi_rect;
1878 wi_rect.left = pt.x;
1879 wi_rect.right = pt.x;
1880 wi_rect.top = pt.y;
1881 wi_rect.bottom = pt.y;
1883 w->dd_company_id = company_id;
1884 ShowDropDownListAt(w, std::move(list), -1, WID_CL_MATRIX, wi_rect, COLOUR_GREY, true, true);
1887 * Chat button on a Client is clicked.
1888 * @param w The instance of this window.
1889 * @param pt The point where this button was clicked.
1890 * @param client_id The client this button was assigned to.
1892 static void OnClickClientChat(NetworkClientListWindow *w, Point pt, ClientID client_id)
1894 ShowNetworkChatQueryWindow(DESTTYPE_CLIENT, client_id);
1898 * Part of RebuildList() to create the information for a single company.
1899 * @param company_id The company to build the list for.
1900 * @param own_ci The NetworkClientInfo of the client itself.
1902 void RebuildListCompany(CompanyID company_id, const NetworkClientInfo *own_ci)
1904 ButtonCommon *chat_button = new CompanyButton(SPR_CHAT, company_id == COMPANY_SPECTATOR ? STR_NETWORK_CLIENT_LIST_CHAT_SPECTATOR_TOOLTIP : STR_NETWORK_CLIENT_LIST_CHAT_COMPANY_TOOLTIP, COLOUR_ORANGE, company_id, &NetworkClientListWindow::OnClickCompanyChat);
1906 if (_network_server) this->buttons[line_count].emplace_back(new CompanyButton(SPR_ADMIN, STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_TOOLTIP, COLOUR_RED, company_id, &NetworkClientListWindow::OnClickCompanyAdmin, company_id == COMPANY_SPECTATOR));
1907 this->buttons[line_count].emplace_back(chat_button);
1908 if (own_ci->client_playas != company_id) this->buttons[line_count].emplace_back(new CompanyButton(SPR_JOIN, STR_NETWORK_CLIENT_LIST_JOIN_TOOLTIP, COLOUR_ORANGE, company_id, &NetworkClientListWindow::OnClickCompanyJoin, company_id != COMPANY_SPECTATOR && Company::Get(company_id)->is_ai));
1910 this->line_count += 1;
1912 bool has_players = false;
1913 for (const NetworkClientInfo *ci : NetworkClientInfo::Iterate()) {
1914 if (ci->client_playas != company_id) continue;
1915 has_players = true;
1917 if (_network_server) this->buttons[line_count].emplace_back(new ClientButton(SPR_ADMIN, STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_TOOLTIP, COLOUR_RED, ci->client_id, &NetworkClientListWindow::OnClickClientAdmin, _network_own_client_id == ci->client_id));
1918 if (_network_own_client_id != ci->client_id) this->buttons[line_count].emplace_back(new ClientButton(SPR_CHAT, STR_NETWORK_CLIENT_LIST_CHAT_CLIENT_TOOLTIP, COLOUR_ORANGE, ci->client_id, &NetworkClientListWindow::OnClickClientChat));
1920 if (ci->client_id == _network_own_client_id) {
1921 this->player_self_index = this->line_count;
1922 } else if (ci->client_id == CLIENT_ID_SERVER) {
1923 this->player_host_index = this->line_count;
1926 this->line_count += 1;
1929 /* Disable the chat button when there are players in this company. */
1930 chat_button->disabled = !has_players;
1934 * Rebuild the list, meaning: calculate the lines needed and what buttons go on which line.
1936 void RebuildList()
1938 const NetworkClientInfo *own_ci = NetworkClientInfo::GetByClientID(_network_own_client_id);
1940 this->buttons.clear();
1941 this->line_count = 0;
1942 this->player_host_index = -1;
1943 this->player_self_index = -1;
1945 /* As spectator, show a line to create a new company. */
1946 if (own_ci->client_playas == COMPANY_SPECTATOR && !NetworkMaxCompaniesReached()) {
1947 this->buttons[line_count].emplace_back(new CompanyButton(SPR_JOIN, STR_NETWORK_CLIENT_LIST_NEW_COMPANY_TOOLTIP, COLOUR_ORANGE, COMPANY_SPECTATOR, &NetworkClientListWindow::OnClickCompanyNew));
1948 this->line_count += 1;
1951 if (own_ci->client_playas != COMPANY_SPECTATOR) {
1952 this->RebuildListCompany(own_ci->client_playas, own_ci);
1955 /* Companies */
1956 for (const Company *c : Company::Iterate()) {
1957 if (c->index == own_ci->client_playas) continue;
1959 this->RebuildListCompany(c->index, own_ci);
1962 /* Spectators */
1963 this->RebuildListCompany(COMPANY_SPECTATOR, own_ci);
1965 this->vscroll->SetCount(this->line_count);
1969 * Get the button at a specific point on the WID_CL_MATRIX.
1970 * @param pt The point to look for a button.
1971 * @return The button or a nullptr if there was none.
1973 ButtonCommon *GetButtonAtPoint(Point pt)
1975 uint index = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_CL_MATRIX);
1976 NWidgetBase *widget_matrix = this->GetWidget<NWidgetBase>(WID_CL_MATRIX);
1978 bool rtl = _current_text_dir == TD_RTL;
1979 uint x = rtl ? (uint)widget_matrix->pos_x + WD_FRAMERECT_LEFT : widget_matrix->current_x - WD_FRAMERECT_RIGHT;
1981 /* Find the buttons for this row. */
1982 auto button_find = this->buttons.find(index);
1983 if (button_find == this->buttons.end()) return nullptr;
1985 /* Check if we want to display a tooltip for any of the buttons. */
1986 for (auto &button : button_find->second) {
1987 uint left = rtl ? x : x - button->width;
1988 uint right = rtl ? x + button->width : x;
1990 if (IsInsideMM(pt.x, left, right)) {
1991 return button.get();
1994 int width = button->width + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
1995 x += rtl ? width : -width;
1998 return nullptr;
2001 public:
2002 NetworkClientListWindow(WindowDesc *desc, WindowNumber window_number) :
2003 Window(desc),
2004 hover_index(-1),
2005 player_self_index(-1),
2006 player_host_index(-1)
2008 this->CreateNestedTree();
2009 this->vscroll = this->GetScrollbar(WID_CL_SCROLLBAR);
2010 this->OnInvalidateData();
2011 this->FinishInitNested(window_number);
2014 void OnInvalidateData(int data = 0, bool gui_scope = true) override
2016 this->RebuildList();
2018 /* Currently server information is not sync'd to clients, so we cannot show it on clients. */
2019 this->GetWidget<NWidgetStacked>(WID_CL_SERVER_SELECTOR)->SetDisplayedPlane(_network_server ? 0 : SZSP_HORIZONTAL);
2022 void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
2024 switch (widget) {
2025 case WID_CL_SERVER_VISIBILITY:
2026 *size = maxdim(maxdim(GetStringBoundingBox(STR_NETWORK_SERVER_VISIBILITY_LOCAL), GetStringBoundingBox(STR_NETWORK_SERVER_VISIBILITY_PUBLIC)), GetStringBoundingBox(STR_NETWORK_SERVER_VISIBILITY_INVITE_ONLY));
2027 size->width += padding.width;
2028 size->height += padding.height;
2029 break;
2031 case WID_CL_MATRIX: {
2032 uint height = std::max({GetSpriteSize(SPR_COMPANY_ICON).height, GetSpriteSize(SPR_JOIN).height, GetSpriteSize(SPR_ADMIN).height, GetSpriteSize(SPR_CHAT).height});
2033 height += ScaleGUITrad(WD_FRAMERECT_TOP) + ScaleGUITrad(WD_FRAMERECT_BOTTOM);
2034 this->line_height = std::max(height, (uint)FONT_HEIGHT_NORMAL) + ScaleGUITrad(WD_MATRIX_TOP + WD_MATRIX_BOTTOM);
2036 resize->width = 1;
2037 resize->height = this->line_height;
2038 fill->height = this->line_height;
2039 size->height = std::max(size->height, 5 * this->line_height);
2040 break;
2045 void OnResize() override
2047 this->vscroll->SetCapacityFromWidget(this, WID_CL_MATRIX);
2050 void SetStringParameters(int widget) const override
2052 switch (widget) {
2053 case WID_CL_SERVER_NAME:
2054 SetDParamStr(0, _settings_client.network.server_name);
2055 break;
2057 case WID_CL_SERVER_VISIBILITY:
2058 SetDParam(0, STR_NETWORK_SERVER_VISIBILITY_LOCAL + _settings_client.network.server_game_type);
2059 break;
2061 case WID_CL_SERVER_INVITE_CODE: {
2062 static std::string empty = {};
2063 SetDParamStr(0, _network_server_connection_type == CONNECTION_TYPE_UNKNOWN ? empty : _network_server_invite_code);
2064 break;
2067 case WID_CL_SERVER_CONNECTION_TYPE:
2068 SetDParam(0, STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_UNKNOWN + _network_server_connection_type);
2069 break;
2071 case WID_CL_CLIENT_NAME:
2072 SetDParamStr(0, _settings_client.network.client_name);
2073 break;
2075 case WID_CL_CLIENT_COMPANY_COUNT:
2076 SetDParam(0, NetworkClientInfo::GetNumItems());
2077 SetDParam(1, Company::GetNumItems());
2078 break;
2082 void OnClick(Point pt, int widget, int click_count) override
2084 switch (widget) {
2085 case WID_CL_SERVER_NAME_EDIT:
2086 if (!_network_server) break;
2088 this->query_widget = WID_CL_SERVER_NAME_EDIT;
2089 SetDParamStr(0, _settings_client.network.server_name);
2090 ShowQueryString(STR_JUST_RAW_STRING, STR_NETWORK_CLIENT_LIST_SERVER_NAME_QUERY_CAPTION, NETWORK_NAME_LENGTH, this, CS_ALPHANUMERAL, QSF_LEN_IN_CHARS);
2091 break;
2093 case WID_CL_CLIENT_NAME_EDIT:
2094 this->query_widget = WID_CL_CLIENT_NAME_EDIT;
2095 SetDParamStr(0, _settings_client.network.client_name);
2096 ShowQueryString(STR_JUST_RAW_STRING, STR_NETWORK_CLIENT_LIST_PLAYER_NAME_QUERY_CAPTION, NETWORK_CLIENT_NAME_LENGTH, this, CS_ALPHANUMERAL, QSF_LEN_IN_CHARS);
2097 break;
2099 case WID_CL_SERVER_VISIBILITY:
2100 if (!_network_server) break;
2102 ShowDropDownList(this, BuildVisibilityDropDownList(), _settings_client.network.server_game_type, WID_CL_SERVER_VISIBILITY);
2103 break;
2105 case WID_CL_MATRIX: {
2106 ButtonCommon *button = this->GetButtonAtPoint(pt);
2107 if (button == nullptr) break;
2109 button->OnClick(this, pt);
2110 break;
2115 bool OnTooltip(Point pt, int widget, TooltipCloseCondition close_cond) override
2117 switch (widget) {
2118 case WID_CL_MATRIX: {
2119 int index = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_CL_MATRIX);
2121 bool rtl = _current_text_dir == TD_RTL;
2122 NWidgetBase *widget_matrix = this->GetWidget<NWidgetBase>(WID_CL_MATRIX);
2124 Dimension d = GetSpriteSize(SPR_COMPANY_ICON);
2125 uint text_left = widget_matrix->pos_x + (rtl ? (uint)WD_FRAMERECT_LEFT : d.width + 8);
2126 uint text_right = widget_matrix->pos_x + widget_matrix->current_x - (rtl ? d.width + 8 : (uint)WD_FRAMERECT_RIGHT);
2128 Dimension d2 = GetSpriteSize(SPR_PLAYER_SELF);
2129 uint offset_x = CLIENT_OFFSET_LEFT - d2.width - 3;
2131 uint player_icon_x = rtl ? text_right - offset_x - d2.width : text_left + offset_x;
2133 if (IsInsideMM(pt.x, player_icon_x, player_icon_x + d2.width)) {
2134 if (index == this->player_self_index) {
2135 GuiShowTooltips(this, STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP, 0, nullptr, close_cond);
2136 return true;
2137 } else if (index == this->player_host_index) {
2138 GuiShowTooltips(this, STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP, 0, nullptr, close_cond);
2139 return true;
2143 ButtonCommon *button = this->GetButtonAtPoint(pt);
2144 if (button == nullptr) return false;
2146 GuiShowTooltips(this, button->tooltip, 0, nullptr, close_cond);
2147 return true;
2151 return false;
2154 void OnDropdownClose(Point pt, int widget, int index, bool instant_close) override
2156 /* If you close the dropdown outside the list, don't take any action. */
2157 if (widget == WID_CL_MATRIX) return;
2159 Window::OnDropdownClose(pt, widget, index, instant_close);
2162 void OnDropdownSelect(int widget, int index) override
2164 switch (widget) {
2165 case WID_CL_SERVER_VISIBILITY:
2166 if (!_network_server) break;
2168 _settings_client.network.server_game_type = (ServerGameType)index;
2169 NetworkUpdateServerGameType();
2170 break;
2172 case WID_CL_MATRIX: {
2173 StringID text = STR_NULL;
2174 QueryCallbackProc *callback = nullptr;
2176 switch (index) {
2177 case DD_CLIENT_ADMIN_KICK:
2178 _admin_client_id = this->dd_client_id;
2179 text = STR_NETWORK_CLIENT_LIST_ASK_CLIENT_KICK;
2180 callback = AdminClientKickCallback;
2181 SetDParamStr(0, NetworkClientInfo::GetByClientID(_admin_client_id)->client_name);
2182 break;
2184 case DD_CLIENT_ADMIN_BAN:
2185 _admin_client_id = this->dd_client_id;
2186 text = STR_NETWORK_CLIENT_LIST_ASK_CLIENT_BAN;
2187 callback = AdminClientBanCallback;
2188 SetDParamStr(0, NetworkClientInfo::GetByClientID(_admin_client_id)->client_name);
2189 break;
2191 case DD_COMPANY_ADMIN_RESET:
2192 _admin_company_id = this->dd_company_id;
2193 text = STR_NETWORK_CLIENT_LIST_ASK_COMPANY_RESET;
2194 callback = AdminCompanyResetCallback;
2195 SetDParam(0, _admin_company_id);
2196 break;
2198 case DD_COMPANY_ADMIN_UNLOCK:
2199 _admin_company_id = this->dd_company_id;
2200 text = STR_NETWORK_CLIENT_LIST_ASK_COMPANY_UNLOCK;
2201 callback = AdminCompanyUnlockCallback;
2202 SetDParam(0, _admin_company_id);
2203 break;
2205 default:
2206 NOT_REACHED();
2209 assert(text != STR_NULL);
2210 assert(callback != nullptr);
2212 /* Always ask confirmation for all admin actions. */
2213 ShowQuery(STR_NETWORK_CLIENT_LIST_ASK_CAPTION, text, this, callback);
2215 break;
2218 default:
2219 NOT_REACHED();
2222 this->SetDirty();
2225 void OnQueryTextFinished(char *str) override
2227 if (str == nullptr) return;
2229 switch (this->query_widget) {
2230 default: NOT_REACHED();
2232 case WID_CL_SERVER_NAME_EDIT: {
2233 if (!_network_server) break;
2235 SetSettingValue(GetSettingFromName("network.server_name")->AsStringSetting(), str);
2236 this->InvalidateData();
2237 break;
2240 case WID_CL_CLIENT_NAME_EDIT: {
2241 SetSettingValue(GetSettingFromName("network.client_name")->AsStringSetting(), str);
2242 this->InvalidateData();
2243 break;
2246 case WID_CL_COMPANY_JOIN:
2247 NetworkClientRequestMove(this->join_company, str);
2248 break;
2253 * Draw the buttons for a single line in the matrix.
2255 * The x-position in RTL is the most left or otherwise the most right pixel
2256 * we can draw the buttons from.
2258 * @param x The x-position to start with the buttons. Updated during this function.
2259 * @param y The y-position to start with the buttons.
2260 * @param buttons The buttons to draw.
2262 void DrawButtons(uint &x, uint y, const std::vector<std::unique_ptr<ButtonCommon>> &buttons) const
2264 for (auto &button : buttons) {
2265 bool rtl = _current_text_dir == TD_RTL;
2267 uint left = rtl ? x : x - button->width;
2268 uint right = rtl ? x + button->width : x;
2270 int offset = std::max(0, ((int)(this->line_height + 1) - (int)button->height) / 2);
2272 DrawFrameRect(left, y + offset, right, y + offset + button->height, button->colour, FR_NONE);
2273 DrawSprite(button->sprite, PAL_NONE, left + ScaleGUITrad(WD_FRAMERECT_LEFT), y + offset + ScaleGUITrad(WD_FRAMERECT_TOP));
2274 if (button->disabled) {
2275 GfxFillRect(left + 1, y + offset + 1, right - 1, y + offset + button->height - 1, _colour_gradient[button->colour & 0xF][2], FILLRECT_CHECKER);
2278 int width = button->width + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
2279 x += rtl ? width : -width;
2284 * Draw a company and its clients on the matrix.
2285 * @param company_id The company to draw.
2286 * @param left The most left pixel of the line.
2287 * @param right The most right pixel of the line.
2288 * @param top The top of the first line.
2289 * @param line The Nth line we are drawing. Updated during this function.
2291 void DrawCompany(CompanyID company_id, uint left, uint right, uint top, uint &line) const
2293 bool rtl = _current_text_dir == TD_RTL;
2294 int text_y_offset = std::max(0, ((int)(this->line_height + 1) - (int)FONT_HEIGHT_NORMAL) / 2) + WD_MATRIX_BOTTOM;
2296 Dimension d = GetSpriteSize(SPR_COMPANY_ICON);
2297 int offset = std::max(0, ((int)(this->line_height + 1) - (int)d.height) / 2);
2299 uint text_left = left + (rtl ? (uint)WD_FRAMERECT_LEFT : d.width + 8);
2300 uint text_right = right - (rtl ? d.width + 8 : (uint)WD_FRAMERECT_RIGHT);
2302 uint line_start = this->vscroll->GetPosition();
2303 uint line_end = line_start + this->vscroll->GetCapacity();
2305 uint y = top + (this->line_height * (line - line_start));
2307 /* Draw the company line (if in range of scrollbar). */
2308 if (IsInsideMM(line, line_start, line_end)) {
2309 uint x = rtl ? text_left : text_right;
2311 /* If there are buttons for this company, draw them. */
2312 auto button_find = this->buttons.find(line);
2313 if (button_find != this->buttons.end()) {
2314 this->DrawButtons(x, y, button_find->second);
2317 if (company_id == COMPANY_SPECTATOR) {
2318 DrawSprite(SPR_COMPANY_ICON, PALETTE_TO_GREY, rtl ? right - d.width - 4 : left + 4, y + offset);
2319 DrawString(rtl ? x : text_left, rtl ? text_right : x, y + text_y_offset, STR_NETWORK_CLIENT_LIST_SPECTATORS, TC_SILVER);
2320 } else if (company_id == COMPANY_NEW_COMPANY) {
2321 DrawSprite(SPR_COMPANY_ICON, PALETTE_TO_GREY, rtl ? right - d.width - 4 : left + 4, y + offset);
2322 DrawString(rtl ? x : text_left, rtl ? text_right : x, y + text_y_offset, STR_NETWORK_CLIENT_LIST_NEW_COMPANY, TC_WHITE);
2323 } else {
2324 DrawCompanyIcon(company_id, rtl ? right - d.width - 4 : left + 4, y + offset);
2326 SetDParam(0, company_id);
2327 SetDParam(1, company_id);
2328 DrawString(rtl ? x : text_left, rtl ? text_right : x, y + text_y_offset, STR_COMPANY_NAME, TC_SILVER);
2332 y += this->line_height;
2333 line++;
2335 for (const NetworkClientInfo *ci : NetworkClientInfo::Iterate()) {
2336 if (ci->client_playas != company_id) continue;
2338 /* Draw the player line (if in range of scrollbar). */
2339 if (IsInsideMM(line, line_start, line_end)) {
2340 uint x = rtl ? text_left : text_right;
2342 /* If there are buttons for this client, draw them. */
2343 auto button_find = this->buttons.find(line);
2344 if (button_find != this->buttons.end()) {
2345 this->DrawButtons(x, y, button_find->second);
2348 SpriteID player_icon = 0;
2349 if (ci->client_id == _network_own_client_id) {
2350 player_icon = SPR_PLAYER_SELF;
2351 } else if (ci->client_id == CLIENT_ID_SERVER) {
2352 player_icon = SPR_PLAYER_HOST;
2355 if (player_icon != 0) {
2356 Dimension d2 = GetSpriteSize(player_icon);
2357 uint offset_x = CLIENT_OFFSET_LEFT - 3;
2358 int offset_y = std::max(0, ((int)(this->line_height + 1) - (int)d2.height) / 2);
2359 DrawSprite(player_icon, PALETTE_TO_GREY, rtl ? text_right - offset_x : text_left + offset_x - d2.width, y + offset_y);
2362 SetDParamStr(0, ci->client_name);
2363 DrawString(rtl ? x : text_left + CLIENT_OFFSET_LEFT, rtl ? text_right - CLIENT_OFFSET_LEFT : x, y + text_y_offset, STR_JUST_RAW_STRING, TC_BLACK);
2366 y += this->line_height;
2367 line++;
2371 void DrawWidget(const Rect &r, int widget) const override
2373 switch (widget) {
2374 case WID_CL_MATRIX: {
2375 uint line = 0;
2377 if (this->hover_index >= 0) {
2378 uint offset = this->hover_index * this->line_height;
2379 GfxFillRect(r.left + 2, r.top + offset, r.right - 1, r.top + offset + this->line_height - 2, GREY_SCALE(9));
2382 NetworkClientInfo *own_ci = NetworkClientInfo::GetByClientID(_network_own_client_id);
2383 if (own_ci->client_playas == COMPANY_SPECTATOR && !NetworkMaxCompaniesReached()) {
2384 this->DrawCompany(COMPANY_NEW_COMPANY, r.left, r.right, r.top, line);
2387 if (own_ci->client_playas != COMPANY_SPECTATOR) {
2388 this->DrawCompany(own_ci->client_playas, r.left, r.right, r.top, line);
2391 for (const Company *c : Company::Iterate()) {
2392 if (own_ci->client_playas == c->index) continue;
2393 this->DrawCompany(c->index, r.left, r.right, r.top, line);
2396 /* Specators */
2397 this->DrawCompany(COMPANY_SPECTATOR, r.left, r.right, r.top, line);
2399 break;
2404 virtual void OnMouseLoop() override
2406 if (GetWidgetFromPos(this, _cursor.pos.x - this->left, _cursor.pos.y - this->top) != WID_CL_MATRIX) {
2407 this->hover_index = -1;
2408 this->SetDirty();
2409 return;
2412 NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_CL_MATRIX);
2413 int y = _cursor.pos.y - this->top - nwi->pos_y - 2;
2414 int index = y / this->line_height;
2416 if (index != this->hover_index) {
2417 this->hover_index = index;
2418 this->SetDirty();
2423 void ShowClientList()
2425 AllocateWindowDescFront<NetworkClientListWindow>(&_client_list_desc, 0);
2428 NetworkJoinStatus _network_join_status; ///< The status of joining.
2429 uint8 _network_join_waiting; ///< The number of clients waiting in front of us.
2430 uint32 _network_join_bytes; ///< The number of bytes we already downloaded.
2431 uint32 _network_join_bytes_total; ///< The total number of bytes to download.
2433 struct NetworkJoinStatusWindow : Window {
2434 NetworkPasswordType password_type;
2436 NetworkJoinStatusWindow(WindowDesc *desc) : Window(desc)
2438 this->parent = FindWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_GAME);
2439 this->InitNested(WN_NETWORK_STATUS_WINDOW_JOIN);
2442 void DrawWidget(const Rect &r, int widget) const override
2444 if (widget != WID_NJS_BACKGROUND) return;
2446 uint8 progress; // used for progress bar
2447 DrawString(r.left + 2, r.right - 2, r.top + 20, STR_NETWORK_CONNECTING_1 + _network_join_status, TC_FROMSTRING, SA_HOR_CENTER);
2448 switch (_network_join_status) {
2449 case NETWORK_JOIN_STATUS_CONNECTING: case NETWORK_JOIN_STATUS_AUTHORIZING:
2450 case NETWORK_JOIN_STATUS_GETTING_COMPANY_INFO:
2451 progress = 10; // first two stages 10%
2452 break;
2453 case NETWORK_JOIN_STATUS_WAITING:
2454 SetDParam(0, _network_join_waiting);
2455 DrawString(r.left + 2, r.right - 2, r.top + 20 + FONT_HEIGHT_NORMAL, STR_NETWORK_CONNECTING_WAITING, TC_FROMSTRING, SA_HOR_CENTER);
2456 progress = 15; // third stage is 15%
2457 break;
2458 case NETWORK_JOIN_STATUS_DOWNLOADING:
2459 SetDParam(0, _network_join_bytes);
2460 SetDParam(1, _network_join_bytes_total);
2461 DrawString(r.left + 2, r.right - 2, r.top + 20 + FONT_HEIGHT_NORMAL, _network_join_bytes_total == 0 ? STR_NETWORK_CONNECTING_DOWNLOADING_1 : STR_NETWORK_CONNECTING_DOWNLOADING_2, TC_FROMSTRING, SA_HOR_CENTER);
2462 if (_network_join_bytes_total == 0) {
2463 progress = 15; // We don't have the final size yet; the server is still compressing!
2464 break;
2466 FALLTHROUGH;
2468 default: // Waiting is 15%, so the resting receivement of map is maximum 70%
2469 progress = 15 + _network_join_bytes * (100 - 15) / _network_join_bytes_total;
2472 /* Draw nice progress bar :) */
2473 DrawFrameRect(r.left + 20, r.top + 5, (int)((this->width - 20) * progress / 100), r.top + 15, COLOUR_MAUVE, FR_NONE);
2476 void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
2478 if (widget != WID_NJS_BACKGROUND) return;
2480 size->height = 25 + 2 * FONT_HEIGHT_NORMAL;
2482 /* Account for the statuses */
2483 uint width = 0;
2484 for (uint i = 0; i < NETWORK_JOIN_STATUS_END; i++) {
2485 width = std::max(width, GetStringBoundingBox(STR_NETWORK_CONNECTING_1 + i).width);
2488 /* For the number of waiting (other) players */
2489 SetDParamMaxValue(0, MAX_CLIENTS);
2490 width = std::max(width, GetStringBoundingBox(STR_NETWORK_CONNECTING_WAITING).width);
2492 /* Account for downloading ~ 10 MiB */
2493 SetDParamMaxDigits(0, 8);
2494 SetDParamMaxDigits(1, 8);
2495 width = std::max(width, GetStringBoundingBox(STR_NETWORK_CONNECTING_DOWNLOADING_1).width);
2496 width = std::max(width, GetStringBoundingBox(STR_NETWORK_CONNECTING_DOWNLOADING_2).width);
2498 /* Give a bit more clearing for the widest strings than strictly needed */
2499 size->width = width + WD_FRAMERECT_LEFT + WD_FRAMERECT_BOTTOM + 10;
2502 void OnClick(Point pt, int widget, int click_count) override
2504 if (widget == WID_NJS_CANCELOK) { // Disconnect button
2505 NetworkDisconnect();
2506 SwitchToMode(SM_MENU);
2507 ShowNetworkGameWindow();
2511 void OnQueryTextFinished(char *str) override
2513 if (StrEmpty(str)) {
2514 NetworkDisconnect();
2515 ShowNetworkGameWindow();
2516 return;
2519 switch (this->password_type) {
2520 case NETWORK_GAME_PASSWORD: MyClient::SendGamePassword (str); break;
2521 case NETWORK_COMPANY_PASSWORD: MyClient::SendCompanyPassword(str); break;
2522 default: NOT_REACHED();
2527 static const NWidgetPart _nested_network_join_status_window_widgets[] = {
2528 NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_NETWORK_CONNECTING_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2529 NWidget(WWT_PANEL, COLOUR_GREY),
2530 NWidget(WWT_EMPTY, COLOUR_GREY, WID_NJS_BACKGROUND),
2531 NWidget(NWID_HORIZONTAL),
2532 NWidget(NWID_SPACER), SetMinimalSize(75, 0), SetFill(1, 0),
2533 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NJS_CANCELOK), SetMinimalSize(101, 12), SetDataTip(STR_NETWORK_CONNECTION_DISCONNECT, STR_NULL),
2534 NWidget(NWID_SPACER), SetMinimalSize(75, 0), SetFill(1, 0),
2535 EndContainer(),
2536 NWidget(NWID_SPACER), SetMinimalSize(0, 4),
2537 EndContainer(),
2540 static WindowDesc _network_join_status_window_desc(
2541 WDP_CENTER, nullptr, 0, 0,
2542 WC_NETWORK_STATUS_WINDOW, WC_NONE,
2543 WDF_MODAL,
2544 _nested_network_join_status_window_widgets, lengthof(_nested_network_join_status_window_widgets)
2547 void ShowJoinStatusWindow()
2549 CloseWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN);
2550 new NetworkJoinStatusWindow(&_network_join_status_window_desc);
2553 void ShowNetworkNeedPassword(NetworkPasswordType npt)
2555 NetworkJoinStatusWindow *w = (NetworkJoinStatusWindow *)FindWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN);
2556 if (w == nullptr) return;
2557 w->password_type = npt;
2559 StringID caption;
2560 switch (npt) {
2561 default: NOT_REACHED();
2562 case NETWORK_GAME_PASSWORD: caption = STR_NETWORK_NEED_GAME_PASSWORD_CAPTION; break;
2563 case NETWORK_COMPANY_PASSWORD: caption = STR_NETWORK_NEED_COMPANY_PASSWORD_CAPTION; break;
2565 ShowQueryString(STR_EMPTY, caption, NETWORK_PASSWORD_LENGTH, w, CS_ALPHANUMERAL, QSF_PASSWORD);
2568 struct NetworkCompanyPasswordWindow : public Window {
2569 QueryString password_editbox; ///< Password editbox.
2570 Dimension warning_size; ///< How much space to use for the warning text
2572 NetworkCompanyPasswordWindow(WindowDesc *desc, Window *parent) : Window(desc), password_editbox(lengthof(_settings_client.network.default_company_pass))
2574 this->InitNested(0);
2575 this->UpdateWarningStringSize();
2577 this->parent = parent;
2578 this->querystrings[WID_NCP_PASSWORD] = &this->password_editbox;
2579 this->password_editbox.cancel_button = WID_NCP_CANCEL;
2580 this->password_editbox.ok_button = WID_NCP_OK;
2581 this->SetFocusedWidget(WID_NCP_PASSWORD);
2584 void UpdateWarningStringSize()
2586 assert(this->nested_root->smallest_x > 0);
2587 this->warning_size.width = this->nested_root->current_x - (WD_FRAMETEXT_LEFT + WD_FRAMETEXT_RIGHT + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT);
2588 this->warning_size.height = GetStringHeight(STR_WARNING_PASSWORD_SECURITY, this->warning_size.width);
2589 this->warning_size.height += WD_FRAMETEXT_TOP + WD_FRAMETEXT_BOTTOM + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
2591 this->ReInit();
2594 void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
2596 if (widget == WID_NCP_WARNING) {
2597 *size = this->warning_size;
2601 void DrawWidget(const Rect &r, int widget) const override
2603 if (widget != WID_NCP_WARNING) return;
2605 DrawStringMultiLine(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT,
2606 r.top + WD_FRAMERECT_TOP, r.bottom - WD_FRAMERECT_BOTTOM,
2607 STR_WARNING_PASSWORD_SECURITY, TC_FROMSTRING, SA_CENTER);
2610 void OnOk()
2612 if (this->IsWidgetLowered(WID_NCP_SAVE_AS_DEFAULT_PASSWORD)) {
2613 _settings_client.network.default_company_pass = this->password_editbox.text.buf;
2616 NetworkChangeCompanyPassword(_local_company, this->password_editbox.text.buf);
2619 void OnClick(Point pt, int widget, int click_count) override
2621 switch (widget) {
2622 case WID_NCP_OK:
2623 this->OnOk();
2624 FALLTHROUGH;
2626 case WID_NCP_CANCEL:
2627 this->Close();
2628 break;
2630 case WID_NCP_SAVE_AS_DEFAULT_PASSWORD:
2631 this->ToggleWidgetLoweredState(WID_NCP_SAVE_AS_DEFAULT_PASSWORD);
2632 this->SetDirty();
2633 break;
2638 static const NWidgetPart _nested_network_company_password_window_widgets[] = {
2639 NWidget(NWID_HORIZONTAL),
2640 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
2641 NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_COMPANY_PASSWORD_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2642 EndContainer(),
2643 NWidget(WWT_PANEL, COLOUR_GREY, WID_NCP_BACKGROUND),
2644 NWidget(NWID_VERTICAL), SetPIP(5, 5, 5),
2645 NWidget(NWID_HORIZONTAL), SetPIP(5, 5, 5),
2646 NWidget(WWT_TEXT, COLOUR_GREY, WID_NCP_LABEL), SetDataTip(STR_COMPANY_VIEW_PASSWORD, STR_NULL),
2647 NWidget(WWT_EDITBOX, COLOUR_GREY, WID_NCP_PASSWORD), SetFill(1, 0), SetMinimalSize(194, 12), SetDataTip(STR_COMPANY_VIEW_SET_PASSWORD, STR_NULL),
2648 EndContainer(),
2649 NWidget(NWID_HORIZONTAL), SetPIP(5, 0, 5),
2650 NWidget(NWID_SPACER), SetFill(1, 0),
2651 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_NCP_SAVE_AS_DEFAULT_PASSWORD), SetMinimalSize(194, 12),
2652 SetDataTip(STR_COMPANY_PASSWORD_MAKE_DEFAULT, STR_COMPANY_PASSWORD_MAKE_DEFAULT_TOOLTIP),
2653 EndContainer(),
2654 EndContainer(),
2655 EndContainer(),
2656 NWidget(WWT_PANEL, COLOUR_GREY, WID_NCP_WARNING), EndContainer(),
2657 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
2658 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_NCP_CANCEL), SetFill(1, 0), SetDataTip(STR_BUTTON_CANCEL, STR_COMPANY_PASSWORD_CANCEL),
2659 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_NCP_OK), SetFill(1, 0), SetDataTip(STR_BUTTON_OK, STR_COMPANY_PASSWORD_OK),
2660 EndContainer(),
2663 static WindowDesc _network_company_password_window_desc(
2664 WDP_AUTO, nullptr, 0, 0,
2665 WC_COMPANY_PASSWORD_WINDOW, WC_NONE,
2667 _nested_network_company_password_window_widgets, lengthof(_nested_network_company_password_window_widgets)
2670 void ShowNetworkCompanyPasswordWindow(Window *parent)
2672 CloseWindowById(WC_COMPANY_PASSWORD_WINDOW, 0);
2674 new NetworkCompanyPasswordWindow(&_network_company_password_window_desc, parent);
2678 * Window used for asking the user if he is okay using a TURN server.
2680 struct NetworkAskRelayWindow : public Window {
2681 std::string connection_string; ///< The TURN server we want to connect to.
2682 std::string token; ///< The token for this connection.
2684 NetworkAskRelayWindow(WindowDesc *desc, Window *parent, const std::string &connection_string, const std::string &token) : Window(desc), connection_string(connection_string), token(token)
2686 this->parent = parent;
2687 this->InitNested(0);
2690 void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
2692 if (widget == WID_NAR_TEXT) {
2693 *size = GetStringBoundingBox(STR_NETWORK_ASK_RELAY_TEXT);
2694 size->height = GetStringHeight(STR_NETWORK_ASK_RELAY_TEXT, size->width - WD_FRAMETEXT_LEFT - WD_FRAMETEXT_RIGHT) + WD_FRAMETEXT_BOTTOM + WD_FRAMETEXT_TOP;
2698 void DrawWidget(const Rect &r, int widget) const override
2700 if (widget == WID_NAR_TEXT) {
2701 DrawStringMultiLine(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, r.top + WD_FRAMETEXT_TOP, r.bottom - WD_FRAMETEXT_BOTTOM, STR_NETWORK_ASK_RELAY_TEXT, TC_FROMSTRING, SA_CENTER);
2705 void FindWindowPlacementAndResize(int def_width, int def_height) override
2707 /* Position query window over the calling window, ensuring it's within screen bounds. */
2708 this->left = Clamp(parent->left + (parent->width / 2) - (this->width / 2), 0, _screen.width - this->width);
2709 this->top = Clamp(parent->top + (parent->height / 2) - (this->height / 2), 0, _screen.height - this->height);
2710 this->SetDirty();
2713 void SetStringParameters(int widget) const override
2715 switch (widget) {
2716 case WID_NAR_TEXT:
2717 SetDParamStr(0, this->connection_string);
2718 break;
2722 void OnClick(Point pt, int widget, int click_count) override
2724 switch (widget) {
2725 case WID_NAR_NO:
2726 _network_coordinator_client.ConnectFailure(this->token, 0);
2727 this->Close();
2728 break;
2730 case WID_NAR_YES_ONCE:
2731 _network_coordinator_client.StartTurnConnection(this->token);
2732 this->Close();
2733 break;
2735 case WID_NAR_YES_ALWAYS:
2736 _settings_client.network.use_relay_service = URS_ALLOW;
2737 _network_coordinator_client.StartTurnConnection(this->token);
2738 this->Close();
2739 break;
2744 static const NWidgetPart _nested_network_ask_relay_widgets[] = {
2745 NWidget(NWID_HORIZONTAL),
2746 NWidget(WWT_CLOSEBOX, COLOUR_RED),
2747 NWidget(WWT_CAPTION, COLOUR_RED, WID_NAR_CAPTION), SetDataTip(STR_NETWORK_ASK_RELAY_CAPTION, STR_NULL),
2748 EndContainer(),
2749 NWidget(WWT_PANEL, COLOUR_RED), SetPIP(0, 0, 8),
2750 NWidget(WWT_TEXT, COLOUR_RED, WID_NAR_TEXT), SetAlignment(SA_HOR_CENTER), SetFill(1, 1),
2751 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 15, 10),
2752 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NAR_NO), SetMinimalSize(71, 12), SetFill(1, 1), SetDataTip(STR_NETWORK_ASK_RELAY_NO, STR_NULL),
2753 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NAR_YES_ONCE), SetMinimalSize(71, 12), SetFill(1, 1), SetDataTip(STR_NETWORK_ASK_RELAY_YES_ONCE, STR_NULL),
2754 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NAR_YES_ALWAYS), SetMinimalSize(71, 12), SetFill(1, 1), SetDataTip(STR_NETWORK_ASK_RELAY_YES_ALWAYS, STR_NULL),
2755 EndContainer(),
2756 EndContainer(),
2759 static WindowDesc _network_ask_relay_desc(
2760 WDP_CENTER, nullptr, 0, 0,
2761 WC_NETWORK_ASK_RELAY, WC_NONE,
2762 WDF_MODAL,
2763 _nested_network_ask_relay_widgets, lengthof(_nested_network_ask_relay_widgets)
2767 * Show a modal confirmation window with "no" / "yes, once" / "yes, always" buttons.
2768 * @param connection_string The relay server we want to connect to.
2769 * @param token The token for this connection.
2771 void ShowNetworkAskRelay(const std::string &connection_string, const std::string &token)
2773 CloseWindowByClass(WC_NETWORK_ASK_RELAY);
2775 Window *parent = FindWindowById(WC_MAIN_WINDOW, 0);
2776 new NetworkAskRelayWindow(&_network_ask_relay_desc, parent, connection_string, token);