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/>.
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"
14 #include "network_client.h"
15 #include "network_gui.h"
16 #include "network_gamelist.h"
18 #include "network_base.h"
19 #include "network_content.h"
20 #include "network_server.h"
21 #include "network_coordinator.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"
48 # include <emscripten.h>
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.
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()
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));
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
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);
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);
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.
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
);
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
;
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
;
182 void Draw(const Window
*w
) override
185 for (NWidgetBase
*child_wid
= this->head
; child_wid
!= nullptr; child_wid
= child_wid
->next
) {
186 if (!this->visible
[i
++]) continue;
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;
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
;
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
{
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. */
268 sf
.SetFilterTerm(this->filter_editbox
.text
.buf
);
271 this->servers
.SetFilterState(true);
272 this->servers
.Filter(sf
);
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
);
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
);
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 */
364 this->list_pos
= SLP_INVALID
;
365 for (uint i
= 0; i
!= this->servers
.size(); i
++) {
366 if (this->servers
[i
] == this->server
) {
373 static bool CDECL
NGameSearchFilter(NetworkGameList
* const *item
, StringFilter
&sf
)
375 assert(item
!= nullptr);
376 assert((*item
) != nullptr);
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
)) {
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
)) {
427 const NWidgetBase
*nwi_date
= this->GetWidget
<NWidgetBase
>(WID_NG_DATE
);
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
);
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());
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();
507 this->last_sorting
= this->servers
.GetListing();
510 void UpdateWidgetSize(int widget
, Dimension
*size
, const Dimension
&padding
, Dimension
*fill
, Dimension
*resize
) override
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
;
519 case WID_NG_LASTJOINED
:
520 size
->height
= WD_MATRIX_TOP
+ std::max(GetSpriteSize(SPR_BLOT
).height
, (uint
)FONT_HEIGHT_NORMAL
) + WD_MATRIX_BOTTOM
;
523 case WID_NG_LASTJOINED_SPACER
:
524 size
->width
= NWidgetScrollbar::GetVerticalDimension().width
;
528 size
->width
+= 2 * Window::SortButtonWidth(); // Make space for the arrow
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
));
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
));
549 size
->width
+= 2 * Window::SortButtonWidth(); // Make space for the arrow
550 SetDParamMaxValue(0, 5);
551 *size
= maxdim(*size
, GetStringBoundingBox(STR_JUST_INT
));
556 void DrawWidget(const Rect
&r
, int widget
) const override
559 case WID_NG_MATRIX
: {
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
;
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
);
578 this->DrawDetails(r
);
587 if (widget
- WID_NG_NAME
== this->servers
.SortType()) this->DrawSortButtonState(widget
, this->servers
.IsDescSortOrder() ? SBS_DOWN
: SBS_UP
);
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);
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
701 case WID_NG_CANCEL
: // Cancel button
702 CloseWindowById(WC_NETWORK_WINDOW
, WN_NETWORK_WINDOW_GAME
);
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;
715 this->servers
.SetSortType(widget
- WID_NG_NAME
);
716 this->servers
.ForceResort();
717 this->SortNetworkGameList();
719 this->ScrollToSelectedServer();
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
;
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);
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();
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);
749 case WID_NG_SEARCH_INTERNET
:
750 _network_coordinator_client
.GetListing();
751 this->searched_internet
= true;
754 case WID_NG_SEARCH_LAN
:
755 NetworkUDPSearchGame();
758 case WID_NG_ADD
: // Add a server
759 SetDParamStr(0, _settings_client
.network
.connect_to_ip
);
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
);
767 case WID_NG_START
: // Start server
768 ShowNetworkStartServerWindow();
771 case WID_NG_JOIN
: // Join Game
772 if (this->server
!= nullptr) {
773 ShowNetworkLobbyWindow(this->server
);
777 case WID_NG_REFRESH
: // Refresh
778 if (this->server
!= nullptr) NetworkQueryServer(this->server
->connection_string
);
781 case WID_NG_NEWGRF
: // NewGRF Settings
782 if (this->server
!= nullptr) ShowNewGRFSettings(false, false, false, &this->server
->info
.grfconfig
);
785 case WID_NG_NEWGRF_MISSING
: // Find missing content online
786 if (this->server
!= nullptr) ShowMissingContentWindow(this->server
->info
.grfconfig
);
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();
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();
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
;
832 void OnEditboxChanged(int wid
) override
835 case WID_NG_FILTER
: {
836 this->servers
.ForceRebuild();
837 this->BuildGUINetworkGameList();
838 this->ScrollToSelectedServer();
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
;
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
[] = {
885 GUIGameServerList::FilterFunction
* const NetworkGameWindow::filter_funcs
[] = {
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
[] = {
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
),
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),
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
),
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
),
918 NWidget(NWID_VSCROLLBAR
, COLOUR_LIGHT_BLUE
, WID_NG_SCROLLBAR
),
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
),
927 NWidget(WWT_EMPTY
, INVALID_COLOUR
, WID_NG_LASTJOINED_SPACER
), SetFill(0, 0),
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
),
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),
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),
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
),
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
),
972 NWidget(NWID_SPACER
), SetMinimalSize(0, 6), SetResize(1, 0), SetFill(1, 0),
974 NWidget(NWID_VERTICAL
),
975 NWidget(NWID_SPACER
), SetFill(0, 1),
976 NWidget(WWT_RESIZEBOX
, COLOUR_LIGHT_BLUE
),
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
);
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
1025 case WID_NSS_CONNTYPE_BTN
:
1026 SetDParam(0, STR_NETWORK_SERVER_VISIBILITY_LOCAL
+ _settings_client
.network
.server_game_type
);
1029 case WID_NSS_CLIENTS_TXT
:
1030 SetDParam(0, _settings_client
.network
.max_clients
);
1033 case WID_NSS_COMPANIES_TXT
:
1034 SetDParam(0, _settings_client
.network
.max_companies
);
1039 void UpdateWidgetSize(int widget
, Dimension
*size
, const Dimension
&padding
, Dimension
*fill
, Dimension
*resize
) override
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
;
1050 void DrawWidget(const Rect
&r
, int widget
) const override
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
1062 case WID_NSS_CANCEL
: // Cancel button
1063 ShowNetworkGameWindow();
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
);
1072 case WID_NSS_CONNTYPE_BTN
: // Connection type
1073 ShowDropDownList(this, BuildVisibilityDropDownList(), _settings_client
.network
.server_game_type
, WID_NSS_CONNTYPE_BTN
);
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
);
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
);
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
);
1092 _left_button_clicked
= false;
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
);
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
);
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
);
1113 ShowGenerateLandscape();
1117 case WID_NSS_LOAD_GAME
:
1118 if (!CheckServerName()) return;
1119 _is_network_server
= true;
1120 ShowSaveLoadDialog(FT_SAVEGAME
, SLO_LOAD
);
1123 case WID_NSS_PLAY_SCENARIO
:
1124 if (!CheckServerName()) return;
1125 _is_network_server
= true;
1126 ShowSaveLoadDialog(FT_SCENARIO
, SLO_LOAD
);
1129 case WID_NSS_PLAY_HEIGHTMAP
:
1130 if (!CheckServerName()) return;
1131 _is_network_server
= true;
1132 ShowSaveLoadDialog(FT_HEIGHTMAP
,SLO_LOAD
);
1137 void OnDropdownSelect(int widget
, int index
) override
1140 case WID_NSS_CONNTYPE_BTN
:
1141 _settings_client
.network
.server_game_type
= (ServerGameType
)index
;
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
);
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
;
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;
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
),
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
),
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
),
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
),
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
),
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
),
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),
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),
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),
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
];
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
1304 size
->height
= WD_MATRIX_TOP
+ FONT_HEIGHT_NORMAL
+ WD_MATRIX_BOTTOM
;
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
;
1312 case WID_NL_DETAILS
:
1313 size
->height
= 30 + 11 * FONT_HEIGHT_NORMAL
;
1318 void SetStringParameters(int widget
) const override
1322 SetDParamStr(0, this->server
->info
.server_name
);
1327 void DrawWidget(const Rect
&r
, int widget
) const override
1330 case WID_NL_DETAILS
:
1331 this->DrawDetails(r
);
1335 this->DrawMatrix(r
);
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;
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
);
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
1464 case WID_NL_CANCEL
: // Cancel button
1465 ShowNetworkGameWindow();
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
);
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);
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
);
1483 case WID_NL_NEW
: // New company
1484 NetworkClientConnectGame(this->server
->connection_string
, COMPANY_NEW_COMPANY
);
1487 case WID_NL_SPECTATE
: // Spectate game
1488 NetworkClientConnectGame(this->server
->connection_string
, COMPANY_SPECTATOR
);
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
);
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
),
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),
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
),
1520 NWidget(NWID_VSCROLLBAR
, COLOUR_LIGHT_BLUE
, WID_NL_SCROLLBAR
),
1521 NWidget(NWID_SPACER
), SetMinimalSize(5, 0), SetResize(0, 1),
1523 NWidget(WWT_PANEL
, COLOUR_LIGHT_BLUE
, WID_NL_DETAILS
), SetMinimalSize(232, 0), SetResize(1, 1), SetFill(1, 1), EndContainer(),
1525 NWidget(NWID_SPACER
), SetMinimalSize(0, 9),
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
),
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
),
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),
1541 NWidget(NWID_SPACER
), SetMinimalSize(0, 8),
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
),
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
),
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
),
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
),
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
),
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
),
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
),
1642 NWidget(NWID_VERTICAL
),
1643 NWidget(NWID_VSCROLLBAR
, COLOUR_GREY
, WID_CL_SCROLLBAR
),
1644 NWidget(WWT_RESIZEBOX
, COLOUR_GREY
),
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
)
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
{
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) :
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
{
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.
1756 Button(SpriteID sprite
, StringID tooltip
, Colours colour
, T id
, ButtonCallback proc
, bool disabled
= false) :
1757 ButtonCommon(sprite
, tooltip
, colour
, disabled
),
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
{
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
);
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
);
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
)
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));
1856 wi_rect
.left
= pt
.x
;
1857 wi_rect
.right
= pt
.x
;
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
)
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
)));
1878 wi_rect
.left
= pt
.x
;
1879 wi_rect
.right
= pt
.x
;
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;
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.
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
);
1956 for (const Company
*c
: Company::Iterate()) {
1957 if (c
->index
== own_ci
->client_playas
) continue;
1959 this->RebuildListCompany(c
->index
, own_ci
);
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
;
2002 NetworkClientListWindow(WindowDesc
*desc
, WindowNumber window_number
) :
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
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
;
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
);
2037 resize
->height
= this->line_height
;
2038 fill
->height
= this->line_height
;
2039 size
->height
= std::max(size
->height
, 5 * this->line_height
);
2045 void OnResize() override
2047 this->vscroll
->SetCapacityFromWidget(this, WID_CL_MATRIX
);
2050 void SetStringParameters(int widget
) const override
2053 case WID_CL_SERVER_NAME
:
2054 SetDParamStr(0, _settings_client
.network
.server_name
);
2057 case WID_CL_SERVER_VISIBILITY
:
2058 SetDParam(0, STR_NETWORK_SERVER_VISIBILITY_LOCAL
+ _settings_client
.network
.server_game_type
);
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
);
2067 case WID_CL_SERVER_CONNECTION_TYPE
:
2068 SetDParam(0, STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_UNKNOWN
+ _network_server_connection_type
);
2071 case WID_CL_CLIENT_NAME
:
2072 SetDParamStr(0, _settings_client
.network
.client_name
);
2075 case WID_CL_CLIENT_COMPANY_COUNT
:
2076 SetDParam(0, NetworkClientInfo::GetNumItems());
2077 SetDParam(1, Company::GetNumItems());
2082 void OnClick(Point pt
, int widget
, int click_count
) override
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
);
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
);
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
);
2105 case WID_CL_MATRIX
: {
2106 ButtonCommon
*button
= this->GetButtonAtPoint(pt
);
2107 if (button
== nullptr) break;
2109 button
->OnClick(this, pt
);
2115 bool OnTooltip(Point pt
, int widget
, TooltipCloseCondition close_cond
) override
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
);
2137 } else if (index
== this->player_host_index
) {
2138 GuiShowTooltips(this, STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP
, 0, nullptr, close_cond
);
2143 ButtonCommon
*button
= this->GetButtonAtPoint(pt
);
2144 if (button
== nullptr) return false;
2146 GuiShowTooltips(this, button
->tooltip
, 0, nullptr, close_cond
);
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
2165 case WID_CL_SERVER_VISIBILITY
:
2166 if (!_network_server
) break;
2168 _settings_client
.network
.server_game_type
= (ServerGameType
)index
;
2169 NetworkUpdateServerGameType();
2172 case WID_CL_MATRIX
: {
2173 StringID text
= STR_NULL
;
2174 QueryCallbackProc
*callback
= nullptr;
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
);
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
);
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
);
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
);
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
);
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();
2240 case WID_CL_CLIENT_NAME_EDIT
: {
2241 SetSettingValue(GetSettingFromName("network.client_name")->AsStringSetting(), str
);
2242 this->InvalidateData();
2246 case WID_CL_COMPANY_JOIN
:
2247 NetworkClientRequestMove(this->join_company
, str
);
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
);
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
;
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
;
2371 void DrawWidget(const Rect
&r
, int widget
) const override
2374 case WID_CL_MATRIX
: {
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
);
2397 this->DrawCompany(COMPANY_SPECTATOR
, r
.left
, r
.right
, r
.top
, line
);
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;
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
;
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%
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%
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!
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 */
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();
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),
2536 NWidget(NWID_SPACER
), SetMinimalSize(0, 4),
2540 static WindowDesc
_network_join_status_window_desc(
2541 WDP_CENTER
, nullptr, 0, 0,
2542 WC_NETWORK_STATUS_WINDOW
, WC_NONE
,
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
;
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
;
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
);
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
2626 case WID_NCP_CANCEL
:
2630 case WID_NCP_SAVE_AS_DEFAULT_PASSWORD
:
2631 this->ToggleWidgetLoweredState(WID_NCP_SAVE_AS_DEFAULT_PASSWORD
);
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
),
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
),
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
),
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
),
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
);
2713 void SetStringParameters(int widget
) const override
2717 SetDParamStr(0, this->connection_string
);
2722 void OnClick(Point pt
, int widget
, int click_count
) override
2726 _network_coordinator_client
.ConnectFailure(this->token
, 0);
2730 case WID_NAR_YES_ONCE
:
2731 _network_coordinator_client
.StartTurnConnection(this->token
);
2735 case WID_NAR_YES_ALWAYS
:
2736 _settings_client
.network
.use_relay_service
= URS_ALLOW
;
2737 _network_coordinator_client
.StartTurnConnection(this->token
);
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
),
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
),
2759 static WindowDesc
_network_ask_relay_desc(
2760 WDP_CENTER
, nullptr, 0, 0,
2761 WC_NETWORK_ASK_RELAY
, WC_NONE
,
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
);