83 * found on the network. |
80 * found on the network. |
84 * @param unselect unselect the currently selected item */ |
81 * @param unselect unselect the currently selected item */ |
85 void UpdateNetworkGameWindow(bool unselect) |
82 void UpdateNetworkGameWindow(bool unselect) |
86 { |
83 { |
87 InvalidateWindowData(WC_NETWORK_WINDOW, 0, unselect); |
84 InvalidateWindowData(WC_NETWORK_WINDOW, 0, unselect); |
88 } |
|
89 |
|
90 static bool _internal_sort_order; // Used for Qsort order-flipping |
|
91 typedef int CDECL NGameNameSortFunction(const void*, const void*); |
|
92 |
|
93 /** Qsort function to sort by name. */ |
|
94 static int CDECL NGameNameSorter(const void *a, const void *b) |
|
95 { |
|
96 const NetworkGameList *cmp1 = *(const NetworkGameList**)a; |
|
97 const NetworkGameList *cmp2 = *(const NetworkGameList**)b; |
|
98 int r = strcasecmp(cmp1->info.server_name, cmp2->info.server_name); |
|
99 |
|
100 return _internal_sort_order ? -r : r; |
|
101 } |
|
102 |
|
103 /** Qsort function to sort by the amount of clients online on a |
|
104 * server. If the two servers have the same amount, the one with the |
|
105 * higher maximum is preferred. */ |
|
106 static int CDECL NGameClientSorter(const void *a, const void *b) |
|
107 { |
|
108 const NetworkGameList *cmp1 = *(const NetworkGameList**)a; |
|
109 const NetworkGameList *cmp2 = *(const NetworkGameList**)b; |
|
110 /* Reverse as per default we are interested in most-clients first */ |
|
111 int r = cmp1->info.clients_on - cmp2->info.clients_on; |
|
112 |
|
113 if (r == 0) r = cmp1->info.clients_max - cmp2->info.clients_max; |
|
114 if (r == 0) r = strcasecmp(cmp1->info.server_name, cmp2->info.server_name); |
|
115 |
|
116 return _internal_sort_order ? -r : r; |
|
117 } |
|
118 |
|
119 /** Qsort function to sort by joinability. If both servers are the |
|
120 * same, prefer the non-passworded server first. */ |
|
121 static int CDECL NGameAllowedSorter(const void *a, const void *b) |
|
122 { |
|
123 const NetworkGameList *cmp1 = *(const NetworkGameList**)a; |
|
124 const NetworkGameList *cmp2 = *(const NetworkGameList**)b; |
|
125 |
|
126 /* The servers we do not know anything about (the ones that did not reply) should be at the bottom) */ |
|
127 int r = StrEmpty(cmp1->info.server_revision) - StrEmpty(cmp2->info.server_revision); |
|
128 |
|
129 /* Reverse default as we are interested in version-compatible clients first */ |
|
130 if (r == 0) r = cmp2->info.version_compatible - cmp1->info.version_compatible; |
|
131 /* The version-compatible ones are then sorted with NewGRF compatible first, incompatible last */ |
|
132 if (r == 0) r = cmp2->info.compatible - cmp1->info.compatible; |
|
133 /* Passworded servers should be below unpassworded servers */ |
|
134 if (r == 0) r = cmp1->info.use_password - cmp2->info.use_password; |
|
135 /* Finally sort on the name of the server */ |
|
136 if (r == 0) r = strcasecmp(cmp1->info.server_name, cmp2->info.server_name); |
|
137 |
|
138 return _internal_sort_order ? -r : r; |
|
139 } |
85 } |
140 |
86 |
141 /** Enum for NetworkGameWindow, referring to _network_game_window_widgets */ |
87 /** Enum for NetworkGameWindow, referring to _network_game_window_widgets */ |
142 enum NetworkGameWindowWidgets { |
88 enum NetworkGameWindowWidgets { |
143 NGWW_CLOSE, ///< Close 'X' button |
89 NGWW_CLOSE, ///< Close 'X' button |
169 NGWW_CANCEL, ///< 'Cancel' button |
115 NGWW_CANCEL, ///< 'Cancel' button |
170 }; |
116 }; |
171 |
117 |
172 typedef GUIList<NetworkGameList*> GUIGameServerList; |
118 typedef GUIList<NetworkGameList*> GUIGameServerList; |
173 |
119 |
174 struct NetworkGameWindow : public QueryStringBaseWindow { |
120 class NetworkGameWindow : public QueryStringBaseWindow { |
|
121 protected: |
|
122 /* Runtime saved values */ |
|
123 static Listing last_sorting; |
|
124 |
|
125 /* Constants for sorting servers */ |
|
126 static GUIGameServerList::SortFunction *const sorter_funcs[]; |
|
127 |
175 byte field; ///< selected text-field |
128 byte field; ///< selected text-field |
176 NetworkGameList *server; ///< selected server |
129 NetworkGameList *server; ///< selected server |
177 GUIGameServerList servers; ///< list with game servers. |
130 GUIGameServerList servers; ///< list with game servers. |
178 |
|
179 NetworkGameWindow(const WindowDesc *desc) : QueryStringBaseWindow(desc) |
|
180 { |
|
181 ttd_strlcpy(this->edit_str_buf, _network_player_name, lengthof(this->edit_str_buf)); |
|
182 this->afilter = CS_ALPHANUMERAL; |
|
183 InitializeTextBuffer(&this->text, this->edit_str_buf, lengthof(this->edit_str_buf), 120); |
|
184 |
|
185 UpdateNetworkGameWindow(true); |
|
186 |
|
187 this->vscroll.cap = 11; |
|
188 this->resize.step_height = NET_PRC__SIZE_OF_ROW; |
|
189 |
|
190 this->field = NGWW_PLAYER; |
|
191 this->server = NULL; |
|
192 |
|
193 this->servers.flags = VL_REBUILD | (_ng_sorting.order ? VL_DESC : VL_NONE); |
|
194 this->servers.sort_type = _ng_sorting.criteria; |
|
195 |
|
196 this->FindWindowPlacementAndResize(desc); |
|
197 } |
|
198 |
|
199 ~NetworkGameWindow() |
|
200 { |
|
201 } |
|
202 |
131 |
203 /** |
132 /** |
204 * (Re)build the network game list as its amount has changed because |
133 * (Re)build the network game list as its amount has changed because |
205 * an item has been added or deleted for example |
134 * an item has been added or deleted for example |
206 */ |
135 */ |
207 void BuildNetworkGameList() |
136 void BuildNetworkGameList() |
208 { |
137 { |
209 if (!(this->servers.flags & VL_REBUILD)) return; |
138 if (!this->servers.NeedRebuild()) return; |
210 |
139 |
211 /* Create temporary array of games to use for listing */ |
140 /* Create temporary array of games to use for listing */ |
212 this->servers.Clear(); |
141 this->servers.Clear(); |
213 |
142 |
214 for (NetworkGameList *ngl = _network_game_list; ngl != NULL; ngl = ngl->next) { |
143 for (NetworkGameList *ngl = _network_game_list; ngl != NULL; ngl = ngl->next) { |
215 *this->servers.Append() = ngl; |
144 *this->servers.Append() = ngl; |
216 } |
145 } |
217 |
146 |
218 this->servers.Compact(); |
147 this->servers.Compact(); |
219 |
148 this->servers.RebuildDone(); |
220 /* Force resort */ |
149 } |
221 this->servers.flags &= ~VL_REBUILD; |
150 |
222 this->servers.flags |= VL_RESORT; |
151 /** Sort servers by name. */ |
223 } |
152 static int CDECL NGameNameSorter(NetworkGameList* const *a, NetworkGameList* const *b) |
224 |
153 { |
|
154 return strcasecmp((*a)->info.server_name, (*b)->info.server_name); |
|
155 } |
|
156 |
|
157 /** Sort servers by the amount of clients online on a |
|
158 * server. If the two servers have the same amount, the one with the |
|
159 * higher maximum is preferred. */ |
|
160 static int CDECL NGameClientSorter(NetworkGameList* const *a, NetworkGameList* const *b) |
|
161 { |
|
162 /* Reverse as per default we are interested in most-clients first */ |
|
163 int r = (*a)->info.clients_on - (*b)->info.clients_on; |
|
164 |
|
165 if (r == 0) r = (*a)->info.clients_max - (*b)->info.clients_max; |
|
166 if (r == 0) r = NGameNameSorter(a, b); |
|
167 |
|
168 return r; |
|
169 } |
|
170 |
|
171 /** Sort servers by joinability. If both servers are the |
|
172 * same, prefer the non-passworded server first. */ |
|
173 static int CDECL NGameAllowedSorter(NetworkGameList* const *a, NetworkGameList* const *b) |
|
174 { |
|
175 /* The servers we do not know anything about (the ones that did not reply) should be at the bottom) */ |
|
176 int r = StrEmpty((*a)->info.server_revision) - StrEmpty((*b)->info.server_revision); |
|
177 |
|
178 /* Reverse default as we are interested in version-compatible clients first */ |
|
179 if (r == 0) r = (*b)->info.version_compatible - (*a)->info.version_compatible; |
|
180 /* The version-compatible ones are then sorted with NewGRF compatible first, incompatible last */ |
|
181 if (r == 0) r = (*b)->info.compatible - (*a)->info.compatible; |
|
182 /* Passworded servers should be below unpassworded servers */ |
|
183 if (r == 0) r = (*a)->info.use_password - (*b)->info.use_password; |
|
184 /* Finally sort on the name of the server */ |
|
185 if (r == 0) r = NGameNameSorter(a, b); |
|
186 |
|
187 return r; |
|
188 } |
|
189 |
|
190 /** Sort the server list */ |
225 void SortNetworkGameList() |
191 void SortNetworkGameList() |
226 { |
192 { |
227 static NGameNameSortFunction * const ngame_sorter[] = { |
193 if (!this->servers.Sort()) return; |
228 &NGameNameSorter, |
|
229 &NGameClientSorter, |
|
230 &NGameAllowedSorter |
|
231 }; |
|
232 |
|
233 NetworkGameList *item; |
|
234 uint i; |
|
235 |
|
236 if (!(this->servers.flags & VL_RESORT)) return; |
|
237 if (this->servers.Length() == 0) return; |
|
238 |
|
239 _internal_sort_order = !!(this->servers.flags & VL_DESC); |
|
240 qsort(this->servers.Begin(), this->servers.Length(), sizeof(*this->servers.Begin()), ngame_sorter[this->servers.sort_type]); |
|
241 |
194 |
242 /* After sorting ngl->sort_list contains the sorted items. Put these back |
195 /* After sorting ngl->sort_list contains the sorted items. Put these back |
243 * into the original list. Basically nothing has changed, we are only |
196 * into the original list. Basically nothing has changed, we are only |
244 * shuffling the ->next pointers */ |
197 * shuffling the ->next pointers */ |
245 _network_game_list = this->servers[0]; |
198 _network_game_list = this->servers[0]; |
246 for (item = _network_game_list, i = 1; i != this->servers.Length(); i++) { |
199 NetworkGameList *item = _network_game_list; |
|
200 for (uint i = 1; i != this->servers.Length(); i++) { |
247 item->next = this->servers[i]; |
201 item->next = this->servers[i]; |
248 item = item->next; |
202 item = item->next; |
249 } |
203 } |
250 item->next = NULL; |
204 item->next = NULL; |
251 |
|
252 this->servers.flags &= ~VL_RESORT; |
|
253 } |
205 } |
254 |
206 |
255 /** |
207 /** |
256 * Draw a single server line. |
208 * Draw a single server line. |
257 * @param cur_item the server to draw. |
209 * @param cur_item the server to draw. |
283 /* draw flag according to server language */ |
235 /* draw flag according to server language */ |
284 DrawSprite(SPR_FLAGS_BASE + cur_item->info.server_lang, PAL_NONE, this->widget[NGWW_INFO].left + 25, y); |
236 DrawSprite(SPR_FLAGS_BASE + cur_item->info.server_lang, PAL_NONE, this->widget[NGWW_INFO].left + 25, y); |
285 } |
237 } |
286 } |
238 } |
287 |
239 |
|
240 public: |
|
241 NetworkGameWindow(const WindowDesc *desc) : QueryStringBaseWindow(desc) |
|
242 { |
|
243 ttd_strlcpy(this->edit_str_buf, _network_player_name, lengthof(this->edit_str_buf)); |
|
244 this->afilter = CS_ALPHANUMERAL; |
|
245 InitializeTextBuffer(&this->text, this->edit_str_buf, lengthof(this->edit_str_buf), 120); |
|
246 |
|
247 UpdateNetworkGameWindow(true); |
|
248 |
|
249 this->vscroll.cap = 11; |
|
250 this->resize.step_height = NET_PRC__SIZE_OF_ROW; |
|
251 |
|
252 this->field = NGWW_PLAYER; |
|
253 this->server = NULL; |
|
254 |
|
255 this->servers.SetListing(this->last_sorting); |
|
256 this->servers.SetSortFuncs(this->sorter_funcs); |
|
257 this->servers.ForceRebuild(); |
|
258 this->SortNetworkGameList(); |
|
259 |
|
260 this->FindWindowPlacementAndResize(desc); |
|
261 } |
|
262 |
|
263 ~NetworkGameWindow() |
|
264 { |
|
265 this->last_sorting = this->servers.GetListing(); |
|
266 } |
|
267 |
288 virtual void OnPaint() |
268 virtual void OnPaint() |
289 { |
269 { |
290 const NetworkGameList *sel = this->server; |
270 const NetworkGameList *sel = this->server; |
291 const SortButtonState arrow = (this->servers.flags & VL_DESC) ? SBS_DOWN : SBS_UP; |
271 const SortButtonState arrow = this->servers.IsDescSortOrder() ? SBS_DOWN : SBS_UP; |
292 |
272 |
293 if (this->servers.flags & VL_REBUILD) { |
273 if (this->servers.NeedRebuild()) { |
294 this->BuildNetworkGameList(); |
274 this->BuildNetworkGameList(); |
295 SetVScrollCount(this, this->servers.Length()); |
275 SetVScrollCount(this, this->servers.Length()); |
296 } |
276 } |
297 if (this->servers.flags & VL_RESORT) this->SortNetworkGameList(); |
277 this->SortNetworkGameList(); |
298 |
278 |
299 /* 'Refresh' button invisible if no server selected */ |
279 /* 'Refresh' button invisible if no server selected */ |
300 this->SetWidgetDisabledState(NGWW_REFRESH, sel == NULL); |
280 this->SetWidgetDisabledState(NGWW_REFRESH, sel == NULL); |
301 /* 'Join' button disabling conditions */ |
281 /* 'Join' button disabling conditions */ |
302 this->SetWidgetDisabledState(NGWW_JOIN, sel == NULL || // no Selected Server |
282 this->SetWidgetDisabledState(NGWW_JOIN, sel == NULL || // no Selected Server |
317 this->DrawEditBox(NGWW_PLAYER); |
297 this->DrawEditBox(NGWW_PLAYER); |
318 |
298 |
319 DrawString(this->widget[NGWW_PLAYER].left - 100, 23, STR_NETWORK_PLAYER_NAME, TC_GOLD); |
299 DrawString(this->widget[NGWW_PLAYER].left - 100, 23, STR_NETWORK_PLAYER_NAME, TC_GOLD); |
320 |
300 |
321 /* Sort based on widgets: name, clients, compatibility */ |
301 /* Sort based on widgets: name, clients, compatibility */ |
322 switch (this->servers.sort_type) { |
302 switch (this->servers.SortType()) { |
323 case NGWW_NAME - NGWW_NAME: this->DrawSortButtonState(NGWW_NAME, arrow); break; |
303 case NGWW_NAME - NGWW_NAME: this->DrawSortButtonState(NGWW_NAME, arrow); break; |
324 case NGWW_CLIENTS - NGWW_NAME: this->DrawSortButtonState(NGWW_CLIENTS, arrow); break; |
304 case NGWW_CLIENTS - NGWW_NAME: this->DrawSortButtonState(NGWW_CLIENTS, arrow); break; |
325 case NGWW_INFO - NGWW_NAME: this->DrawSortButtonState(NGWW_INFO, arrow); break; |
305 case NGWW_INFO - NGWW_NAME: this->DrawSortButtonState(NGWW_INFO, arrow); break; |
326 } |
306 } |
327 |
307 |
328 uint16 y = NET_PRC__OFFSET_TOP_WIDGET + 3; |
308 uint16 y = NET_PRC__OFFSET_TOP_WIDGET + 3; |
329 int32 n = 0; |
309 |
330 int32 pos = this->vscroll.pos; |
310 const int max = min(this->vscroll.pos + this->vscroll.cap, this->servers.Length()); |
331 const NetworkGameList *cur_item = _network_game_list; |
311 |
332 |
312 for (int i = this->vscroll.pos; i < max; ++i) { |
333 while (pos > 0 && cur_item != NULL) { |
313 const NetworkGameList *ngl = this->servers[i]; |
334 pos--; |
314 this->DrawServerLine(ngl, y, ngl == sel); |
335 cur_item = cur_item->next; |
|
336 } |
|
337 |
|
338 while (cur_item != NULL) { |
|
339 this->DrawServerLine(cur_item, y, cur_item == sel); |
|
340 |
|
341 cur_item = cur_item->next; |
|
342 y += NET_PRC__SIZE_OF_ROW; |
315 y += NET_PRC__SIZE_OF_ROW; |
343 if (++n == this->vscroll.cap) break; // max number of games in the window |
|
344 } |
316 } |
345 |
317 |
346 const NetworkGameList *last_joined = NetworkGameListAddItem(inet_addr(_network_last_host), _network_last_port); |
318 const NetworkGameList *last_joined = NetworkGameListAddItem(inet_addr(_network_last_host), _network_last_port); |
347 /* Draw the last joined server, if any */ |
319 /* Draw the last joined server, if any */ |
348 if (last_joined != NULL) this->DrawServerLine(last_joined, y = this->widget[NGWW_LASTJOINED].top + 3, last_joined == sel); |
320 if (last_joined != NULL) this->DrawServerLine(last_joined, y = this->widget[NGWW_LASTJOINED].top + 3, last_joined == sel); |
438 break; |
410 break; |
439 |
411 |
440 case NGWW_NAME: // Sort by name |
412 case NGWW_NAME: // Sort by name |
441 case NGWW_CLIENTS: // Sort by connected clients |
413 case NGWW_CLIENTS: // Sort by connected clients |
442 case NGWW_INFO: // Connectivity (green dot) |
414 case NGWW_INFO: // Connectivity (green dot) |
443 if (this->servers.sort_type == widget - NGWW_NAME) this->servers.flags ^= VL_DESC; |
415 if (this->servers.SortType() == widget - NGWW_NAME) { |
444 this->servers.flags |= VL_RESORT; |
416 this->servers.ToggleSortOrder(); |
445 this->servers.sort_type = widget - NGWW_NAME; |
417 } else { |
446 |
418 this->servers.SetSortType(widget - NGWW_NAME); |
447 _ng_sorting.order = !!(this->servers.flags & VL_DESC); |
419 this->servers.ForceResort(); |
448 _ng_sorting.criteria = this->servers.sort_type; |
420 } |
449 this->SetDirty(); |
421 this->SetDirty(); |
450 break; |
422 break; |
451 |
423 |
452 case NGWW_MATRIX: { // Matrix to show networkgames |
424 case NGWW_MATRIX: { // Matrix to show networkgames |
453 NetworkGameList *cur_item; |
|
454 uint32 id_v = (pt.y - NET_PRC__OFFSET_TOP_WIDGET) / NET_PRC__SIZE_OF_ROW; |
425 uint32 id_v = (pt.y - NET_PRC__OFFSET_TOP_WIDGET) / NET_PRC__SIZE_OF_ROW; |
455 |
426 |
456 if (id_v >= this->vscroll.cap) return; // click out of bounds |
427 if (id_v >= this->vscroll.cap) return; // click out of bounds |
457 id_v += this->vscroll.pos; |
428 id_v += this->vscroll.pos; |
458 |
429 |
459 cur_item = _network_game_list; |
430 this->server = this->servers[id_v]; |
460 for (; id_v > 0 && cur_item != NULL; id_v--) cur_item = cur_item->next; |
|
461 |
|
462 this->server = cur_item; |
|
463 this->SetDirty(); |
431 this->SetDirty(); |
464 } break; |
432 } break; |
465 |
433 |
466 case NGWW_LASTJOINED: { |
434 case NGWW_LASTJOINED: { |
467 NetworkGameList *last_joined = NetworkGameListAddItem(inet_addr(_network_last_host), _network_last_port); |
435 NetworkGameList *last_joined = NetworkGameListAddItem(inet_addr(_network_last_host), _network_last_port); |
587 offset += space; |
555 offset += space; |
588 } |
556 } |
589 } |
557 } |
590 }; |
558 }; |
591 |
559 |
|
560 Listing NetworkGameWindow::last_sorting = {false, 2}; |
|
561 GUIGameServerList::SortFunction *const NetworkGameWindow::sorter_funcs[] = { |
|
562 &NGameNameSorter, |
|
563 &NGameClientSorter, |
|
564 &NGameAllowedSorter |
|
565 }; |
|
566 |
|
567 |
592 static const Widget _network_game_window_widgets[] = { |
568 static const Widget _network_game_window_widgets[] = { |
593 /* TOP */ |
569 /* TOP */ |
594 { WWT_CLOSEBOX, RESIZE_NONE, BGC, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, // NGWW_CLOSE |
570 { WWT_CLOSEBOX, RESIZE_NONE, BGC, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, // NGWW_CLOSE |
595 { WWT_CAPTION, RESIZE_RIGHT, BGC, 11, 449, 0, 13, STR_NETWORK_MULTIPLAYER, STR_NULL}, // NGWW_CAPTION |
571 { WWT_CAPTION, RESIZE_RIGHT, BGC, 11, 449, 0, 13, STR_NETWORK_MULTIPLAYER, STR_NULL}, // NGWW_CAPTION |
596 { WWT_PANEL, RESIZE_RB, BGC, 0, 449, 14, 263, 0x0, STR_NULL}, // NGWW_RESIZE |
572 { WWT_PANEL, RESIZE_RB, BGC, 0, 449, 14, 263, 0x0, STR_NULL}, // NGWW_RESIZE |