OpenTTD

- Main Page
- Related Pages
- Modules
- Namespaces
- Data Structures
- Files

- File List
- Globals

network_gui.cpp
Go to the documentation of this file.
1 /* $Id: network_gui.cpp 27653 2016-09-04 16:06:50Z alberth $ */
2
3 /*
4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 */
9
12 #ifdef ENABLE_NETWORK
13 #include "../stdafx.h"
14 #include "../strings_func.h"
15 #include "../date_func.h"
16 #include "../fios.h"
17 #include "network_client.h"
18 #include "network_gui.h"
19 #include "network_gamelist.h"
20 #include "network.h"
21 #include "network_base.h"
22 #include "network_content.h"
23 #include "../gui.h"
24 #include "network_udp.h"
25 #include "../window_func.h"
26 #include "../gfx_func.h"
27 #include "../widgets/dropdown_func.h"
28 #include "../querystring_gui.h"
29 #include "../sortlist_type.h"
30 #include "../company_func.h"
31 #include "../core/geometry_func.hpp"
32 #include "../genworld.h"
33 #include "../map_type.h"
34
35 #include "../widgets/network_widget.h"
36
37 #include "table/strings.h"
38 #include "../table/sprites.h"
39
40 #include "../stringfilter_type.h"
41
42 #include "../safeguards.h"
43
44
45 static void ShowNetworkStartServerWindow();
46 static void ShowNetworkLobbyWindow(NetworkGameList *ngl);
47
51 static const StringID _connection_types_dropdown[] = {
52 STR_NETWORK_START_SERVER_UNADVERTISED,
53 STR_NETWORK_START_SERVER_ADVERTISED,
54 INVALID_STRING_ID
55 };
56
60 static const StringID _lan_internet_types_dropdown[] = {
61 STR_NETWORK_SERVER_LIST_ADVERTISED_NO,
62 STR_NETWORK_SERVER_LIST_ADVERTISED_YES,
63 INVALID_STRING_ID
64 };
65
66 static StringID _language_dropdown[NETLANG_COUNT + 1] = {STR_NULL};
67
68 void SortNetworkLanguages()
69 {
70 /* Init the strings */
71 if (_language_dropdown[0] == STR_NULL) {
72 for (int i = 0; i < NETLANG_COUNT; i++) _language_dropdown[i] = STR_NETWORK_LANG_ANY + i;
73 _language_dropdown[NETLANG_COUNT] = INVALID_STRING_ID;
74 }
75
76 /* Sort the strings (we don't move 'any' and the 'invalid' one) */
77 QSortT(_language_dropdown + 1, NETLANG_COUNT - 1, &StringIDSorter);
78 }
79
84 void UpdateNetworkGameWindow()
85 {
86 InvalidateWindowData(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_GAME, 0);
87 }
88
89 typedef GUIList<NetworkGameList*, StringFilter&> GUIGameServerList;
90 typedef uint16 ServerListPosition;
91 static const ServerListPosition SLP_INVALID = 0xFFFF;
92
94 class NWidgetServerListHeader : public NWidgetContainer {
95 static const uint MINIMUM_NAME_WIDTH_BEFORE_NEW_HEADER = 150;
96 bool visible[6];
97 public:
98 NWidgetServerListHeader() : NWidgetContainer(NWID_HORIZONTAL)
99 {
100 NWidgetLeaf *leaf = new NWidgetLeaf(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_NAME, STR_NETWORK_SERVER_LIST_GAME_NAME, STR_NETWORK_SERVER_LIST_GAME_NAME_TOOLTIP);
101 leaf->SetResize(1, 0);
102 leaf->SetFill(1, 0);
103 this->Add(leaf);
104
105 this->Add(new NWidgetLeaf(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_CLIENTS, STR_NETWORK_SERVER_LIST_CLIENTS_CAPTION, STR_NETWORK_SERVER_LIST_CLIENTS_CAPTION_TOOLTIP));
106 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));
107 this->Add(new NWidgetLeaf(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_DATE, STR_NETWORK_SERVER_LIST_DATE_CAPTION, STR_NETWORK_SERVER_LIST_DATE_CAPTION_TOOLTIP));
108 this->Add(new NWidgetLeaf(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_YEARS, STR_NETWORK_SERVER_LIST_YEARS_CAPTION, STR_NETWORK_SERVER_LIST_YEARS_CAPTION_TOOLTIP));
109
110 leaf = new NWidgetLeaf(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_INFO, STR_EMPTY, STR_NETWORK_SERVER_LIST_INFO_ICONS_TOOLTIP);
111 leaf->SetMinimalSize(14 + GetSpriteSize(SPR_LOCK).width + GetSpriteSize(SPR_BLOT).width + GetSpriteSize(SPR_FLAGS_BASE).width, 12);
112 leaf->SetFill(0, 1);
113 this->Add(leaf);
114
115 /* First and last are always visible, the rest is implicitly zeroed */
116 this->visible[0] = true;
117 *lastof(this->visible) = true;
118 }
119
120 void SetupSmallestSize(Window *w, bool init_array)
121 {
122 /* Oh yeah, we ought to be findable! */
123 w->nested_array[WID_NG_HEADER] = this;
124
125 this->smallest_y = 0; // Biggest child.
126 this->fill_x = 1;
127 this->fill_y = 0;
128 this->resize_x = 1; // We only resize in this direction
129 this->resize_y = 0; // We never resize in this direction
130
131 /* First initialise some variables... */
132 for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
133 child_wid->SetupSmallestSize(w, init_array);
134 this->smallest_y = max(this->smallest_y, child_wid->smallest_y + child_wid->padding_top + child_wid->padding_bottom);
135 }
136
137 /* ... then in a second pass make sure the 'current' sizes are set. Won't change for most widgets. */
138 for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
139 child_wid->current_x = child_wid->smallest_x;
140 child_wid->current_y = this->smallest_y;
141 }
142
143 this->smallest_x = this->head->smallest_x + this->tail->smallest_x; // First and last are always shown, rest not
144 }
145
146 void AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl)
147 {
148 assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
149
150 this->pos_x = x;
151 this->pos_y = y;
152 this->current_x = given_width;
153 this->current_y = given_height;
154
155 given_width -= this->tail->smallest_x;
156 NWidgetBase *child_wid = this->head->next;
157 /* The first and last widget are always visible, determine which other should be visible */
158 for (uint i = 1; i < lengthof(this->visible) - 1; i++) {
159 if (given_width > MINIMUM_NAME_WIDTH_BEFORE_NEW_HEADER + child_wid->smallest_x && this->visible[i - 1]) {
160 this->visible[i] = true;
161 given_width -= child_wid->smallest_x;
162 } else {
163 this->visible[i] = false;
164 }
165 child_wid = child_wid->next;
166 }
167
168 /* All remaining space goes to the first (name) widget */
169 this->head->current_x = given_width;
170
171 /* Now assign the widgets to their rightful place */
172 uint position = 0; // Place to put next child relative to origin of the container.
173 uint i = rtl ? lengthof(this->visible) - 1 : 0;
174 child_wid = rtl ? this->tail : this->head;
175 while (child_wid != NULL) {
176 if (this->visible[i]) {
177 child_wid->AssignSizePosition(sizing, x + position, y, child_wid->current_x, this->current_y, rtl);
178 position += child_wid->current_x;
179 }
180
181 child_wid = rtl ? child_wid->prev : child_wid->next;
182 i += rtl ? -1 : 1;
183 }
184 }
185
186 /* virtual */ void Draw(const Window *w)
187 {
188 int i = 0;
189 for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
190 if (!this->visible[i++]) continue;
191
192 child_wid->Draw(w);
193 }
194 }
195
196 /* virtual */ NWidgetCore *GetWidgetFromPos(int x, int y)
197 {
198 if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return NULL;
199
200 int i = 0;
201 for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
202 if (!this->visible[i++]) continue;
203 NWidgetCore *nwid = child_wid->GetWidgetFromPos(x, y);
204 if (nwid != NULL) return nwid;
205 }
206 return NULL;
207 }
208
214 bool IsWidgetVisible(NetworkGameWidgets widget) const
215 {
216 assert((uint)(widget - WID_NG_NAME) < lengthof(this->visible));
217 return this->visible[widget - WID_NG_NAME];
218 }
219 };
220
221 class NetworkGameWindow : public Window {
222 protected:
223 /* Runtime saved values */
224 static Listing last_sorting;
225
226 /* Constants for sorting servers */
227 static GUIGameServerList::SortFunction * const sorter_funcs[];
228 static GUIGameServerList::FilterFunction * const filter_funcs[];
229
230 NetworkGameList *server;
231 NetworkGameList *last_joined;
232 GUIGameServerList servers;
233 ServerListPosition list_pos;
234 Scrollbar *vscroll;
235 QueryString name_editbox;
236 QueryString filter_editbox;
237
238 int lock_offset;
239 int blot_offset;
240 int flag_offset;
241
247 void BuildGUINetworkGameList()
248 {
249 if (!this->servers.NeedRebuild()) return;
250
251 /* Create temporary array of games to use for listing */
252 this->servers.Clear();
253
254 for (NetworkGameList *ngl = _network_game_list; ngl != NULL; ngl = ngl->next) {
255 *this->servers.Append() = ngl;
256 }
257
258 /* Apply the filter condition immediately, if a search string has been provided. */
259 StringFilter sf;
260 sf.SetFilterTerm(this->filter_editbox.text.buf);
261
262 if (!sf.IsEmpty()) {
263 this->servers.SetFilterState(true);
264 this->servers.Filter(sf);
265 } else {
266 this->servers.SetFilterState(false);
267 }
268
269 this->servers.Compact();
270 this->servers.RebuildDone();
271 this->vscroll->SetCount(this->servers.Length());
272
273 /* Sort the list of network games as requested. */
274 this->servers.Sort();
275 this->UpdateListPos();
276 }
277
279 static int CDECL NGameNameSorter(NetworkGameList * const *a, NetworkGameList * const *b)
280 {
281 int r = strnatcmp((*a)->info.server_name, (*b)->info.server_name, true); // Sort by name (natural sorting).
282 return r == 0 ? (*a)->address.CompareTo((*b)->address) : r;
283 }
284
290 static int CDECL NGameClientSorter(NetworkGameList * const *a, NetworkGameList * const *b)
291 {
292 /* Reverse as per default we are interested in most-clients first */
293 int r = (*a)->info.clients_on - (*b)->info.clients_on;
294
295 if (r == 0) r = (*a)->info.clients_max - (*b)->info.clients_max;
296 if (r == 0) r = NGameNameSorter(a, b);
297
298 return r;
299 }
300
302 static int CDECL NGameMapSizeSorter(NetworkGameList * const *a, NetworkGameList * const *b)
303 {
304 /* Sort by the area of the map. */
305 int r = ((*a)->info.map_height) * ((*a)->info.map_width) - ((*b)->info.map_height) * ((*b)->info.map_width);
306
307 if (r == 0) r = (*a)->info.map_width - (*b)->info.map_width;
308 return (r != 0) ? r : NGameClientSorter(a, b);
309 }
310
312 static int CDECL NGameDateSorter(NetworkGameList * const *a, NetworkGameList * const *b)
313 {
314 int r = (*a)->info.game_date - (*b)->info.game_date;
315 return (r != 0) ? r : NGameClientSorter(a, b);
316 }
317
319 static int CDECL NGameYearsSorter(NetworkGameList * const *a, NetworkGameList * const *b)
320 {
321 int r = (*a)->info.game_date - (*a)->info.start_date - (*b)->info.game_date + (*b)->info.start_date;
322 return (r != 0) ? r : NGameDateSorter(a, b);
323 }
324
329 static int CDECL NGameAllowedSorter(NetworkGameList * const *a, NetworkGameList * const *b)
330 {
331 /* The servers we do not know anything about (the ones that did not reply) should be at the bottom) */
332 int r = StrEmpty((*a)->info.server_revision) - StrEmpty((*b)->info.server_revision);
333
334 /* Reverse default as we are interested in version-compatible clients first */
335 if (r == 0) r = (*b)->info.version_compatible - (*a)->info.version_compatible;
336 /* The version-compatible ones are then sorted with NewGRF compatible first, incompatible last */
337 if (r == 0) r = (*b)->info.compatible - (*a)->info.compatible;
338 /* Passworded servers should be below unpassworded servers */
339 if (r == 0) r = (*a)->info.use_password - (*b)->info.use_password;
340 /* Finally sort on the number of clients of the server */
341 if (r == 0) r = -NGameClientSorter(a, b);
342
343 return r;
344 }
345
347 void SortNetworkGameList()
348 {
349 if (this->servers.Sort()) this->UpdateListPos();
350 }
351
353 void UpdateListPos()
354 {
355 this->list_pos = SLP_INVALID;
356 for (uint i = 0; i != this->servers.Length(); i++) {
357 if (this->servers[i] == this->server) {
358 this->list_pos = i;
359 break;
360 }
361 }
362 }
363
364 static bool CDECL NGameSearchFilter(NetworkGameList * const *item, StringFilter &sf)
365 {
366 assert(item != NULL);
367 assert((*item) != NULL);
368
369 sf.ResetState();
370 sf.AddLine((*item)->info.server_name);
371 return sf.GetState();
372 }
373
380 void DrawServerLine(const NetworkGameList *cur_item, uint y, bool highlight) const
381 {
382 const NWidgetBase *nwi_name = this->GetWidget<NWidgetBase>(WID_NG_NAME);
383 const NWidgetBase *nwi_info = this->GetWidget<NWidgetBase>(WID_NG_INFO);
384
385 /* show highlighted item with a different colour */
386 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);
387
388 /* offsets to vertically centre text and icons */
389 int text_y_offset = (this->resize.step_height - FONT_HEIGHT_NORMAL) / 2 + 1;
390 int icon_y_offset = (this->resize.step_height - GetSpriteSize(SPR_BLOT).height) / 2;
391
392 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);
393
394 /* only draw details if the server is online */
395 if (cur_item->online) {
396 const NWidgetServerListHeader *nwi_header = this->GetWidget<NWidgetServerListHeader>(WID_NG_HEADER);
397
398 if (nwi_header->IsWidgetVisible(WID_NG_CLIENTS)) {
399 const NWidgetBase *nwi_clients = this->GetWidget<NWidgetBase>(WID_NG_CLIENTS);
400 SetDParam(0, cur_item->info.clients_on);
401 SetDParam(1, cur_item->info.clients_max);
402 SetDParam(2, cur_item->info.companies_on);
403 SetDParam(3, cur_item->info.companies_max);
404 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);
405 }
406
407 if (nwi_header->IsWidgetVisible(WID_NG_MAPSIZE)) {
408 /* map size */
409 const NWidgetBase *nwi_mapsize = this->GetWidget<NWidgetBase>(WID_NG_MAPSIZE);
410 SetDParam(0, cur_item->info.map_width);
411 SetDParam(1, cur_item->info.map_height);
412 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);
413 }
414
415 if (nwi_header->IsWidgetVisible(WID_NG_DATE)) {
416 /* current date */
417 const NWidgetBase *nwi_date = this->GetWidget<NWidgetBase>(WID_NG_DATE);
418 YearMonthDay ymd;
419 ConvertDateToYMD(cur_item->info.game_date, &ymd);
420 SetDParam(0, ymd.year);
421 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);
422 }
423
424 if (nwi_header->IsWidgetVisible(WID_NG_YEARS)) {
425 /* number of years the game is running */
426 const NWidgetBase *nwi_years = this->GetWidget<NWidgetBase>(WID_NG_YEARS);
427 YearMonthDay ymd_cur, ymd_start;
428 ConvertDateToYMD(cur_item->info.game_date, &ymd_cur);
429 ConvertDateToYMD(cur_item->info.start_date, &ymd_start);
430 SetDParam(0, ymd_cur.year - ymd_start.year);
431 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);
432 }
433
434 /* draw a lock if the server is password protected */
435 if (cur_item->info.use_password) DrawSprite(SPR_LOCK, PAL_NONE, nwi_info->pos_x + this->lock_offset, y + icon_y_offset - 1);
436
437 /* draw red or green icon, depending on compatibility with server */
438 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);
439
440 /* draw flag according to server language */
441 DrawSprite(SPR_FLAGS_BASE + cur_item->info.server_lang, PAL_NONE, nwi_info->pos_x + this->flag_offset, y + icon_y_offset);
442 }
443 }
444
452 void ScrollToSelectedServer()
453 {
454 if (this->list_pos == SLP_INVALID) return; // no server selected
455 this->vscroll->ScrollTowards(this->list_pos);
456 }
457
458 public:
459 NetworkGameWindow(WindowDesc *desc) : Window(desc), name_editbox(NETWORK_CLIENT_NAME_LENGTH), filter_editbox(120)
460 {
461 this->list_pos = SLP_INVALID;
462 this->server = NULL;
463
464 this->lock_offset = 5;
465 this->blot_offset = this->lock_offset + 3 + GetSpriteSize(SPR_LOCK).width;
466 this->flag_offset = this->blot_offset + 2 + GetSpriteSize(SPR_BLOT).width;
467
468 this->CreateNestedTree();
469 this->vscroll = this->GetScrollbar(WID_NG_SCROLLBAR);
470 this->FinishInitNested(WN_NETWORK_WINDOW_GAME);
471
472 this->querystrings[WID_NG_CLIENT] = &this->name_editbox;
473 this->name_editbox.text.Assign(_settings_client.network.client_name);
474
475 this->querystrings[WID_NG_FILTER] = &this->filter_editbox;
476 this->filter_editbox.cancel_button = QueryString::ACTION_CLEAR;
477 this->SetFocusedWidget(WID_NG_FILTER);
478
479 this->last_joined = NetworkGameListAddItem(NetworkAddress(_settings_client.network.last_host, _settings_client.network.last_port));
480 this->server = this->last_joined;
481 if (this->last_joined != NULL) NetworkUDPQueryServer(this->last_joined->address);
482
483 this->servers.SetListing(this->last_sorting);
484 this->servers.SetSortFuncs(this->sorter_funcs);
485 this->servers.SetFilterFuncs(this->filter_funcs);
486 this->servers.ForceRebuild();
487 }
488
489 ~NetworkGameWindow()
490 {
491 this->last_sorting = this->servers.GetListing();
492 }
493
494 virtual void SetStringParameters(int widget) const
495 {
496 switch (widget) {
497 case WID_NG_CONN_BTN:
498 SetDParam(0, _lan_internet_types_dropdown[_settings_client.network.lan_internet]);
499 break;
500 }
501 }
502
503 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
504 {
505 switch (widget) {
506 case WID_NG_CONN_BTN:
507 *size = maxdim(*size, maxdim(GetStringBoundingBox(_lan_internet_types_dropdown[0]), GetStringBoundingBox(_lan_internet_types_dropdown[1])));
508 size->width += padding.width;
509 size->height += padding.height;
510 break;
511
512 case WID_NG_MATRIX:
513 resize->height = WD_MATRIX_TOP + max(GetSpriteSize(SPR_BLOT).height, (uint)FONT_HEIGHT_NORMAL) + WD_MATRIX_BOTTOM;
514 size->height = 10 * resize->height;
515 break;
516
517 case WID_NG_LASTJOINED:
518 size->height = WD_MATRIX_TOP + max(GetSpriteSize(SPR_BLOT).height, (uint)FONT_HEIGHT_NORMAL) + WD_MATRIX_BOTTOM;
519 break;
520
521 case WID_NG_LASTJOINED_SPACER:
522 size->width = NWidgetScrollbar::GetVerticalDimension().width;
523 break;
524
525 case WID_NG_NAME:
526 size->width += 2 * Window::SortButtonWidth(); // Make space for the arrow
527 break;
528
529 case WID_NG_CLIENTS:
530 size->width += 2 * Window::SortButtonWidth(); // Make space for the arrow
531 SetDParamMaxValue(0, MAX_CLIENTS);
532 SetDParamMaxValue(1, MAX_CLIENTS);
533 SetDParamMaxValue(2, MAX_COMPANIES);
534 SetDParamMaxValue(3, MAX_COMPANIES);
535 *size = maxdim(*size, GetStringBoundingBox(STR_NETWORK_SERVER_LIST_GENERAL_ONLINE));
536 break;
537
538 case WID_NG_MAPSIZE:
539 size->width += 2 * Window::SortButtonWidth(); // Make space for the arrow
540 SetDParamMaxValue(0, MAX_MAP_SIZE);
541 SetDParamMaxValue(1, MAX_MAP_SIZE);
542 *size = maxdim(*size, GetStringBoundingBox(STR_NETWORK_SERVER_LIST_MAP_SIZE_SHORT));
543 break;
544
545 case WID_NG_DATE:
546 case WID_NG_YEARS:
547 size->width += 2 * Window::SortButtonWidth(); // Make space for the arrow
548 SetDParamMaxValue(0, 5);
549 *size = maxdim(*size, GetStringBoundingBox(STR_JUST_INT));
550 break;
551
552 case WID_NG_DETAILS_SPACER:
553 size->height = 20 + 12 * FONT_HEIGHT_NORMAL;
554 break;
555 }
556 }
557
558 virtual void DrawWidget(const Rect &r, int widget) const
559 {
560 switch (widget) {
561 case WID_NG_MATRIX: {
562 uint16 y = r.top;
563
564 const int max = min(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), (int)this->servers.Length());
565
566 for (int i = this->vscroll->GetPosition(); i < max; ++i) {
567 const NetworkGameList *ngl = this->servers[i];
568 this->DrawServerLine(ngl, y, ngl == this->server);
569 y += this->resize.step_height;
570 }
571 break;
572 }
573
574 case WID_NG_LASTJOINED:
575 /* Draw the last joined server, if any */
576 if (this->last_joined != NULL) this->DrawServerLine(this->last_joined, r.top, this->last_joined == this->server);
577 break;
578
579 case WID_NG_DETAILS:
580 this->DrawDetails(r);
581 break;
582
583 case WID_NG_NAME:
584 case WID_NG_CLIENTS:
585 case WID_NG_MAPSIZE:
586 case WID_NG_DATE:
587 case WID_NG_YEARS:
588 case WID_NG_INFO:
589 if (widget - WID_NG_NAME == this->servers.SortType()) this->DrawSortButtonState(widget, this->servers.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
590 break;
591 }
592 }
593
594
595 virtual void OnPaint()
596 {
597 if (this->servers.NeedRebuild()) {
598 this->BuildGUINetworkGameList();
599 }
600 if (this->servers.NeedResort()) {
601 this->SortNetworkGameList();
602 }
603
604 NetworkGameList *sel = this->server;
605 /* 'Refresh' button invisible if no server selected */
606 this->SetWidgetDisabledState(WID_NG_REFRESH, sel == NULL);
607 /* 'Join' button disabling conditions */
608 this->SetWidgetDisabledState(WID_NG_JOIN, sel == NULL || // no Selected Server
609 !sel->online || // Server offline
610 sel->info.clients_on >= sel->info.clients_max || // Server full
611 !sel->info.compatible); // Revision mismatch
612
613 /* 'NewGRF Settings' button invisible if no NewGRF is used */
614 this->GetWidget<NWidgetStacked>(WID_NG_NEWGRF_SEL)->SetDisplayedPlane(sel == NULL || !sel->online || sel->info.grfconfig == NULL);
615 this->GetWidget<NWidgetStacked>(WID_NG_NEWGRF_MISSING_SEL)->SetDisplayedPlane(sel == NULL || !sel->online || sel->info.grfconfig == NULL || !sel->info.version_compatible || sel->info.compatible);
616
617 this->DrawWidgets();
618 }
619
620 void DrawDetails(const Rect &r) const
621 {
622 NetworkGameList *sel = this->server;
623
624 const int detail_height = 6 + 8 + 6 + 3 * FONT_HEIGHT_NORMAL;
625
626 /* Draw the right menu */
627 GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.top + detail_height - 1, PC_DARK_BLUE);
628 if (sel == NULL) {
629 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);
630 } else if (!sel->online) {
631 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
632
633 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
634 } else { // show game info
635
636 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);
637 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 + 6 + 8 + 2 * FONT_HEIGHT_NORMAL, sel->info.map_name, TC_BLACK, SA_HOR_CENTER); // map name
639
640 uint16 y = r.top + detail_height + 4;
641
642 SetDParam(0, sel->info.clients_on);
643 SetDParam(1, sel->info.clients_max);
644 SetDParam(2, sel->info.companies_on);
645 SetDParam(3, sel->info.companies_max);
646 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_CLIENTS);
647 y += FONT_HEIGHT_NORMAL;
648
649 SetDParam(0, STR_NETWORK_LANG_ANY + sel->info.server_lang);
650 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_LANGUAGE); // server language
651 y += FONT_HEIGHT_NORMAL;
652
653 SetDParam(0, STR_CHEAT_SWITCH_CLIMATE_TEMPERATE_LANDSCAPE + sel->info.map_set);
654 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_LANDSCAPE); // landscape
655 y += FONT_HEIGHT_NORMAL;
656
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;
661
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;
665
666 SetDParamStr(0, sel->address.GetAddressAsString());
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;
669
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;
673
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;
677
678 y += WD_PAR_VSEP_NORMAL;
679
680 if (!sel->info.compatible) {
681 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
682 } else if (sel->info.clients_on == sel->info.clients_max) {
683 /* Show: server full, when clients_on == max_clients */
684 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
685 } else if (sel->info.use_password) {
686 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_PASSWORD, TC_FROMSTRING, SA_HOR_CENTER); // password warning
687 }
688 }
689 }
690
691 virtual void OnClick(Point pt, int widget, int click_count)
692 {
693 switch (widget) {
694 case WID_NG_CANCEL: // Cancel button
695 DeleteWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_GAME);
696 break;
697
698 case WID_NG_CONN_BTN: // 'Connection' droplist
699 ShowDropDownMenu(this, _lan_internet_types_dropdown, _settings_client.network.lan_internet, WID_NG_CONN_BTN, 0, 0); // do it for widget WID_NSS_CONN_BTN
700 break;
701
702 case WID_NG_NAME: // Sort by name
703 case WID_NG_CLIENTS: // Sort by connected clients
704 case WID_NG_MAPSIZE: // Sort by map size
705 case WID_NG_DATE: // Sort by date
706 case WID_NG_YEARS: // Sort by years
707 case WID_NG_INFO: // Connectivity (green dot)
708 if (this->servers.SortType() == widget - WID_NG_NAME) {
709 this->servers.ToggleSortOrder();
710 if (this->list_pos != SLP_INVALID) this->list_pos = this->servers.Length() - this->list_pos - 1;
711 } else {
712 this->servers.SetSortType(widget - WID_NG_NAME);
713 this->servers.ForceResort();
714 this->SortNetworkGameList();
715 }
716 this->ScrollToSelectedServer();
717 this->SetDirty();
718 break;
719
720 case WID_NG_MATRIX: { // Show available network games
721 uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NG_MATRIX);
722 this->server = (id_v < this->servers.Length()) ? this->servers[id_v] : NULL;
723 this->list_pos = (server == NULL) ? SLP_INVALID : id_v;
724 this->SetDirty();
725
726 /* FIXME the disabling should go into some InvalidateData, which is called instead of the SetDirty */
727 if (click_count > 1 && !this->IsWidgetDisabled(WID_NG_JOIN)) this->OnClick(pt, WID_NG_JOIN, 1);
728 break;
729 }
730
731 case WID_NG_LASTJOINED: {
732 if (this->last_joined != NULL) {
733 this->server = this->last_joined;
734
735 /* search the position of the newly selected server */
736 this->UpdateListPos();
737 this->ScrollToSelectedServer();
738 this->SetDirty();
739
740 /* FIXME the disabling should go into some InvalidateData, which is called instead of the SetDirty */
741 if (click_count > 1 && !this->IsWidgetDisabled(WID_NG_JOIN)) this->OnClick(pt, WID_NG_JOIN, 1);
742 }
743 break;
744 }
745
746 case WID_NG_FIND: // Find server automatically
747 switch (_settings_client.network.lan_internet) {
748 case 0: NetworkUDPSearchGame(); break;
749 case 1: NetworkUDPQueryMasterServer(); break;
750 }
751 break;
752
753 case WID_NG_ADD: // Add a server
754 SetDParamStr(0, _settings_client.network.connect_to_ip);
755 ShowQueryString(
756 STR_JUST_RAW_STRING,
757 STR_NETWORK_SERVER_LIST_ENTER_IP,
758 NETWORK_HOSTNAME_LENGTH, // maximum number of characters including '\0'
759 this, CS_ALPHANUMERAL, QSF_ACCEPT_UNCHANGED);
760 break;
761
762 case WID_NG_START: // Start server
763 ShowNetworkStartServerWindow();
764 break;
765
766 case WID_NG_JOIN: // Join Game
767 if (this->server != NULL) {
768 seprintf(_settings_client.network.last_host, lastof(_settings_client.network.last_host), "%s", this->server->address.GetHostname());
769 _settings_client.network.last_port = this->server->address.GetPort();
770 ShowNetworkLobbyWindow(this->server);
771 }
772 break;
773
774 case WID_NG_REFRESH: // Refresh
775 if (this->server != NULL) NetworkUDPQueryServer(this->server->address);
776 break;
777
778 case WID_NG_NEWGRF: // NewGRF Settings
779 if (this->server != NULL) ShowNewGRFSettings(false, false, false, &this->server->info.grfconfig);
780 break;
781
782 case WID_NG_NEWGRF_MISSING: // Find missing content online
783 if (this->server != NULL) ShowMissingContentWindow(this->server->info.grfconfig);
784 break;
785 }
786 }
787
788 virtual void OnDropdownSelect(int widget, int index)
789 {
790 switch (widget) {
791 case WID_NG_CONN_BTN:
792 _settings_client.network.lan_internet = index;
793 break;
794
795 default:
796 NOT_REACHED();
797 }
798
799 this->SetDirty();
800 }
801
807 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
808 {
809 this->servers.ForceRebuild();
810 this->SetDirty();
811 }
812
813 virtual EventState OnKeyPress(WChar key, uint16 keycode)
814 {
815 EventState state = ES_NOT_HANDLED;
816
817 /* handle up, down, pageup, pagedown, home and end */
818 if (keycode == WKC_UP || keycode == WKC_DOWN || keycode == WKC_PAGEUP || keycode == WKC_PAGEDOWN || keycode == WKC_HOME || keycode == WKC_END) {
819 if (this->servers.Length() == 0) return ES_HANDLED;
820 switch (keycode) {
821 case WKC_UP:
822 /* scroll up by one */
823 if (this->list_pos == SLP_INVALID) return ES_HANDLED;
824 if (this->list_pos > 0) this->list_pos--;
825 break;
826 case WKC_DOWN:
827 /* scroll down by one */
828 if (this->list_pos == SLP_INVALID) return ES_HANDLED;
829 if (this->list_pos < this->servers.Length() - 1) this->list_pos++;
830 break;
831 case WKC_PAGEUP:
832 /* scroll up a page */
833 if (this->list_pos == SLP_INVALID) return ES_HANDLED;
834 this->list_pos = (this->list_pos < this->vscroll->GetCapacity()) ? 0 : this->list_pos - this->vscroll->GetCapacity();
835 break;
836 case WKC_PAGEDOWN:
837 /* scroll down a page */
838 if (this->list_pos == SLP_INVALID) return ES_HANDLED;
839 this->list_pos = min(this->list_pos + this->vscroll->GetCapacity(), (int)this->servers.Length() - 1);
840 break;
841 case WKC_HOME:
842 /* jump to beginning */
843 this->list_pos = 0;
844 break;
845 case WKC_END:
846 /* jump to end */
847 this->list_pos = this->servers.Length() - 1;
848 break;
849 default: NOT_REACHED();
850 }
851
852 this->server = this->servers[this->list_pos];
853
854 /* Scroll to the new server if it is outside the current range. */
855 this->ScrollToSelectedServer();
856
857 /* redraw window */
858 this->SetDirty();
859 return ES_HANDLED;
860 }
861
862 if (this->server != NULL) {
863 if (keycode == WKC_DELETE) { // Press 'delete' to remove servers
864 NetworkGameListRemoveItem(this->server);
865 if (this->server == this->last_joined) this->last_joined = NULL;
866 this->server = NULL;
867 this->list_pos = SLP_INVALID;
868 }
869 }
870
871 return state;
872 }
873
874 virtual void OnEditboxChanged(int wid)
875 {
876 switch (wid) {
877 case WID_NG_FILTER: {
878 this->servers.ForceRebuild();
879 this->BuildGUINetworkGameList();
880 this->ScrollToSelectedServer();
881 this->SetDirty();
882 break;
883 }
884
885 case WID_NG_CLIENT:
886 /* Make sure the name does not start with a space, so TAB completion works */
887 if (!StrEmpty(this->name_editbox.text.buf) && this->name_editbox.text.buf[0] != ' ') {
888 strecpy(_settings_client.network.client_name, this->name_editbox.text.buf, lastof(_settings_client.network.client_name));
889 } else {
890 strecpy(_settings_client.network.client_name, "Player", lastof(_settings_client.network.client_name));
891 }
892 break;
893 }
894 }
895
896 virtual void OnQueryTextFinished(char *str)
897 {
898 if (!StrEmpty(str)) NetworkAddServer(str);
899 }
900
901 virtual void OnResize()
902 {
903 this->vscroll->SetCapacityFromWidget(this, WID_NG_MATRIX);
904 }
905
906 virtual void OnTick()
907 {
908 NetworkGameListRequery();
909 }
910 };
911
912 Listing NetworkGameWindow::last_sorting = {false, 5};
913 GUIGameServerList::SortFunction * const NetworkGameWindow::sorter_funcs[] = {
914 &NGameNameSorter,
915 &NGameClientSorter,
916 &NGameMapSizeSorter,
917 &NGameDateSorter,
918 &NGameYearsSorter,
919 &NGameAllowedSorter
920 };
921
922 GUIGameServerList::FilterFunction * const NetworkGameWindow::filter_funcs[] = {
923 &NGameSearchFilter
924 };
925
926 static NWidgetBase *MakeResizableHeader(int *biggest_index)
927 {
928 *biggest_index = max<int>(*biggest_index, WID_NG_INFO);
929 return new NWidgetServerListHeader();
930 }
931
932 static const NWidgetPart _nested_network_game_widgets[] = {
933 /* TOP */
934 NWidget(NWID_HORIZONTAL),
935 NWidget(WWT_CLOSEBOX, COLOUR_LIGHT_BLUE),
936 NWidget(WWT_CAPTION, COLOUR_LIGHT_BLUE), SetDataTip(STR_NETWORK_SERVER_LIST_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
937 NWidget(WWT_DEFSIZEBOX, COLOUR_LIGHT_BLUE),
938 EndContainer(),
939 NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, WID_NG_MAIN),
940 NWidget(NWID_VERTICAL), SetPIP(10, 7, 0),
941 NWidget(NWID_HORIZONTAL), SetPIP(10, 7, 10),
942 /* LEFT SIDE */
943 NWidget(NWID_VERTICAL), SetPIP(0, 7, 0),
944 NWidget(NWID_HORIZONTAL), SetPIP(0, 7, 0),
945 NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, WID_NG_CONNECTION), SetDataTip(STR_NETWORK_SERVER_LIST_ADVERTISED, STR_NULL),
946 NWidget(WWT_DROPDOWN, COLOUR_LIGHT_BLUE, WID_NG_CONN_BTN),
947 SetDataTip(STR_BLACK_STRING, STR_NETWORK_SERVER_LIST_ADVERTISED_TOOLTIP),
948 NWidget(NWID_SPACER), SetFill(1, 0), SetResize(1, 0),
949 EndContainer(),
950 NWidget(NWID_HORIZONTAL), SetPIP(0, 7, 0),
951 NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, WID_NG_FILTER_LABEL), SetDataTip(STR_LIST_FILTER_TITLE, STR_NULL),
952 NWidget(WWT_EDITBOX, COLOUR_LIGHT_BLUE, WID_NG_FILTER), SetMinimalSize(251, 12), SetFill(1, 0), SetResize(1, 0),
953 SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
954 EndContainer(),
955 NWidget(NWID_HORIZONTAL),
956 NWidget(NWID_VERTICAL),
957 NWidgetFunction(MakeResizableHeader),
958 NWidget(WWT_MATRIX, COLOUR_LIGHT_BLUE, WID_NG_MATRIX), SetResize(1, 1), SetFill(1, 0),
959 SetMatrixDataTip(1, 0, STR_NETWORK_SERVER_LIST_CLICK_GAME_TO_SELECT), SetScrollbar(WID_NG_SCROLLBAR),
960 EndContainer(),
961 NWidget(NWID_VSCROLLBAR, COLOUR_LIGHT_BLUE, WID_NG_SCROLLBAR),
962 EndContainer(),
963 NWidget(NWID_VERTICAL),
964 NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, WID_NG_LASTJOINED_LABEL), SetFill(1, 0),
965 SetDataTip(STR_NETWORK_SERVER_LIST_LAST_JOINED_SERVER, STR_NULL), SetResize(1, 0),
966 NWidget(NWID_HORIZONTAL),
967 NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, WID_NG_LASTJOINED), SetFill(1, 0), SetResize(1, 0),
968 SetDataTip(0x0, STR_NETWORK_SERVER_LIST_CLICK_TO_SELECT_LAST),
969 EndContainer(),
970 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_NG_LASTJOINED_SPACER), SetFill(0, 0),
971 EndContainer(),
972 EndContainer(),
973 EndContainer(),
974 /* RIGHT SIDE */
975 NWidget(NWID_VERTICAL), SetPIP(0, 7, 0),
976 NWidget(NWID_HORIZONTAL), SetPIP(0, 7, 0),
977 NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, WID_NG_CLIENT_LABEL), SetDataTip(STR_NETWORK_SERVER_LIST_PLAYER_NAME, STR_NULL),
978 NWidget(WWT_EDITBOX, COLOUR_LIGHT_BLUE, WID_NG_CLIENT), SetMinimalSize(151, 12), SetFill(1, 0), SetResize(1, 0),
979 SetDataTip(STR_NETWORK_SERVER_LIST_PLAYER_NAME_OSKTITLE, STR_NETWORK_SERVER_LIST_ENTER_NAME_TOOLTIP),
980 EndContainer(),
981 NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, WID_NG_DETAILS),
982 NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(5, 5, 5),
983 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_NG_DETAILS_SPACER), SetMinimalSize(140, 155), SetResize(0, 1), SetFill(1, 1), // Make sure it's at least this wide
984 NWidget(NWID_HORIZONTAL, NC_NONE), SetPIP(5, 5, 5),
985 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_NG_NEWGRF_MISSING_SEL),
986 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),
987 NWidget(NWID_SPACER), SetFill(1, 0),
988 EndContainer(),
989 EndContainer(),
990 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(5, 5, 5),
991 NWidget(NWID_SPACER), SetFill(1, 0),
992 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_NG_NEWGRF_SEL),
993 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_NEWGRF), SetFill(1, 0), SetDataTip(STR_INTRO_NEWGRF_SETTINGS, STR_NULL),
994 NWidget(NWID_SPACER), SetFill(1, 0),
995 EndContainer(),
996 EndContainer(),
997 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(5, 5, 5),
998 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_JOIN), SetFill(1, 0), SetDataTip(STR_NETWORK_SERVER_LIST_JOIN_GAME, STR_NULL),
999 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_REFRESH), SetFill(1, 0), SetDataTip(STR_NETWORK_SERVER_LIST_REFRESH, STR_NETWORK_SERVER_LIST_REFRESH_TOOLTIP),
1000 EndContainer(),
1001 EndContainer(),
1002 EndContainer(),
1003 EndContainer(),
1004 EndContainer(),
1005 /* BOTTOM */
1006 NWidget(NWID_HORIZONTAL),
1007 NWidget(NWID_VERTICAL),
1008 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 7, 4),
1009 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_FIND), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_NETWORK_SERVER_LIST_FIND_SERVER, STR_NETWORK_SERVER_LIST_FIND_SERVER_TOOLTIP),
1010 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),
1011 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),
1012 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NG_CANCEL), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_BUTTON_CANCEL, STR_NULL),
1013 EndContainer(),
1014 NWidget(NWID_SPACER), SetMinimalSize(0, 6), SetResize(1, 0), SetFill(1, 0),
1015 EndContainer(),
1016 NWidget(NWID_VERTICAL),
1017 NWidget(NWID_SPACER), SetFill(0, 1),
1018 NWidget(WWT_RESIZEBOX, COLOUR_LIGHT_BLUE),
1019 EndContainer(),
1020 EndContainer(),
1021 EndContainer(),
1022 EndContainer(),
1023 };
1024
1025 static WindowDesc _network_game_window_desc(
1026 WDP_CENTER, "list_servers", 1000, 730,
1027 WC_NETWORK_WINDOW, WC_NONE,
1028 0,
1029 _nested_network_game_widgets, lengthof(_nested_network_game_widgets)
1030 );
1031
1032 void ShowNetworkGameWindow()
1033 {
1034 static bool first = true;
1035 DeleteWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_LOBBY);
1036 DeleteWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_START);
1037
1038 /* Only show once */
1039 if (first) {
1040 first = false;
1041 /* Add all servers from the config file to our list. */
1042 for (char **iter = _network_host_list.Begin(); iter != _network_host_list.End(); iter++) {
1043 NetworkAddServer(*iter);
1044 }
1045 }
1046
1047 new NetworkGameWindow(&_network_game_window_desc);
1048 }
1049
1050 struct NetworkStartServerWindow : public Window {
1051 byte widget_id;
1052 QueryString name_editbox;
1053
1054 NetworkStartServerWindow(WindowDesc *desc) : Window(desc), name_editbox(NETWORK_NAME_LENGTH)
1055 {
1056 this->InitNested(WN_NETWORK_WINDOW_START);
1057
1058 this->querystrings[WID_NSS_GAMENAME] = &this->name_editbox;
1059 this->name_editbox.text.Assign(_settings_client.network.server_name);
1060
1061 this->SetFocusedWidget(WID_NSS_GAMENAME);
1062 }
1063
1064 virtual void SetStringParameters(int widget) const
1065 {
1066 switch (widget) {
1067 case WID_NSS_CONNTYPE_BTN:
1068 SetDParam(0, _connection_types_dropdown[_settings_client.network.server_advertise]);
1069 break;
1070
1071 case WID_NSS_CLIENTS_TXT:
1072 SetDParam(0, _settings_client.network.max_clients);
1073 break;
1074
1075 case WID_NSS_COMPANIES_TXT:
1076 SetDParam(0, _settings_client.network.max_companies);
1077 break;
1078
1079 case WID_NSS_SPECTATORS_TXT:
1080 SetDParam(0, _settings_client.network.max_spectators);
1081 break;
1082
1083 case WID_NSS_LANGUAGE_BTN:
1084 SetDParam(0, STR_NETWORK_LANG_ANY + _settings_client.network.server_lang);
1085 break;
1086 }
1087 }
1088
1089 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
1090 {
1091 switch (widget) {
1092 case WID_NSS_CONNTYPE_BTN:
1093 *size = maxdim(GetStringBoundingBox(_connection_types_dropdown[0]), GetStringBoundingBox(_connection_types_dropdown[1]));
1094 size->width += padding.width;
1095 size->height += padding.height;
1096 break;
1097 }
1098 }
1099
1100 virtual void DrawWidget(const Rect &r, int widget) const
1101 {
1102 switch (widget) {
1103 case WID_NSS_SETPWD:
1104 /* If password is set, draw red '*' next to 'Set password' button. */
1105 if (!StrEmpty(_settings_client.network.server_password)) DrawString(r.right + WD_FRAMERECT_LEFT, this->width - WD_FRAMERECT_RIGHT, r.top, "*", TC_RED);
1106 }
1107 }
1108
1109 virtual void OnClick(Point pt, int widget, int click_count)
1110 {
1111 switch (widget) {
1112 case WID_NSS_CANCEL: // Cancel button
1113 ShowNetworkGameWindow();
1114 break;
1115
1116 case WID_NSS_SETPWD: // Set password button
1117 this->widget_id = WID_NSS_SETPWD;
1118 SetDParamStr(0, _settings_client.network.server_password);
1119 ShowQueryString(STR_JUST_RAW_STRING, STR_NETWORK_START_SERVER_SET_PASSWORD, 20, this, CS_ALPHANUMERAL, QSF_NONE);
1120 break;
1121
1122 case WID_NSS_CONNTYPE_BTN: // Connection type
1123 ShowDropDownMenu(this, _connection_types_dropdown, _settings_client.network.server_advertise, WID_NSS_CONNTYPE_BTN, 0, 0); // do it for widget WID_NSS_CONNTYPE_BTN
1124 break;
1125
1126 case WID_NSS_CLIENTS_BTND: case WID_NSS_CLIENTS_BTNU: // Click on up/down button for number of clients
1127 case WID_NSS_COMPANIES_BTND: case WID_NSS_COMPANIES_BTNU: // Click on up/down button for number of companies
1128 case WID_NSS_SPECTATORS_BTND: case WID_NSS_SPECTATORS_BTNU: // Click on up/down button for number of spectators
1129 /* Don't allow too fast scrolling. */
1130 if (!(this->flags & WF_TIMEOUT) || this->timeout_timer <= 1) {
1131 this->HandleButtonClick(widget);
1132 this->SetDirty();
1133 switch (widget) {
1134 default: NOT_REACHED();
1135 case WID_NSS_CLIENTS_BTND: case WID_NSS_CLIENTS_BTNU:
1136 _settings_client.network.max_clients = Clamp(_settings_client.network.max_clients + widget - WID_NSS_CLIENTS_TXT, 2, MAX_CLIENTS);
1137 break;
1138 case WID_NSS_COMPANIES_BTND: case WID_NSS_COMPANIES_BTNU:
1139 _settings_client.network.max_companies = Clamp(_settings_client.network.max_companies + widget - WID_NSS_COMPANIES_TXT, 1, MAX_COMPANIES);
1140 break;
1141 case WID_NSS_SPECTATORS_BTND: case WID_NSS_SPECTATORS_BTNU:
1142 _settings_client.network.max_spectators = Clamp(_settings_client.network.max_spectators + widget - WID_NSS_SPECTATORS_TXT, 0, MAX_CLIENTS);
1143 break;
1144 }
1145 }
1146 _left_button_clicked = false;
1147 break;
1148
1149 case WID_NSS_CLIENTS_TXT: // Click on number of clients
1150 this->widget_id = WID_NSS_CLIENTS_TXT;
1151 SetDParam(0, _settings_client.network.max_clients);
1152 ShowQueryString(STR_JUST_INT, STR_NETWORK_START_SERVER_NUMBER_OF_CLIENTS, 4, this, CS_NUMERAL, QSF_NONE);
1153 break;
1154
1155 case WID_NSS_COMPANIES_TXT: // Click on number of companies
1156 this->widget_id = WID_NSS_COMPANIES_TXT;
1157 SetDParam(0, _settings_client.network.max_companies);
1158 ShowQueryString(STR_JUST_INT, STR_NETWORK_START_SERVER_NUMBER_OF_COMPANIES, 3, this, CS_NUMERAL, QSF_NONE);
1159 break;
1160
1161 case WID_NSS_SPECTATORS_TXT: // Click on number of spectators
1162 this->widget_id = WID_NSS_SPECTATORS_TXT;
1163 SetDParam(0, _settings_client.network.max_spectators);
1164 ShowQueryString(STR_JUST_INT, STR_NETWORK_START_SERVER_NUMBER_OF_SPECTATORS, 4, this, CS_NUMERAL, QSF_NONE);
1165 break;
1166
1167 case WID_NSS_LANGUAGE_BTN: { // Language
1168 uint sel = 0;
1169 for (uint i = 0; i < lengthof(_language_dropdown) - 1; i++) {
1170 if (_language_dropdown[i] == STR_NETWORK_LANG_ANY + _settings_client.network.server_lang) {
1171 sel = i;
1172 break;
1173 }
1174 }
1175 ShowDropDownMenu(this, _language_dropdown, sel, WID_NSS_LANGUAGE_BTN, 0, 0);
1176 break;
1177 }
1178
1179 case WID_NSS_GENERATE_GAME: // Start game
1180 _is_network_server = true;
1181 if (_ctrl_pressed) {
1182 StartNewGameWithoutGUI(GENERATE_NEW_SEED);
1183 } else {
1184 ShowGenerateLandscape();
1185 }
1186 break;
1187
1188 case WID_NSS_LOAD_GAME:
1189 _is_network_server = true;
1190 ShowSaveLoadDialog(FT_SAVEGAME, SLO_LOAD);
1191 break;
1192
1193 case WID_NSS_PLAY_SCENARIO:
1194 _is_network_server = true;
1195 ShowSaveLoadDialog(FT_SCENARIO, SLO_LOAD);
1196 break;
1197
1198 case WID_NSS_PLAY_HEIGHTMAP:
1199 _is_network_server = true;
1200 ShowSaveLoadDialog(FT_HEIGHTMAP,SLO_LOAD);
1201 break;
1202 }
1203 }
1204
1205 virtual void OnDropdownSelect(int widget, int index)
1206 {
1207 switch (widget) {
1208 case WID_NSS_CONNTYPE_BTN:
1209 _settings_client.network.server_advertise = (index != 0);
1210 break;
1211 case WID_NSS_LANGUAGE_BTN:
1212 _settings_client.network.server_lang = _language_dropdown[index] - STR_NETWORK_LANG_ANY;
1213 break;
1214 default:
1215 NOT_REACHED();
1216 }
1217
1218 this->SetDirty();
1219 }
1220
1221 virtual void OnEditboxChanged(int wid)
1222 {
1223 if (wid == WID_NSS_GAMENAME) {
1224 strecpy(_settings_client.network.server_name, this->name_editbox.text.buf, lastof(_settings_client.network.server_name));
1225 }
1226 }
1227
1228 virtual void OnTimeout()
1229 {
1230 static const int raise_widgets[] = {WID_NSS_CLIENTS_BTND, WID_NSS_CLIENTS_BTNU, WID_NSS_COMPANIES_BTND, WID_NSS_COMPANIES_BTNU, WID_NSS_SPECTATORS_BTND, WID_NSS_SPECTATORS_BTNU, WIDGET_LIST_END};
1231 for (const int *widget = raise_widgets; *widget != WIDGET_LIST_END; widget++) {
1232 if (this->IsWidgetLowered(*widget)) {
1233 this->RaiseWidget(*widget);
1234 this->SetWidgetDirty(*widget);
1235 }
1236 }
1237 }
1238
1239 virtual void OnQueryTextFinished(char *str)
1240 {
1241 if (str == NULL) return;
1242
1243 if (this->widget_id == WID_NSS_SETPWD) {
1244 strecpy(_settings_client.network.server_password, str, lastof(_settings_client.network.server_password));
1245 } else {
1246 int32 value = atoi(str);
1247 this->SetWidgetDirty(this->widget_id);
1248 switch (this->widget_id) {
1249 default: NOT_REACHED();
1250 case WID_NSS_CLIENTS_TXT: _settings_client.network.max_clients = Clamp(value, 2, MAX_CLIENTS); break;
1251 case WID_NSS_COMPANIES_TXT: _settings_client.network.max_companies = Clamp(value, 1, MAX_COMPANIES); break;
1252 case WID_NSS_SPECTATORS_TXT: _settings_client.network.max_spectators = Clamp(value, 0, MAX_CLIENTS); break;
1253 }
1254 }
1255
1256 this->SetDirty();
1257 }
1258 };
1259
1260 static const NWidgetPart _nested_network_start_server_window_widgets[] = {
1261 NWidget(NWID_HORIZONTAL),
1262 NWidget(WWT_CLOSEBOX, COLOUR_LIGHT_BLUE),
1263 NWidget(WWT_CAPTION, COLOUR_LIGHT_BLUE), SetDataTip(STR_NETWORK_START_SERVER_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1264 EndContainer(),
1265 NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, WID_NSS_BACKGROUND),
1266 NWidget(NWID_VERTICAL), SetPIP(10, 6, 10),
1267 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 6, 10),
1268 NWidget(NWID_VERTICAL), SetPIP(0, 1, 0),
1269 /* Game name widgets */
1270 NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, WID_NSS_GAMENAME_LABEL), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_NEW_GAME_NAME, STR_NULL),
1271 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),
1272 EndContainer(),
1273 EndContainer(),
1274
1275 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 6, 10),
1276 NWidget(NWID_VERTICAL), SetPIP(0, 1, 0),
1277 NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, WID_NSS_CONNTYPE_LABEL), SetFill(1, 0), SetDataTip(STR_NETWORK_SERVER_LIST_ADVERTISED, STR_NULL),
1278 NWidget(WWT_DROPDOWN, COLOUR_LIGHT_BLUE, WID_NSS_CONNTYPE_BTN), SetFill(1, 0), SetDataTip(STR_BLACK_STRING, STR_NETWORK_SERVER_LIST_ADVERTISED_TOOLTIP),
1279 EndContainer(),
1280 NWidget(NWID_VERTICAL), SetPIP(0, 1, 0),
1281 NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, WID_NSS_LANGUAGE_LABEL), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_LANGUAGE_SPOKEN, STR_NULL),
1282 NWidget(WWT_DROPDOWN, COLOUR_LIGHT_BLUE, WID_NSS_LANGUAGE_BTN), SetFill(1, 0), SetDataTip(STR_BLACK_STRING, STR_NETWORK_START_SERVER_LANGUAGE_TOOLTIP),
1283 EndContainer(),
1284 NWidget(NWID_VERTICAL), SetPIP(0, 1, 0),
1285 NWidget(NWID_SPACER), SetFill(1, 1),
1286 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NSS_SETPWD), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_SET_PASSWORD, STR_NETWORK_START_SERVER_PASSWORD_TOOLTIP),
1287 EndContainer(),
1288 EndContainer(),
1289
1290 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 6, 10),
1291 NWidget(NWID_VERTICAL), SetPIP(0, 1, 0),
1292 NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, WID_NSS_CLIENTS_LABEL), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_NUMBER_OF_CLIENTS, STR_NULL),
1293 NWidget(NWID_HORIZONTAL),
1294 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),
1295 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),
1296 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),
1297 EndContainer(),
1298 EndContainer(),
1299
1300 NWidget(NWID_VERTICAL), SetPIP(0, 1, 0),
1301 NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, WID_NSS_COMPANIES_LABEL), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_NUMBER_OF_COMPANIES, STR_NULL),
1302 NWidget(NWID_HORIZONTAL),
1303 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),
1304 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),
1305 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),
1306 EndContainer(),
1307 EndContainer(),
1308
1309 NWidget(NWID_VERTICAL), SetPIP(0, 1, 0),
1310 NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, WID_NSS_SPECTATORS_LABEL), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_NUMBER_OF_SPECTATORS, STR_NULL),
1311 NWidget(NWID_HORIZONTAL),
1312 NWidget(WWT_IMGBTN, COLOUR_LIGHT_BLUE, WID_NSS_SPECTATORS_BTND), SetMinimalSize(12, 12), SetFill(0, 1), SetDataTip(SPR_ARROW_DOWN, STR_NETWORK_START_SERVER_NUMBER_OF_SPECTATORS_TOOLTIP),
1313 NWidget(WWT_PUSHTXTBTN, COLOUR_LIGHT_BLUE, WID_NSS_SPECTATORS_TXT), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_SPECTATORS_SELECT, STR_NETWORK_START_SERVER_NUMBER_OF_SPECTATORS_TOOLTIP),
1314 NWidget(WWT_IMGBTN, COLOUR_LIGHT_BLUE, WID_NSS_SPECTATORS_BTNU), SetMinimalSize(12, 12), SetFill(0, 1), SetDataTip(SPR_ARROW_UP, STR_NETWORK_START_SERVER_NUMBER_OF_SPECTATORS_TOOLTIP),
1315 EndContainer(),
1316 EndContainer(),
1317 EndContainer(),
1318
1319 /* 'generate game' and 'load game' buttons */
1320 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 6, 10),
1321 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NSS_GENERATE_GAME), SetDataTip(STR_INTRO_NEW_GAME, STR_INTRO_TOOLTIP_NEW_GAME), SetFill(1, 0),
1322 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NSS_LOAD_GAME), SetDataTip(STR_INTRO_LOAD_GAME, STR_INTRO_TOOLTIP_LOAD_GAME), SetFill(1, 0),
1323 EndContainer(),
1324
1325 /* 'play scenario' and 'play heightmap' buttons */
1326 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 6, 10),
1327 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NSS_PLAY_SCENARIO), SetDataTip(STR_INTRO_PLAY_SCENARIO, STR_INTRO_TOOLTIP_PLAY_SCENARIO), SetFill(1, 0),
1328 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NSS_PLAY_HEIGHTMAP), SetDataTip(STR_INTRO_PLAY_HEIGHTMAP, STR_INTRO_TOOLTIP_PLAY_HEIGHTMAP), SetFill(1, 0),
1329 EndContainer(),
1330
1331 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 0, 10),
1332 NWidget(NWID_SPACER), SetFill(1, 0),
1333 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NSS_CANCEL), SetDataTip(STR_BUTTON_CANCEL, STR_NULL), SetMinimalSize(128, 12),
1334 NWidget(NWID_SPACER), SetFill(1, 0),
1335 EndContainer(),
1336 EndContainer(),
1337 EndContainer(),
1338 };
1339
1340 static WindowDesc _network_start_server_window_desc(
1341 WDP_CENTER, NULL, 0, 0,
1342 WC_NETWORK_WINDOW, WC_NONE,
1343 0,
1344 _nested_network_start_server_window_widgets, lengthof(_nested_network_start_server_window_widgets)
1345 );
1346
1347 static void ShowNetworkStartServerWindow()
1348 {
1349 DeleteWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_GAME);
1350 DeleteWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_LOBBY);
1351
1352 new NetworkStartServerWindow(&_network_start_server_window_desc);
1353 }
1354
1355 struct NetworkLobbyWindow : public Window {
1356 CompanyID company;
1357 NetworkGameList *server;
1358 NetworkCompanyInfo company_info[MAX_COMPANIES];
1359 Scrollbar *vscroll;
1360
1361 NetworkLobbyWindow(WindowDesc *desc, NetworkGameList *ngl) :
1362 Window(desc), company(INVALID_COMPANY), server(ngl)
1363 {
1364 this->CreateNestedTree();
1365 this->vscroll = this->GetScrollbar(WID_NL_SCROLLBAR);
1366 this->FinishInitNested(WN_NETWORK_WINDOW_LOBBY);
1367 }
1368
1369 CompanyID NetworkLobbyFindCompanyIndex(byte pos) const
1370 {
1371 /* Scroll through all this->company_info and get the 'pos' item that is not empty. */
1372 for (CompanyID i = COMPANY_FIRST; i < MAX_COMPANIES; i++) {
1373 if (!StrEmpty(this->company_info[i].company_name)) {
1374 if (pos-- == 0) return i;
1375 }
1376 }
1377
1378 return COMPANY_FIRST;
1379 }
1380
1381 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
1382 {
1383 switch (widget) {
1384 case WID_NL_HEADER:
1385 size->height = WD_MATRIX_TOP + FONT_HEIGHT_NORMAL + WD_MATRIX_BOTTOM;
1386 break;
1387
1388 case WID_NL_MATRIX:
1389 resize->height = WD_MATRIX_TOP + FONT_HEIGHT_NORMAL + WD_MATRIX_BOTTOM;
1390 size->height = 10 * resize->height;
1391 break;
1392
1393 case WID_NL_DETAILS:
1394 size->height = 30 + 11 * FONT_HEIGHT_NORMAL;
1395 break;
1396 }
1397 }
1398
1399 virtual void SetStringParameters(int widget) const
1400 {
1401 switch (widget) {
1402 case WID_NL_TEXT:
1403 SetDParamStr(0, this->server->info.server_name);
1404 break;
1405 }
1406 }
1407
1408 virtual void DrawWidget(const Rect &r, int widget) const
1409 {
1410 switch (widget) {
1411 case WID_NL_DETAILS:
1412 this->DrawDetails(r);
1413 break;
1414
1415 case WID_NL_MATRIX:
1416 this->DrawMatrix(r);
1417 break;
1418 }
1419 }
1420
1421 virtual void OnPaint()
1422 {
1423 const NetworkGameInfo *gi = &this->server->info;
1424
1425 /* Join button is disabled when no company is selected and for AI companies. */
1426 this->SetWidgetDisabledState(WID_NL_JOIN, this->company == INVALID_COMPANY || GetLobbyCompanyInfo(this->company)->ai);
1427 /* Cannot start new company if there are too many. */
1428 this->SetWidgetDisabledState(WID_NL_NEW, gi->companies_on >= gi->companies_max);
1429 /* Cannot spectate if there are too many spectators. */
1430 this->SetWidgetDisabledState(WID_NL_SPECTATE, gi->spectators_on >= gi->spectators_max);
1431
1432 this->vscroll->SetCount(gi->companies_on);
1433
1434 /* Draw window widgets */
1435 this->DrawWidgets();
1436 }
1437
1438 void DrawMatrix(const Rect &r) const
1439 {
1440 bool rtl = _current_text_dir == TD_RTL;
1441 uint left = r.left + WD_FRAMERECT_LEFT;
1442 uint right = r.right - WD_FRAMERECT_RIGHT;
1443
1444 Dimension lock_size = GetSpriteSize(SPR_LOCK);
1445 int lock_width = lock_size.width;
1446 int lock_y_offset = (this->resize.step_height - WD_MATRIX_TOP - WD_MATRIX_BOTTOM - lock_size.height) / 2;
1447
1448 Dimension profit_size = GetSpriteSize(SPR_PROFIT_LOT);
1449 int profit_width = lock_size.width;
1450 int profit_y_offset = (this->resize.step_height - WD_MATRIX_TOP - WD_MATRIX_BOTTOM - profit_size.height) / 2;
1451
1452 uint text_left = left + (rtl ? lock_width + profit_width + 4 : 0);
1453 uint text_right = right - (rtl ? 0 : lock_width + profit_width + 4);
1454 uint profit_left = rtl ? left : right - profit_width;
1455 uint lock_left = rtl ? left + profit_width + 2 : right - profit_width - lock_width - 2;
1456
1457 int y = r.top + WD_MATRIX_TOP;
1458 /* Draw company list */
1459 int pos = this->vscroll->GetPosition();
1460 while (pos < this->server->info.companies_on) {
1461 byte company = NetworkLobbyFindCompanyIndex(pos);
1462 bool income = false;
1463 if (this->company == company) {
1464 GfxFillRect(r.left + 1, y - 2, r.right - 1, y + FONT_HEIGHT_NORMAL, PC_GREY); // show highlighted item with a different colour
1465 }
1466
1467 DrawString(text_left, text_right, y, this->company_info[company].company_name, TC_BLACK);
1468 if (this->company_info[company].use_password != 0) DrawSprite(SPR_LOCK, PAL_NONE, lock_left, y + lock_y_offset);
1469
1470 /* If the company's income was positive puts a green dot else a red dot */
1471 if (this->company_info[company].income >= 0) income = true;
1472 DrawSprite(income ? SPR_PROFIT_LOT : SPR_PROFIT_NEGATIVE, PAL_NONE, profit_left, y + profit_y_offset);
1473
1474 pos++;
1475 y += this->resize.step_height;
1476 if (pos >= this->vscroll->GetPosition() + this->vscroll->GetCapacity()) break;
1477 }
1478 }
1479
1480 void DrawDetails(const Rect &r) const
1481 {
1482 const int detail_height = 12 + FONT_HEIGHT_NORMAL + 12;
1483 /* Draw info about selected company when it is selected in the left window. */
1484 GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.top + detail_height - 1, PC_DARK_BLUE);
1485 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);
1486
1487 if (this->company == INVALID_COMPANY || StrEmpty(this->company_info[this->company].company_name)) return;
1488
1489 int y = r.top + detail_height + 4;
1490 const NetworkGameInfo *gi = &this->server->info;
1491
1492 SetDParam(0, gi->clients_on);
1493 SetDParam(1, gi->clients_max);
1494 SetDParam(2, gi->companies_on);
1495 SetDParam(3, gi->companies_max);
1496 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_CLIENTS);
1497 y += FONT_HEIGHT_NORMAL;
1498
1499 SetDParamStr(0, this->company_info[this->company].company_name);
1500 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_COMPANY_NAME);
1501 y += FONT_HEIGHT_NORMAL;
1502
1503 SetDParam(0, this->company_info[this->company].inaugurated_year);
1504 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_INAUGURATION_YEAR); // inauguration year
1505 y += FONT_HEIGHT_NORMAL;
1506
1507 SetDParam(0, this->company_info[this->company].company_value);
1508 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_VALUE); // company value
1509 y += FONT_HEIGHT_NORMAL;
1510
1511 SetDParam(0, this->company_info[this->company].money);
1512 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_CURRENT_BALANCE); // current balance
1513 y += FONT_HEIGHT_NORMAL;
1514
1515 SetDParam(0, this->company_info[this->company].income);
1516 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_LAST_YEARS_INCOME); // last year's income
1517 y += FONT_HEIGHT_NORMAL;
1518
1519 SetDParam(0, this->company_info[this->company].performance);
1520 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_PERFORMANCE); // performance
1521 y += FONT_HEIGHT_NORMAL;
1522
1523 SetDParam(0, this->company_info[this->company].num_vehicle[NETWORK_VEH_TRAIN]);
1524 SetDParam(1, this->company_info[this->company].num_vehicle[NETWORK_VEH_LORRY]);
1525 SetDParam(2, this->company_info[this->company].num_vehicle[NETWORK_VEH_BUS]);
1526 SetDParam(3, this->company_info[this->company].num_vehicle[NETWORK_VEH_SHIP]);
1527 SetDParam(4, this->company_info[this->company].num_vehicle[NETWORK_VEH_PLANE]);
1528 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_VEHICLES); // vehicles
1529 y += FONT_HEIGHT_NORMAL;
1530
1531 SetDParam(0, this->company_info[this->company].num_station[NETWORK_VEH_TRAIN]);
1532 SetDParam(1, this->company_info[this->company].num_station[NETWORK_VEH_LORRY]);
1533 SetDParam(2, this->company_info[this->company].num_station[NETWORK_VEH_BUS]);
1534 SetDParam(3, this->company_info[this->company].num_station[NETWORK_VEH_SHIP]);
1535 SetDParam(4, this->company_info[this->company].num_station[NETWORK_VEH_PLANE]);
1536 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_STATIONS); // stations
1537 y += FONT_HEIGHT_NORMAL;
1538
1539 SetDParamStr(0, this->company_info[this->company].clients);
1540 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_PLAYERS); // players
1541 }
1542
1543 virtual void OnClick(Point pt, int widget, int click_count)
1544 {
1545 switch (widget) {
1546 case WID_NL_CANCEL: // Cancel button
1547 ShowNetworkGameWindow();
1548 break;
1549
1550 case WID_NL_MATRIX: { // Company list
1551 uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NL_MATRIX);
1552 this->company = (id_v >= this->server->info.companies_on) ? INVALID_COMPANY : NetworkLobbyFindCompanyIndex(id_v);
1553 this->SetDirty();
1554
1555 /* FIXME the disabling should go into some InvalidateData, which is called instead of the SetDirty */
1556 if (click_count > 1 && !this->IsWidgetDisabled(WID_NL_JOIN)) this->OnClick(pt, WID_NL_JOIN, 1);
1557 break;
1558 }
1559
1560 case WID_NL_JOIN: // Join company
1561 /* Button can be clicked only when it is enabled. */
1562 NetworkClientConnectGame(NetworkAddress(_settings_client.network.last_host, _settings_client.network.last_port), this->company);
1563 break;
1564
1565 case WID_NL_NEW: // New company
1566 NetworkClientConnectGame(NetworkAddress(_settings_client.network.last_host, _settings_client.network.last_port), COMPANY_NEW_COMPANY);
1567 break;
1568
1569 case WID_NL_SPECTATE: // Spectate game
1570 NetworkClientConnectGame(NetworkAddress(_settings_client.network.last_host, _settings_client.network.last_port), COMPANY_SPECTATOR);
1571 break;
1572
1573 case WID_NL_REFRESH: // Refresh
1574 NetworkTCPQueryServer(NetworkAddress(_settings_client.network.last_host, _settings_client.network.last_port)); // company info
1575 NetworkUDPQueryServer(NetworkAddress(_settings_client.network.last_host, _settings_client.network.last_port)); // general data
1576 /* Clear the information so removed companies don't remain */
1577 memset(this->company_info, 0, sizeof(this->company_info));
1578 break;
1579 }
1580 }
1581
1582 virtual void OnResize()
1583 {
1584 this->vscroll->SetCapacityFromWidget(this, WID_NL_MATRIX);
1585 }
1586 };
1587
1588 static const NWidgetPart _nested_network_lobby_window_widgets[] = {
1589 NWidget(NWID_HORIZONTAL),
1590 NWidget(WWT_CLOSEBOX, COLOUR_LIGHT_BLUE),
1591 NWidget(WWT_CAPTION, COLOUR_LIGHT_BLUE), SetDataTip(STR_NETWORK_GAME_LOBBY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1592 EndContainer(),
1593 NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, WID_NL_BACKGROUND),
1594 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),
1595 NWidget(NWID_SPACER), SetMinimalSize(0, 3),
1596 NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 10),
1597 /* Company list. */
1598 NWidget(NWID_VERTICAL),
1599 NWidget(WWT_PANEL, COLOUR_WHITE, WID_NL_HEADER), SetMinimalSize(146, 0), SetResize(1, 0), SetFill(1, 0), EndContainer(),
1600 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),
1601 EndContainer(),
1602 NWidget(NWID_VSCROLLBAR, COLOUR_LIGHT_BLUE, WID_NL_SCROLLBAR),
1603 NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetResize(0, 1),
1604 /* Company info. */
1605 NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, WID_NL_DETAILS), SetMinimalSize(232, 0), SetResize(1, 1), SetFill(1, 1), EndContainer(),
1606 EndContainer(),
1607 NWidget(NWID_SPACER), SetMinimalSize(0, 9),
1608 /* Buttons. */
1609 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 3, 10),
1610 NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, 3, 0),
1611 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),
1612 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),
1613 EndContainer(),
1614 NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, 3, 0),
1615 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),
1616 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),
1617 EndContainer(),
1618 NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, 3, 0),
1619 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NL_CANCEL), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_BUTTON_CANCEL, STR_NULL),
1620 NWidget(NWID_SPACER), SetFill(1, 1),
1621 EndContainer(),
1622 EndContainer(),
1623 NWidget(NWID_SPACER), SetMinimalSize(0, 8),
1624 EndContainer(),
1625 };
1626
1627 static WindowDesc _network_lobby_window_desc(
1628 WDP_CENTER, NULL, 0, 0,
1629 WC_NETWORK_WINDOW, WC_NONE,
1630 0,
1631 _nested_network_lobby_window_widgets, lengthof(_nested_network_lobby_window_widgets)
1632 );
1633
1638 static void ShowNetworkLobbyWindow(NetworkGameList *ngl)
1639 {
1640 DeleteWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_START);
1641 DeleteWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_GAME);
1642
1643 NetworkTCPQueryServer(NetworkAddress(_settings_client.network.last_host, _settings_client.network.last_port)); // company info
1644 NetworkUDPQueryServer(NetworkAddress(_settings_client.network.last_host, _settings_client.network.last_port)); // general data
1645
1646 new NetworkLobbyWindow(&_network_lobby_window_desc, ngl);
1647 }
1648
1654 NetworkCompanyInfo *GetLobbyCompanyInfo(CompanyID company)
1655 {
1656 NetworkLobbyWindow *lobby = dynamic_cast<NetworkLobbyWindow*>(FindWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_LOBBY));
1657 return (lobby != NULL && company < MAX_COMPANIES) ? &lobby->company_info[company] : NULL;
1658 }
1659
1660 /* The window below gives information about the connected clients
1661 * and also makes able to give money to them, kick them (if server)
1662 * and stuff like that. */
1663
1664 extern void DrawCompanyIcon(CompanyID cid, int x, int y);
1665
1670 typedef void ClientList_Action_Proc(const NetworkClientInfo *ci);
1671
1672 static const NWidgetPart _nested_client_list_popup_widgets[] = {
1673 NWidget(WWT_PANEL, COLOUR_GREY, WID_CLP_PANEL), EndContainer(),
1674 };
1675
1676 static WindowDesc _client_list_popup_desc(
1677 WDP_AUTO, NULL, 0, 0,
1678 WC_CLIENT_LIST_POPUP, WC_CLIENT_LIST,
1679 0,
1680 _nested_client_list_popup_widgets, lengthof(_nested_client_list_popup_widgets)
1681 );
1682
1683 /* Here we start to define the options out of the menu */
1684 static void ClientList_Kick(const NetworkClientInfo *ci)
1685 {
1686 NetworkServerKickClient(ci->client_id);
1687 }
1688
1689 static void ClientList_Ban(const NetworkClientInfo *ci)
1690 {
1691 NetworkServerKickOrBanIP(ci->client_id, true);
1692 }
1693
1694 static void ClientList_GiveMoney(const NetworkClientInfo *ci)
1695 {
1696 ShowNetworkGiveMoneyWindow(ci->client_playas);
1697 }
1698
1699 static void ClientList_SpeakToClient(const NetworkClientInfo *ci)
1700 {
1701 ShowNetworkChatQueryWindow(DESTTYPE_CLIENT, ci->client_id);
1702 }
1703
1704 static void ClientList_SpeakToCompany(const NetworkClientInfo *ci)
1705 {
1706 ShowNetworkChatQueryWindow(DESTTYPE_TEAM, ci->client_playas);
1707 }
1708
1709 static void ClientList_SpeakToAll(const NetworkClientInfo *ci)
1710 {
1711 ShowNetworkChatQueryWindow(DESTTYPE_BROADCAST, 0);
1712 }
1713
1715 struct NetworkClientListPopupWindow : Window {
1717 struct ClientListAction {
1718 StringID name;
1719 ClientList_Action_Proc *proc;
1720 };
1721
1722 uint sel_index;
1723 ClientID client_id;
1724 Point desired_location;
1725 SmallVector<ClientListAction, 2> actions;
1726
1732 inline void AddAction(StringID name, ClientList_Action_Proc *proc)
1733 {
1734 ClientListAction *action = this->actions.Append();
1735 action->name = name;
1736 action->proc = proc;
1737 }
1738
1739 NetworkClientListPopupWindow(WindowDesc *desc, int x, int y, ClientID client_id) :
1740 Window(desc),
1741 sel_index(0), client_id(client_id)
1742 {
1743 this->desired_location.x = x;
1744 this->desired_location.y = y;
1745
1746 const NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(client_id);
1747
1748 if (_network_own_client_id != ci->client_id) {
1749 this->AddAction(STR_NETWORK_CLIENTLIST_SPEAK_TO_CLIENT, &ClientList_SpeakToClient);
1750 }
1751
1752 if (Company::IsValidID(ci->client_playas) || ci->client_playas == COMPANY_SPECTATOR) {
1753 this->AddAction(STR_NETWORK_CLIENTLIST_SPEAK_TO_COMPANY, &ClientList_SpeakToCompany);
1754 }
1755 this->AddAction(STR_NETWORK_CLIENTLIST_SPEAK_TO_ALL, &ClientList_SpeakToAll);
1756
1757 if (_network_own_client_id != ci->client_id) {
1758 /* We are no spectator and the company we want to give money to is no spectator and money gifts are allowed. */
1759 if (Company::IsValidID(_local_company) && Company::IsValidID(ci->client_playas) && _settings_game.economy.give_money) {
1760 this->AddAction(STR_NETWORK_CLIENTLIST_GIVE_MONEY, &ClientList_GiveMoney);
1761 }
1762 }
1763
1764 /* A server can kick clients (but not himself). */
1765 if (_network_server && _network_own_client_id != ci->client_id) {
1766 this->AddAction(STR_NETWORK_CLIENTLIST_KICK, &ClientList_Kick);
1767 this->AddAction(STR_NETWORK_CLIENTLIST_BAN, &ClientList_Ban);
1768 }
1769
1770 this->InitNested(client_id);
1771 CLRBITS(this->flags, WF_WHITE_BORDER);
1772 }
1773
1774 virtual Point OnInitialPosition(int16 sm_width, int16 sm_height, int window_number)
1775 {
1776 return this->desired_location;
1777 }
1778
1779 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
1780 {
1781 Dimension d = *size;
1782 for (const ClientListAction *action = this->actions.Begin(); action != this->actions.End(); action++) {
1783 d = maxdim(GetStringBoundingBox(action->name), d);
1784 }
1785
1786 d.height *= this->actions.Length();
1787 d.width += WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
1788 d.height += WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
1789 *size = d;
1790 }
1791
1792 virtual void DrawWidget(const Rect &r, int widget) const
1793 {
1794 /* Draw the actions */
1795 int sel = this->sel_index;
1796 int y = r.top + WD_FRAMERECT_TOP;
1797 for (const ClientListAction *action = this->actions.Begin(); action != this->actions.End(); action++, y += FONT_HEIGHT_NORMAL) {
1798 TextColour colour;
1799 if (sel-- == 0) { // Selected item, highlight it
1800 GfxFillRect(r.left + 1, y, r.right - 1, y + FONT_HEIGHT_NORMAL - 1, PC_BLACK);
1801 colour = TC_WHITE;
1802 } else {
1803 colour = TC_BLACK;
1804 }
1805
1806 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, action->name, colour);
1807 }
1808 }
1809
1810 virtual void OnMouseLoop()
1811 {
1812 /* We selected an action */
1813 uint index = (_cursor.pos.y - this->top - WD_FRAMERECT_TOP) / FONT_HEIGHT_NORMAL;
1814
1815 if (_left_button_down) {
1816 if (index == this->sel_index || index >= this->actions.Length()) return;
1817
1818 this->sel_index = index;
1819 this->SetDirty();
1820 } else {
1821 if (index < this->actions.Length() && _cursor.pos.y >= this->top) {
1822 const NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(this->client_id);
1823 if (ci != NULL) this->actions[index].proc(ci);
1824 }
1825
1826 DeleteWindowByClass(WC_CLIENT_LIST_POPUP);
1827 }
1828 }
1829 };
1830
1834 static void PopupClientList(ClientID client_id, int x, int y)
1835 {
1836 DeleteWindowByClass(WC_CLIENT_LIST_POPUP);
1837
1838 if (NetworkClientInfo::GetByClientID(client_id) == NULL) return;
1839
1840 new NetworkClientListPopupWindow(&_client_list_popup_desc, x, y, client_id);
1841 }
1842
1843 static const NWidgetPart _nested_client_list_widgets[] = {
1844 NWidget(NWID_HORIZONTAL),
1845 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
1846 NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_NETWORK_COMPANY_LIST_CLIENT_LIST, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1847 NWidget(WWT_STICKYBOX, COLOUR_GREY),
1848 EndContainer(),
1849 NWidget(WWT_PANEL, COLOUR_GREY, WID_CL_PANEL), SetMinimalSize(250, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM), SetResize(1, 1), EndContainer(),
1850 };
1851
1852 static WindowDesc _client_list_desc(
1853 WDP_AUTO, "list_clients", 0, 0,
1854 WC_CLIENT_LIST, WC_NONE,
1855 0,
1856 _nested_client_list_widgets, lengthof(_nested_client_list_widgets)
1857 );
1858
1862 struct NetworkClientListWindow : Window {
1863 int selected_item;
1864
1865 uint server_client_width;
1866 uint line_height;
1867
1868 Dimension icon_size;
1869
1870 NetworkClientListWindow(WindowDesc *desc, WindowNumber window_number) :
1871 Window(desc),
1872 selected_item(-1)
1873 {
1874 this->InitNested(window_number);
1875 }
1876
1880 bool CheckClientListHeight()
1881 {
1882 int num = 0;
1883 const NetworkClientInfo *ci;
1884
1885 /* Should be replaced with a loop through all clients */
1886 FOR_ALL_CLIENT_INFOS(ci) {
1887 if (ci->client_playas != COMPANY_INACTIVE_CLIENT) num++;
1888 }
1889
1890 num *= this->line_height;
1891
1892 int diff = (num + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM) - (this->GetWidget<NWidgetBase>(WID_CL_PANEL)->current_y);
1893 /* If height is changed */
1894 if (diff != 0) {
1895 ResizeWindow(this, 0, diff, false);
1896 return false;
1897 }
1898 return true;
1899 }
1900
1901 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
1902 {
1903 if (widget != WID_CL_PANEL) return;
1904
1905 this->server_client_width = max(GetStringBoundingBox(STR_NETWORK_SERVER).width, GetStringBoundingBox(STR_NETWORK_CLIENT).width) + WD_FRAMERECT_RIGHT;
1906 this->icon_size = GetSpriteSize(SPR_COMPANY_ICON);
1907 this->line_height = max(this->icon_size.height + 2U, (uint)FONT_HEIGHT_NORMAL);
1908
1909 uint width = 100; // Default width
1910 const NetworkClientInfo *ci;
1911 FOR_ALL_CLIENT_INFOS(ci) {
1912 width = max(width, GetStringBoundingBox(ci->client_name).width);
1913 }
1914
1915 size->width = WD_FRAMERECT_LEFT + this->server_client_width + this->icon_size.width + WD_FRAMERECT_LEFT + width + WD_FRAMERECT_RIGHT;
1916 }
1917
1918 virtual void OnPaint()
1919 {
1920 /* Check if we need to reset the height */
1921 if (!this->CheckClientListHeight()) return;
1922
1923 this->DrawWidgets();
1924 }
1925
1926 virtual void DrawWidget(const Rect &r, int widget) const
1927 {
1928 if (widget != WID_CL_PANEL) return;
1929
1930 bool rtl = _current_text_dir == TD_RTL;
1931 int icon_offset = (this->line_height - icon_size.height) / 2;
1932 int text_offset = (this->line_height - FONT_HEIGHT_NORMAL) / 2;
1933
1934 uint y = r.top + WD_FRAMERECT_TOP;
1935 uint left = r.left + WD_FRAMERECT_LEFT;
1936 uint right = r.right - WD_FRAMERECT_RIGHT;
1937 uint type_icon_width = this->server_client_width + this->icon_size.width + WD_FRAMERECT_LEFT;
1938
1939
1940 uint type_left = rtl ? right - this->server_client_width : left;
1941 uint type_right = rtl ? right : left + this->server_client_width - 1;
1942 uint icon_left = rtl ? right - type_icon_width + WD_FRAMERECT_LEFT : left + this->server_client_width;
1943 uint name_left = rtl ? left : left + type_icon_width;
1944 uint name_right = rtl ? right - type_icon_width : right;
1945
1946 int i = 0;
1947 const NetworkClientInfo *ci;
1948 FOR_ALL_CLIENT_INFOS(ci) {
1949 TextColour colour;
1950 if (this->selected_item == i++) { // Selected item, highlight it
1951 GfxFillRect(r.left + 1, y, r.right - 1, y + this->line_height - 1, PC_BLACK);
1952 colour = TC_WHITE;
1953 } else {
1954 colour = TC_BLACK;
1955 }
1956
1957 if (ci->client_id == CLIENT_ID_SERVER) {
1958 DrawString(type_left, type_right, y + text_offset, STR_NETWORK_SERVER, colour);
1959 } else {
1960 DrawString(type_left, type_right, y + text_offset, STR_NETWORK_CLIENT, colour);
1961 }
1962
1963 /* Filter out spectators */
1964 if (Company::IsValidID(ci->client_playas)) DrawCompanyIcon(ci->client_playas, icon_left, y + icon_offset);
1965
1966 DrawString(name_left, name_right, y + text_offset, ci->client_name, colour);
1967
1968 y += line_height;
1969 }
1970 }
1971
1972 virtual void OnClick(Point pt, int widget, int click_count)
1973 {
1974 /* Show the popup with option */
1975 if (this->selected_item != -1) {
1976 NetworkClientInfo *ci;
1977
1978 int client_no = this->selected_item;
1979 FOR_ALL_CLIENT_INFOS(ci) {
1980 if (client_no == 0) break;
1981 client_no--;
1982 }
1983
1984 if (ci != NULL) PopupClientList(ci->client_id, pt.x + this->left, pt.y + this->top);
1985 }
1986 }
1987
1988 virtual void OnMouseOver(Point pt, int widget)
1989 {
1990 /* -1 means we left the current window */
1991 if (pt.y == -1) {
1992 this->selected_item = -1;
1993 this->SetDirty();
1994 return;
1995 }
1996
1997 /* Find the new selected item (if any) */
1998 pt.y -= this->GetWidget<NWidgetBase>(WID_CL_PANEL)->pos_y;
1999 int item = -1;
2000 if (IsInsideMM(pt.y, WD_FRAMERECT_TOP, this->GetWidget<NWidgetBase>(WID_CL_PANEL)->current_y - WD_FRAMERECT_BOTTOM)) {
2001 item = (pt.y - WD_FRAMERECT_TOP) / this->line_height;
2002 }
2003
2004 /* It did not change.. no update! */
2005 if (item == this->selected_item) return;
2006 this->selected_item = item;
2007
2008 /* Repaint */
2009 this->SetDirty();
2010 }
2011 };
2012
2013 void ShowClientList()
2014 {
2015 AllocateWindowDescFront<NetworkClientListWindow>(&_client_list_desc, 0);
2016 }
2017
2018 NetworkJoinStatus _network_join_status;
2019 uint8 _network_join_waiting;
2020 uint32 _network_join_bytes;
2021 uint32 _network_join_bytes_total;
2022
2023 struct NetworkJoinStatusWindow : Window {
2024 NetworkPasswordType password_type;
2025
2026 NetworkJoinStatusWindow(WindowDesc *desc) : Window(desc)
2027 {
2028 this->parent = FindWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_GAME);
2029 this->InitNested(WN_NETWORK_STATUS_WINDOW_JOIN);
2030 }
2031
2032 virtual void DrawWidget(const Rect &r, int widget) const
2033 {
2034 if (widget != WID_NJS_BACKGROUND) return;
2035
2036 uint8 progress; // used for progress bar
2037 DrawString(r.left + 2, r.right - 2, r.top + 20, STR_NETWORK_CONNECTING_1 + _network_join_status, TC_FROMSTRING, SA_HOR_CENTER);
2038 switch (_network_join_status) {
2039 case NETWORK_JOIN_STATUS_CONNECTING: case NETWORK_JOIN_STATUS_AUTHORIZING:
2040 case NETWORK_JOIN_STATUS_GETTING_COMPANY_INFO:
2041 progress = 10; // first two stages 10%
2042 break;
2043 case NETWORK_JOIN_STATUS_WAITING:
2044 SetDParam(0, _network_join_waiting);
2045 DrawString(r.left + 2, r.right - 2, r.top + 20 + FONT_HEIGHT_NORMAL, STR_NETWORK_CONNECTING_WAITING, TC_FROMSTRING, SA_HOR_CENTER);
2046 progress = 15; // third stage is 15%
2047 break;
2048 case NETWORK_JOIN_STATUS_DOWNLOADING:
2049 SetDParam(0, _network_join_bytes);
2050 SetDParam(1, _network_join_bytes_total);
2051 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);
2052 if (_network_join_bytes_total == 0) {
2053 progress = 15; // We don't have the final size yet; the server is still compressing!
2054 break;
2055 }
2056 /* FALL THROUGH */
2057 default: // Waiting is 15%, so the resting receivement of map is maximum 70%
2058 progress = 15 + _network_join_bytes * (100 - 15) / _network_join_bytes_total;
2059 }
2060
2061 /* Draw nice progress bar :) */
2062 DrawFrameRect(r.left + 20, r.top + 5, (int)((this->width - 20) * progress / 100), r.top + 15, COLOUR_MAUVE, FR_NONE);
2063 }
2064
2065 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
2066 {
2067 if (widget != WID_NJS_BACKGROUND) return;
2068
2069 size->height = 25 + 2 * FONT_HEIGHT_NORMAL;
2070
2071 /* Account for the statuses */
2072 uint width = 0;
2073 for (uint i = 0; i < NETWORK_JOIN_STATUS_END; i++) {
2074 width = max(width, GetStringBoundingBox(STR_NETWORK_CONNECTING_1 + i).width);
2075 }
2076
2077 /* For the number of waiting (other) players */
2078 SetDParamMaxValue(0, MAX_CLIENTS);
2079 width = max(width, GetStringBoundingBox(STR_NETWORK_CONNECTING_WAITING).width);
2080
2081 /* Account for downloading ~ 10 MiB */
2082 SetDParamMaxDigits(0, 8);
2083 SetDParamMaxDigits(1, 8);
2084 width = max(width, GetStringBoundingBox(STR_NETWORK_CONNECTING_DOWNLOADING_1).width);
2085 width = max(width, GetStringBoundingBox(STR_NETWORK_CONNECTING_DOWNLOADING_2).width);
2086
2087 /* Give a bit more clearing for the widest strings than strictly needed */
2088 size->width = width + WD_FRAMERECT_LEFT + WD_FRAMERECT_BOTTOM + 10;
2089 }
2090
2091 virtual void OnClick(Point pt, int widget, int click_count)
2092 {
2093 if (widget == WID_NJS_CANCELOK) { // Disconnect button
2094 NetworkDisconnect();
2095 SwitchToMode(SM_MENU);
2096 ShowNetworkGameWindow();
2097 }
2098 }
2099
2100 virtual void OnQueryTextFinished(char *str)
2101 {
2102 if (StrEmpty(str)) {
2103 NetworkDisconnect();
2104 ShowNetworkGameWindow();
2105 return;
2106 }
2107
2108 switch (this->password_type) {
2109 case NETWORK_GAME_PASSWORD: MyClient::SendGamePassword (str); break;
2110 case NETWORK_COMPANY_PASSWORD: MyClient::SendCompanyPassword(str); break;
2111 default: NOT_REACHED();
2112 }
2113 }
2114 };
2115
2116 static const NWidgetPart _nested_network_join_status_window_widgets[] = {
2117 NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_NETWORK_CONNECTING_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2118 NWidget(WWT_PANEL, COLOUR_GREY),
2119 NWidget(WWT_EMPTY, COLOUR_GREY, WID_NJS_BACKGROUND),
2120 NWidget(NWID_HORIZONTAL),
2121 NWidget(NWID_SPACER), SetMinimalSize(75, 0), SetFill(1, 0),
2122 NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NJS_CANCELOK), SetMinimalSize(101, 12), SetDataTip(STR_NETWORK_CONNECTION_DISCONNECT, STR_NULL),
2123 NWidget(NWID_SPACER), SetMinimalSize(75, 0), SetFill(1, 0),
2124 EndContainer(),
2125 NWidget(NWID_SPACER), SetMinimalSize(0, 4),
2126 EndContainer(),
2127 };
2128
2129 static WindowDesc _network_join_status_window_desc(
2130 WDP_CENTER, NULL, 0, 0,
2131 WC_NETWORK_STATUS_WINDOW, WC_NONE,
2132 WDF_MODAL,
2133 _nested_network_join_status_window_widgets, lengthof(_nested_network_join_status_window_widgets)
2134 );
2135
2136 void ShowJoinStatusWindow()
2137 {
2138 DeleteWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN);
2139 new NetworkJoinStatusWindow(&_network_join_status_window_desc);
2140 }
2141
2142 void ShowNetworkNeedPassword(NetworkPasswordType npt)
2143 {
2144 NetworkJoinStatusWindow *w = (NetworkJoinStatusWindow *)FindWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN);
2145 if (w == NULL) return;
2146 w->password_type = npt;
2147
2148 StringID caption;
2149 switch (npt) {
2150 default: NOT_REACHED();
2151 case NETWORK_GAME_PASSWORD: caption = STR_NETWORK_NEED_GAME_PASSWORD_CAPTION; break;
2152 case NETWORK_COMPANY_PASSWORD: caption = STR_NETWORK_NEED_COMPANY_PASSWORD_CAPTION; break;
2153 }
2154 ShowQueryString(STR_EMPTY, caption, NETWORK_PASSWORD_LENGTH, w, CS_ALPHANUMERAL, QSF_NONE);
2155 }
2156
2157 struct NetworkCompanyPasswordWindow : public Window {
2158 QueryString password_editbox;
2159
2160 NetworkCompanyPasswordWindow(WindowDesc *desc, Window *parent) : Window(desc), password_editbox(lengthof(_settings_client.network.default_company_pass))
2161 {
2162 this->InitNested(0);
2163
2164 this->parent = parent;
2165 this->querystrings[WID_NCP_PASSWORD] = &this->password_editbox;
2166 this->password_editbox.cancel_button = WID_NCP_CANCEL;
2167 this->password_editbox.ok_button = WID_NCP_OK;
2168 this->SetFocusedWidget(WID_NCP_PASSWORD);
2169 }
2170
2171 void OnOk()
2172 {
2173 if (this->IsWidgetLowered(WID_NCP_SAVE_AS_DEFAULT_PASSWORD)) {
2174 strecpy(_settings_client.network.default_company_pass, this->password_editbox.text.buf, lastof(_settings_client.network.default_company_pass));
2175 }
2176
2177 NetworkChangeCompanyPassword(_local_company, this->password_editbox.text.buf);
2178 }
2179
2180 virtual void OnClick(Point pt, int widget, int click_count)
2181 {
2182 switch (widget) {
2183 case WID_NCP_OK:
2184 this->OnOk();
2185 /* FALL THROUGH */
2186
2187 case WID_NCP_CANCEL:
2188 delete this;
2189 break;
2190
2191 case WID_NCP_SAVE_AS_DEFAULT_PASSWORD:
2192 this->ToggleWidgetLoweredState(WID_NCP_SAVE_AS_DEFAULT_PASSWORD);
2193 this->SetDirty();
2194 break;
2195 }
2196 }
2197 };
2198
2199 static const NWidgetPart _nested_network_company_password_window_widgets[] = {
2200 NWidget(NWID_HORIZONTAL),
2201 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
2202 NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_COMPANY_PASSWORD_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2203 EndContainer(),
2204 NWidget(WWT_PANEL, COLOUR_GREY, WID_NCP_BACKGROUND),
2205 NWidget(NWID_VERTICAL), SetPIP(5, 5, 5),
2206 NWidget(NWID_HORIZONTAL), SetPIP(5, 5, 5),
2207 NWidget(WWT_TEXT, COLOUR_GREY, WID_NCP_LABEL), SetDataTip(STR_COMPANY_VIEW_PASSWORD, STR_NULL),
2208 NWidget(WWT_EDITBOX, COLOUR_GREY, WID_NCP_PASSWORD), SetFill(1, 0), SetMinimalSize(194, 12), SetDataTip(STR_COMPANY_VIEW_SET_PASSWORD, STR_NULL),
2209 EndContainer(),
2210 NWidget(NWID_HORIZONTAL), SetPIP(5, 0, 5),
2211 NWidget(NWID_SPACER), SetFill(1, 0),
2212 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_NCP_SAVE_AS_DEFAULT_PASSWORD), SetMinimalSize(194, 12),
2213 SetDataTip(STR_COMPANY_PASSWORD_MAKE_DEFAULT, STR_COMPANY_PASSWORD_MAKE_DEFAULT_TOOLTIP),
2214 EndContainer(),
2215 EndContainer(),
2216 EndContainer(),
2217 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
2218 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_NCP_CANCEL), SetFill(1, 0), SetDataTip(STR_BUTTON_CANCEL, STR_COMPANY_PASSWORD_CANCEL),
2219 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_NCP_OK), SetFill(1, 0), SetDataTip(STR_BUTTON_OK, STR_COMPANY_PASSWORD_OK),
2220 EndContainer(),
2221 };
2222
2223 static WindowDesc _network_company_password_window_desc(
2224 WDP_AUTO, NULL, 0, 0,
2225 WC_COMPANY_PASSWORD_WINDOW, WC_NONE,
2226 0,
2227 _nested_network_company_password_window_widgets, lengthof(_nested_network_company_password_window_widgets)
2228 );
2229
2230 void ShowNetworkCompanyPasswordWindow(Window *parent)
2231 {
2232 DeleteWindowById(WC_COMPANY_PASSWORD_WINDOW, 0);
2233
2234 new NetworkCompanyPasswordWindow(&_network_company_password_window_desc, parent);
2235 }
2236
2237 #endif /* ENABLE_NETWORK */