|
1 /* $Id$ */ |
|
2 |
|
3 #ifdef ENABLE_NETWORK |
|
4 #include "../stdafx.h" |
|
5 #include "../openttd.h" |
|
6 #include "../string.h" |
|
7 #include "../strings.h" |
|
8 #include "../table/sprites.h" |
|
9 #include "network.h" |
|
10 #include "../date.h" |
|
11 |
|
12 #include "../fios.h" |
|
13 #include "../table/strings.h" |
|
14 #include "../functions.h" |
|
15 #include "network_data.h" |
|
16 #include "network_client.h" |
|
17 #include "network_gui.h" |
|
18 #include "network_gamelist.h" |
|
19 #include "../window.h" |
|
20 #include "../gui.h" |
|
21 #include "../gfx.h" |
|
22 #include "../command.h" |
|
23 #include "../variables.h" |
|
24 #include "network_server.h" |
|
25 #include "network_udp.h" |
|
26 #include "../settings.h" |
|
27 #include "../string.h" |
|
28 #include "../town.h" |
|
29 #include "../newgrf.h" |
|
30 |
|
31 #define BGC 5 |
|
32 #define BTC 15 |
|
33 |
|
34 typedef struct network_d { |
|
35 PlayerID company; // select company in network lobby |
|
36 byte field; // select text-field in start-server and game-listing |
|
37 NetworkGameList *server; // selected server in lobby and game-listing |
|
38 FiosItem *map; // selected map in start-server |
|
39 } network_d; |
|
40 assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(network_d)); |
|
41 |
|
42 typedef struct network_ql_d { |
|
43 network_d n; // see above; general stuff |
|
44 querystr_d q; // text-input in start-server and game-listing |
|
45 NetworkGameList **sort_list; // list of games (sorted) |
|
46 list_d l; // accompanying list-administration |
|
47 } network_ql_d; |
|
48 assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(network_ql_d)); |
|
49 |
|
50 /* Global to remember sorting after window has been closed */ |
|
51 static Listing _ng_sorting; |
|
52 |
|
53 static char _edit_str_buf[150]; |
|
54 static bool _chat_tab_completion_active; |
|
55 |
|
56 static void ShowNetworkStartServerWindow(void); |
|
57 static void ShowNetworkLobbyWindow(NetworkGameList *ngl); |
|
58 extern void SwitchMode(int new_mode); |
|
59 |
|
60 static const StringID _connection_types_dropdown[] = { |
|
61 STR_NETWORK_LAN_INTERNET, |
|
62 STR_NETWORK_INTERNET_ADVERTISE, |
|
63 INVALID_STRING_ID |
|
64 }; |
|
65 |
|
66 static const StringID _lan_internet_types_dropdown[] = { |
|
67 STR_NETWORK_LAN, |
|
68 STR_NETWORK_INTERNET, |
|
69 INVALID_STRING_ID |
|
70 }; |
|
71 |
|
72 static const StringID _players_dropdown[] = { |
|
73 STR_NETWORK_0_PLAYERS, |
|
74 STR_NETWORK_1_PLAYERS, |
|
75 STR_NETWORK_2_PLAYERS, |
|
76 STR_NETWORK_3_PLAYERS, |
|
77 STR_NETWORK_4_PLAYERS, |
|
78 STR_NETWORK_5_PLAYERS, |
|
79 STR_NETWORK_6_PLAYERS, |
|
80 STR_NETWORK_7_PLAYERS, |
|
81 STR_NETWORK_8_PLAYERS, |
|
82 STR_NETWORK_9_PLAYERS, |
|
83 STR_NETWORK_10_PLAYERS, |
|
84 INVALID_STRING_ID |
|
85 }; |
|
86 |
|
87 static const StringID _language_dropdown[] = { |
|
88 STR_NETWORK_LANG_ANY, |
|
89 STR_NETWORK_LANG_ENGLISH, |
|
90 STR_NETWORK_LANG_GERMAN, |
|
91 STR_NETWORK_LANG_FRENCH, |
|
92 INVALID_STRING_ID |
|
93 }; |
|
94 |
|
95 enum { |
|
96 NET_PRC__OFFSET_TOP_WIDGET = 54, |
|
97 NET_PRC__OFFSET_TOP_WIDGET_COMPANY = 52, |
|
98 NET_PRC__SIZE_OF_ROW = 14, |
|
99 }; |
|
100 |
|
101 /** Update the network new window because a new server is |
|
102 * found on the network. |
|
103 * @param unselect unselect the currently selected item */ |
|
104 void UpdateNetworkGameWindow(bool unselect) |
|
105 { |
|
106 SendWindowMessage(WC_NETWORK_WINDOW, 0, unselect, 0, 0); |
|
107 } |
|
108 |
|
109 static bool _internal_sort_order; // Used for Qsort order-flipping |
|
110 typedef int CDECL NGameNameSortFunction(const void*, const void*); |
|
111 |
|
112 /** Qsort function to sort by name. */ |
|
113 static int CDECL NGameNameSorter(const void *a, const void *b) |
|
114 { |
|
115 const NetworkGameList *cmp1 = *(const NetworkGameList**)a; |
|
116 const NetworkGameList *cmp2 = *(const NetworkGameList**)b; |
|
117 int r = strcasecmp(cmp1->info.server_name, cmp2->info.server_name); |
|
118 |
|
119 return _internal_sort_order ? -r : r; |
|
120 } |
|
121 |
|
122 /** Qsort function to sort by the amount of clients online on a |
|
123 * server. If the two servers have the same amount, the one with the |
|
124 * higher maximum is preferred. */ |
|
125 static int CDECL NGameClientSorter(const void *a, const void *b) |
|
126 { |
|
127 const NetworkGameList *cmp1 = *(const NetworkGameList**)a; |
|
128 const NetworkGameList *cmp2 = *(const NetworkGameList**)b; |
|
129 /* Reverse as per default we are interested in most-clients first */ |
|
130 int r = cmp1->info.clients_on - cmp2->info.clients_on; |
|
131 |
|
132 if (r == 0) r = cmp1->info.clients_max - cmp2->info.clients_max; |
|
133 if (r == 0) r = strcasecmp(cmp1->info.server_name, cmp2->info.server_name); |
|
134 |
|
135 return _internal_sort_order ? -r : r; |
|
136 } |
|
137 |
|
138 /** Qsort function to sort by joinability. If both servers are the |
|
139 * same, prefer the non-passworded server first. */ |
|
140 static int CDECL NGameAllowedSorter(const void *a, const void *b) |
|
141 { |
|
142 const NetworkGameList *cmp1 = *(const NetworkGameList**)a; |
|
143 const NetworkGameList *cmp2 = *(const NetworkGameList**)b; |
|
144 /* Reverse default as we are interested in compatible clients first */ |
|
145 int r = cmp2->info.compatible - cmp1->info.compatible; |
|
146 |
|
147 if (r == 0) r = cmp1->info.use_password - cmp2->info.use_password; |
|
148 if (r == 0) r = strcasecmp(cmp1->info.server_name, cmp2->info.server_name); |
|
149 |
|
150 return _internal_sort_order ? -r : r; |
|
151 } |
|
152 |
|
153 /** (Re)build the network game list as its amount has changed because |
|
154 * an item has been added or deleted for example |
|
155 * @param ngl list_d struct that contains all necessary information for sorting */ |
|
156 static void BuildNetworkGameList(network_ql_d *nqld) |
|
157 { |
|
158 NetworkGameList *ngl_temp; |
|
159 uint n = 0; |
|
160 |
|
161 if (!(nqld->l.flags & VL_REBUILD)) return; |
|
162 |
|
163 /* Count the number of games in the list */ |
|
164 for (ngl_temp = _network_game_list; ngl_temp != NULL; ngl_temp = ngl_temp->next) n++; |
|
165 if (n == 0) return; |
|
166 |
|
167 /* Create temporary array of games to use for listing */ |
|
168 free(nqld->sort_list); |
|
169 nqld->sort_list = malloc(n * sizeof(nqld->sort_list[0])); |
|
170 if (nqld->sort_list == NULL) error("Could not allocate memory for the network-game-sorting-list"); |
|
171 nqld->l.list_length = n; |
|
172 |
|
173 for (n = 0, ngl_temp = _network_game_list; ngl_temp != NULL; ngl_temp = ngl_temp->next) { |
|
174 nqld->sort_list[n++] = ngl_temp; |
|
175 } |
|
176 |
|
177 /* Force resort */ |
|
178 nqld->l.flags &= ~VL_REBUILD; |
|
179 nqld->l.flags |= VL_RESORT; |
|
180 } |
|
181 |
|
182 static void SortNetworkGameList(network_ql_d *nqld) |
|
183 { |
|
184 static NGameNameSortFunction * const ngame_sorter[] = { |
|
185 &NGameNameSorter, |
|
186 &NGameClientSorter, |
|
187 &NGameAllowedSorter |
|
188 }; |
|
189 |
|
190 NetworkGameList *item; |
|
191 uint i; |
|
192 |
|
193 if (!(nqld->l.flags & VL_RESORT)) return; |
|
194 if (nqld->l.list_length == 0) return; |
|
195 |
|
196 _internal_sort_order = !!(nqld->l.flags & VL_DESC); |
|
197 qsort(nqld->sort_list, nqld->l.list_length, sizeof(nqld->sort_list[0]), ngame_sorter[nqld->l.sort_type]); |
|
198 |
|
199 /* After sorting ngl->sort_list contains the sorted items. Put these back |
|
200 * into the original list. Basically nothing has changed, we are only |
|
201 * shuffling the ->next pointers */ |
|
202 _network_game_list = nqld->sort_list[0]; |
|
203 for (item = _network_game_list, i = 1; i != nqld->l.list_length; i++) { |
|
204 item->next = nqld->sort_list[i]; |
|
205 item = item->next; |
|
206 } |
|
207 item->next = NULL; |
|
208 |
|
209 nqld->l.flags &= ~VL_RESORT; |
|
210 } |
|
211 |
|
212 /* Uses network_ql_d (network_d, querystr_d and list_d) WP macro */ |
|
213 static void NetworkGameWindowWndProc(Window *w, WindowEvent *e) |
|
214 { |
|
215 network_d *nd = &WP(w, network_ql_d).n; |
|
216 list_d *ld = &WP(w, network_ql_d).l; |
|
217 |
|
218 switch (e->event) { |
|
219 case WE_CREATE: /* Focus input box */ |
|
220 nd->field = 3; |
|
221 nd->server = NULL; |
|
222 |
|
223 WP(w, network_ql_d).sort_list = NULL; |
|
224 ld->flags = VL_REBUILD | (_ng_sorting.order << (VL_DESC - 1)); |
|
225 ld->sort_type = _ng_sorting.criteria; |
|
226 break; |
|
227 |
|
228 case WE_PAINT: { |
|
229 const NetworkGameList *sel = nd->server; |
|
230 const char *arrow = (ld->flags & VL_DESC) ? DOWNARROW : UPARROW; |
|
231 |
|
232 if (ld->flags & VL_REBUILD) { |
|
233 BuildNetworkGameList(&WP(w, network_ql_d)); |
|
234 SetVScrollCount(w, ld->list_length); |
|
235 } |
|
236 if (ld->flags & VL_RESORT) SortNetworkGameList(&WP(w, network_ql_d)); |
|
237 |
|
238 SetWindowWidgetDisabledState(w, 17, sel == NULL); |
|
239 /* Join Button disabling conditions */ |
|
240 SetWindowWidgetDisabledState(w, 16, sel == NULL || // no Selected Server |
|
241 !sel->online || // Server offline |
|
242 sel->info.clients_on >= sel->info.clients_max || // Server full |
|
243 !sel->info.compatible); // Revision mismatch |
|
244 |
|
245 SetWindowWidgetHiddenState(w, 18, sel == NULL || |
|
246 !sel->online || |
|
247 sel->info.grfconfig == NULL); |
|
248 |
|
249 SetDParam(0, 0x00); |
|
250 SetDParam(7, _lan_internet_types_dropdown[_network_lan_internet]); |
|
251 DrawWindowWidgets(w); |
|
252 |
|
253 DrawEditBox(w, &WP(w, network_ql_d).q, 3); |
|
254 |
|
255 DrawString(9, 23, STR_NETWORK_CONNECTION, 2); |
|
256 DrawString(210, 23, STR_NETWORK_PLAYER_NAME, 2); |
|
257 |
|
258 /* Sort based on widgets: name, clients, compatibility */ |
|
259 switch (ld->sort_type) { |
|
260 case 6 - 6: DoDrawString(arrow, w->widget[6].right - 10, 42, 0x10); break; |
|
261 case 7 - 6: DoDrawString(arrow, w->widget[7].right - 10, 42, 0x10); break; |
|
262 case 8 - 6: DoDrawString(arrow, w->widget[8].right - 10, 42, 0x10); break; |
|
263 } |
|
264 |
|
265 { // draw list of games |
|
266 uint16 y = NET_PRC__OFFSET_TOP_WIDGET + 3; |
|
267 int32 n = 0; |
|
268 int32 pos = w->vscroll.pos; |
|
269 uint max_name_width = w->widget[6].right - w->widget[6].left - 5; |
|
270 const NetworkGameList *cur_item = _network_game_list; |
|
271 |
|
272 while (pos > 0 && cur_item != NULL) { |
|
273 pos--; |
|
274 cur_item = cur_item->next; |
|
275 } |
|
276 |
|
277 while (cur_item != NULL) { |
|
278 // show highlighted item with a different colour |
|
279 if (cur_item == sel) GfxFillRect(w->widget[6].left + 1, y - 2, w->widget[8].right - 1, y + 9, 10); |
|
280 |
|
281 SetDParamStr(0, cur_item->info.server_name); |
|
282 DrawStringTruncated(w->widget[6].left + 5, y, STR_02BD, 16, max_name_width); |
|
283 |
|
284 SetDParam(0, cur_item->info.clients_on); |
|
285 SetDParam(1, cur_item->info.clients_max); |
|
286 SetDParam(2, cur_item->info.companies_on); |
|
287 SetDParam(3, cur_item->info.companies_max); |
|
288 DrawStringCentered(210, y, STR_NETWORK_GENERAL_ONLINE, 2); |
|
289 |
|
290 // only draw icons if the server is online |
|
291 if (cur_item->online) { |
|
292 // draw a lock if the server is password protected. |
|
293 if (cur_item->info.use_password) DrawSprite(SPR_LOCK, w->widget[8].left + 5, y - 1); |
|
294 |
|
295 // draw red or green icon, depending on compatibility with server. |
|
296 DrawSprite(SPR_BLOT | (cur_item->info.compatible ? PALETTE_TO_GREEN : (cur_item->info.version_compatible ? PALETTE_TO_YELLOW : PALETTE_TO_RED)), w->widget[8].left + 15, y); |
|
297 |
|
298 // draw flag according to server language |
|
299 DrawSprite(SPR_FLAGS_BASE + cur_item->info.server_lang, w->widget[8].left + 25, y); |
|
300 } |
|
301 |
|
302 cur_item = cur_item->next; |
|
303 y += NET_PRC__SIZE_OF_ROW; |
|
304 if (++n == w->vscroll.cap) break; // max number of games in the window |
|
305 } |
|
306 } |
|
307 |
|
308 /* Draw the right menu */ |
|
309 GfxFillRect(311, 43, 539, 92, 157); |
|
310 if (sel == NULL) { |
|
311 DrawStringCentered(425, 58, STR_NETWORK_GAME_INFO, 0); |
|
312 } else if (!sel->online) { |
|
313 SetDParamStr(0, sel->info.server_name); |
|
314 DrawStringCentered(425, 68, STR_ORANGE, 0); // game name |
|
315 |
|
316 DrawStringCentered(425, 132, STR_NETWORK_SERVER_OFFLINE, 0); // server offline |
|
317 } else { // show game info |
|
318 uint16 y = 100; |
|
319 const uint16 x = w->widget[15].left + 5; |
|
320 |
|
321 DrawStringCentered(425, 48, STR_NETWORK_GAME_INFO, 0); |
|
322 |
|
323 |
|
324 SetDParamStr(0, sel->info.server_name); |
|
325 DrawStringCenteredTruncated(w->widget[15].left, w->widget[15].right, 62, STR_ORANGE, 16); // game name |
|
326 |
|
327 SetDParamStr(0, sel->info.map_name); |
|
328 DrawStringCenteredTruncated(w->widget[15].left, w->widget[15].right, 74, STR_02BD, 16); // map name |
|
329 |
|
330 SetDParam(0, sel->info.clients_on); |
|
331 SetDParam(1, sel->info.clients_max); |
|
332 SetDParam(2, sel->info.companies_on); |
|
333 SetDParam(3, sel->info.companies_max); |
|
334 DrawString(x, y, STR_NETWORK_CLIENTS, 2); |
|
335 y += 10; |
|
336 |
|
337 SetDParam(0, _language_dropdown[sel->info.server_lang]); |
|
338 DrawString(x, y, STR_NETWORK_LANGUAGE, 2); // server language |
|
339 y += 10; |
|
340 |
|
341 SetDParam(0, STR_TEMPERATE_LANDSCAPE + sel->info.map_set); |
|
342 DrawString(x, y, STR_NETWORK_TILESET, 2); // tileset |
|
343 y += 10; |
|
344 |
|
345 SetDParam(0, sel->info.map_width); |
|
346 SetDParam(1, sel->info.map_height); |
|
347 DrawString(x, y, STR_NETWORK_MAP_SIZE, 2); // map size |
|
348 y += 10; |
|
349 |
|
350 SetDParamStr(0, sel->info.server_revision); |
|
351 DrawString(x, y, STR_NETWORK_SERVER_VERSION, 2); // server version |
|
352 y += 10; |
|
353 |
|
354 SetDParamStr(0, sel->info.hostname); |
|
355 SetDParam(1, sel->port); |
|
356 DrawString(x, y, STR_NETWORK_SERVER_ADDRESS, 2); // server address |
|
357 y += 10; |
|
358 |
|
359 SetDParam(0, sel->info.start_date); |
|
360 DrawString(x, y, STR_NETWORK_START_DATE, 2); // start date |
|
361 y += 10; |
|
362 |
|
363 SetDParam(0, sel->info.game_date); |
|
364 DrawString(x, y, STR_NETWORK_CURRENT_DATE, 2); // current date |
|
365 y += 10; |
|
366 |
|
367 y += 2; |
|
368 |
|
369 if (!sel->info.compatible) { |
|
370 DrawStringCentered(425, y, sel->info.version_compatible ? STR_NETWORK_GRF_MISMATCH : STR_NETWORK_VERSION_MISMATCH, 0); // server mismatch |
|
371 } else if (sel->info.clients_on == sel->info.clients_max) { |
|
372 // Show: server full, when clients_on == clients_max |
|
373 DrawStringCentered(425, y, STR_NETWORK_SERVER_FULL, 0); // server full |
|
374 } else if (sel->info.use_password) { |
|
375 DrawStringCentered(425, y, STR_NETWORK_PASSWORD, 0); // password warning |
|
376 } |
|
377 |
|
378 y += 10; |
|
379 } |
|
380 } break; |
|
381 |
|
382 case WE_CLICK: |
|
383 nd->field = e->we.click.widget; |
|
384 switch (e->we.click.widget) { |
|
385 case 0: case 14: /* Close 'X' | Cancel button */ |
|
386 DeleteWindowById(WC_NETWORK_WINDOW, 0); |
|
387 break; |
|
388 case 4: case 5: |
|
389 ShowDropDownMenu(w, _lan_internet_types_dropdown, _network_lan_internet, 5, 0, 0); // do it for widget 5 |
|
390 break; |
|
391 case 6: /* Sort by name */ |
|
392 case 7: /* Sort by connected clients */ |
|
393 case 8: /* Connectivity (green dot) */ |
|
394 if (ld->sort_type == e->we.click.widget - 6) ld->flags ^= VL_DESC; |
|
395 ld->flags |= VL_RESORT; |
|
396 ld->sort_type = e->we.click.widget - 6; |
|
397 |
|
398 _ng_sorting.order = !!(ld->flags & VL_DESC); |
|
399 _ng_sorting.criteria = ld->sort_type; |
|
400 SetWindowDirty(w); |
|
401 break; |
|
402 case 9: { /* Matrix to show networkgames */ |
|
403 NetworkGameList *cur_item; |
|
404 uint32 id_v = (e->we.click.pt.y - NET_PRC__OFFSET_TOP_WIDGET) / NET_PRC__SIZE_OF_ROW; |
|
405 |
|
406 if (id_v >= w->vscroll.cap) return; // click out of bounds |
|
407 id_v += w->vscroll.pos; |
|
408 |
|
409 cur_item = _network_game_list; |
|
410 for (; id_v > 0 && cur_item != NULL; id_v--) cur_item = cur_item->next; |
|
411 |
|
412 nd->server = cur_item; |
|
413 SetWindowDirty(w); |
|
414 } break; |
|
415 case 11: /* Find server automatically */ |
|
416 switch (_network_lan_internet) { |
|
417 case 0: NetworkUDPSearchGame(); break; |
|
418 case 1: NetworkUDPQueryMasterServer(); break; |
|
419 } |
|
420 break; |
|
421 case 12: { // Add a server |
|
422 ShowQueryString( |
|
423 BindCString(_network_default_ip), |
|
424 STR_NETWORK_ENTER_IP, |
|
425 31 | 0x1000, // maximum number of characters OR |
|
426 250, // characters up to this width pixels, whichever is satisfied first |
|
427 w, CS_ALPHANUMERAL); |
|
428 } break; |
|
429 case 13: /* Start server */ |
|
430 ShowNetworkStartServerWindow(); |
|
431 break; |
|
432 case 16: /* Join Game */ |
|
433 if (nd->server != NULL) { |
|
434 snprintf(_network_last_host, sizeof(_network_last_host), "%s", inet_ntoa(*(struct in_addr *)&nd->server->ip)); |
|
435 _network_last_port = nd->server->port; |
|
436 ShowNetworkLobbyWindow(nd->server); |
|
437 } |
|
438 break; |
|
439 case 17: // Refresh |
|
440 if (nd->server != NULL) |
|
441 NetworkQueryServer(nd->server->info.hostname, nd->server->port, true); |
|
442 break; |
|
443 case 18: // NewGRF Settings |
|
444 if (nd->server != NULL) ShowNewGRFSettings(false, false, false, &nd->server->info.grfconfig); |
|
445 break; |
|
446 |
|
447 } break; |
|
448 |
|
449 case WE_DROPDOWN_SELECT: /* we have selected a dropdown item in the list */ |
|
450 switch (e->we.dropdown.button) { |
|
451 case 5: |
|
452 _network_lan_internet = e->we.dropdown.index; |
|
453 break; |
|
454 } |
|
455 |
|
456 SetWindowDirty(w); |
|
457 break; |
|
458 |
|
459 case WE_MOUSELOOP: |
|
460 if (nd->field == 3) HandleEditBox(w, &WP(w, network_ql_d).q, 3); |
|
461 break; |
|
462 |
|
463 case WE_MESSAGE: |
|
464 if (e->we.message.msg != 0) nd->server = NULL; |
|
465 ld->flags |= VL_REBUILD; |
|
466 SetWindowDirty(w); |
|
467 break; |
|
468 |
|
469 case WE_KEYPRESS: |
|
470 if (nd->field != 3) { |
|
471 if (nd->server != NULL) { |
|
472 if (e->we.keypress.keycode == WKC_DELETE) { /* Press 'delete' to remove servers */ |
|
473 NetworkGameListRemoveItem(nd->server); |
|
474 NetworkRebuildHostList(); |
|
475 nd->server = NULL; |
|
476 } |
|
477 } |
|
478 break; |
|
479 } |
|
480 |
|
481 if (HandleEditBoxKey(w, &WP(w, network_ql_d).q, 3, e) == 1) break; // enter pressed |
|
482 |
|
483 // The name is only allowed when it starts with a letter! |
|
484 if (_edit_str_buf[0] != '\0' && _edit_str_buf[0] != ' ') { |
|
485 ttd_strlcpy(_network_player_name, _edit_str_buf, lengthof(_network_player_name)); |
|
486 } else { |
|
487 ttd_strlcpy(_network_player_name, "Player", lengthof(_network_player_name)); |
|
488 } |
|
489 |
|
490 break; |
|
491 |
|
492 case WE_ON_EDIT_TEXT: |
|
493 NetworkAddServer(e->we.edittext.str); |
|
494 NetworkRebuildHostList(); |
|
495 break; |
|
496 |
|
497 case WE_DESTROY: /* Nicely clean up the sort-list */ |
|
498 free(WP(w, network_ql_d).sort_list); |
|
499 break; |
|
500 } |
|
501 } |
|
502 |
|
503 static const Widget _network_game_window_widgets[] = { |
|
504 { WWT_CLOSEBOX, RESIZE_NONE, BGC, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, |
|
505 { WWT_CAPTION, RESIZE_NONE, BGC, 11, 549, 0, 13, STR_NETWORK_MULTIPLAYER, STR_NULL}, |
|
506 { WWT_PANEL, RESIZE_NONE, BGC, 0, 549, 14, 263, 0x0, STR_NULL}, |
|
507 |
|
508 /* LEFT SIDE */ |
|
509 { WWT_PANEL, RESIZE_NONE, BGC, 310, 461, 22, 33, 0x0, STR_NETWORK_ENTER_NAME_TIP}, |
|
510 |
|
511 { WWT_INSET, RESIZE_NONE, BGC, 90, 181, 22, 33, STR_NETWORK_COMBO1, STR_NETWORK_CONNECTION_TIP}, |
|
512 { WWT_TEXTBTN, RESIZE_NONE, BGC, 170, 180, 23, 32, STR_0225, STR_NETWORK_CONNECTION_TIP}, |
|
513 |
|
514 { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 10, 170, 42, 53, STR_NETWORK_GAME_NAME, STR_NETWORK_GAME_NAME_TIP}, |
|
515 { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 171, 250, 42, 53, STR_NETWORK_CLIENTS_CAPTION, STR_NETWORK_CLIENTS_CAPTION_TIP}, |
|
516 { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 251, 290, 42, 53, STR_EMPTY, STR_NETWORK_INFO_ICONS_TIP}, |
|
517 |
|
518 { WWT_MATRIX, RESIZE_NONE, BGC, 10, 290, 54, 236, (13 << 8) + 1, STR_NETWORK_CLICK_GAME_TO_SELECT}, |
|
519 { WWT_SCROLLBAR, RESIZE_NONE, BGC, 291, 302, 42, 236, STR_NULL, STR_0190_SCROLL_BAR_SCROLLS_LIST}, |
|
520 |
|
521 { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 30, 130, 246, 257, STR_NETWORK_FIND_SERVER, STR_NETWORK_FIND_SERVER_TIP}, |
|
522 { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 180, 280, 246, 257, STR_NETWORK_ADD_SERVER, STR_NETWORK_ADD_SERVER_TIP}, |
|
523 |
|
524 /* RIGHT SIDE */ |
|
525 { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 315, 415, 246, 257, STR_NETWORK_START_SERVER, STR_NETWORK_START_SERVER_TIP}, |
|
526 { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 430, 535, 246, 257, STR_012E_CANCEL, STR_NULL}, |
|
527 |
|
528 { WWT_PANEL, RESIZE_NONE, BGC, 310, 540, 42, 236, 0x0, STR_NULL}, |
|
529 |
|
530 { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 315, 415, 215, 226, STR_NETWORK_JOIN_GAME, STR_NULL}, |
|
531 { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 430, 535, 215, 226, STR_NETWORK_REFRESH, STR_NETWORK_REFRESH_TIP}, |
|
532 |
|
533 { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 430, 535, 197, 208, STR_NEWGRF_SETTINGS_BUTTON, STR_NULL}, |
|
534 |
|
535 { WIDGETS_END}, |
|
536 }; |
|
537 |
|
538 static const WindowDesc _network_game_window_desc = { |
|
539 WDP_CENTER, WDP_CENTER, 550, 264, |
|
540 WC_NETWORK_WINDOW,0, |
|
541 WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, |
|
542 _network_game_window_widgets, |
|
543 NetworkGameWindowWndProc, |
|
544 }; |
|
545 |
|
546 void ShowNetworkGameWindow(void) |
|
547 { |
|
548 static bool first = true; |
|
549 Window *w; |
|
550 DeleteWindowById(WC_NETWORK_WINDOW, 0); |
|
551 |
|
552 /* Only show once */ |
|
553 if (first) { |
|
554 char* const *srv; |
|
555 |
|
556 first = false; |
|
557 // add all servers from the config file to our list |
|
558 for (srv = &_network_host_list[0]; srv != endof(_network_host_list) && *srv != NULL; srv++) { |
|
559 NetworkAddServer(*srv); |
|
560 } |
|
561 |
|
562 _ng_sorting.criteria = 2; // sort default by collectivity (green-dots on top) |
|
563 _ng_sorting.order = 0; // sort ascending by default |
|
564 } |
|
565 |
|
566 w = AllocateWindowDesc(&_network_game_window_desc); |
|
567 if (w != NULL) { |
|
568 querystr_d *querystr = &WP(w, network_ql_d).q; |
|
569 |
|
570 ttd_strlcpy(_edit_str_buf, _network_player_name, lengthof(_edit_str_buf)); |
|
571 w->vscroll.cap = 13; |
|
572 |
|
573 querystr->afilter = CS_ALPHANUMERAL; |
|
574 InitializeTextBuffer(&querystr->text, _edit_str_buf, lengthof(_edit_str_buf), 120); |
|
575 |
|
576 UpdateNetworkGameWindow(true); |
|
577 } |
|
578 } |
|
579 |
|
580 enum { |
|
581 NSSWND_START = 64, |
|
582 NSSWND_ROWSIZE = 12 |
|
583 }; |
|
584 |
|
585 /* Uses network_ql_d (network_d, querystr_d and list_d) WP macro */ |
|
586 static void NetworkStartServerWindowWndProc(Window *w, WindowEvent *e) |
|
587 { |
|
588 network_d *nd = &WP(w, network_ql_d).n; |
|
589 |
|
590 switch (e->event) { |
|
591 case WE_CREATE: /* focus input box */ |
|
592 nd->field = 3; |
|
593 _network_game_info.use_password = (_network_server_password[0] != '\0'); |
|
594 break; |
|
595 |
|
596 case WE_PAINT: { |
|
597 int y = NSSWND_START, pos; |
|
598 const FiosItem *item; |
|
599 |
|
600 SetDParam( 7, _connection_types_dropdown[_network_advertise]); |
|
601 SetDParam( 9, _players_dropdown[_network_game_info.clients_max]); |
|
602 SetDParam(11, _players_dropdown[_network_game_info.companies_max]); |
|
603 SetDParam(13, _players_dropdown[_network_game_info.spectators_max]); |
|
604 SetDParam(15, _language_dropdown[_network_game_info.server_lang]); |
|
605 DrawWindowWidgets(w); |
|
606 |
|
607 GfxFillRect(11, 63, 258, 215, 0xD7); |
|
608 DrawEditBox(w, &WP(w, network_ql_d).q, 3); |
|
609 |
|
610 DrawString(10, 22, STR_NETWORK_NEW_GAME_NAME, 2); |
|
611 |
|
612 DrawString(10, 43, STR_NETWORK_SELECT_MAP, 2); |
|
613 |
|
614 DrawString(280, 63, STR_NETWORK_CONNECTION, 2); |
|
615 DrawString(280, 95, STR_NETWORK_NUMBER_OF_CLIENTS, 2); |
|
616 DrawString(280, 127, STR_NETWORK_NUMBER_OF_COMPANIES, 2); |
|
617 DrawString(280, 159, STR_NETWORK_NUMBER_OF_SPECTATORS, 2); |
|
618 DrawString(280, 191, STR_NETWORK_LANGUAGE_SPOKEN, 2); |
|
619 |
|
620 if (_network_game_info.use_password) DoDrawString("*", 408, 23, 3); |
|
621 |
|
622 // draw list of maps |
|
623 pos = w->vscroll.pos; |
|
624 while (pos < _fios_num + 1) { |
|
625 item = _fios_list + pos - 1; |
|
626 if (item == nd->map || (pos == 0 && nd->map == NULL)) |
|
627 GfxFillRect(11, y - 1, 258, y + 10, 155); // show highlighted item with a different colour |
|
628 |
|
629 if (pos == 0) { |
|
630 DrawString(14, y, STR_4010_GENERATE_RANDOM_NEW_GAME, 9); |
|
631 } else { |
|
632 DoDrawString(item->title, 14, y, _fios_colors[item->type] ); |
|
633 } |
|
634 pos++; |
|
635 y += NSSWND_ROWSIZE; |
|
636 |
|
637 if (y >= w->vscroll.cap * NSSWND_ROWSIZE + NSSWND_START) break; |
|
638 } |
|
639 } break; |
|
640 |
|
641 case WE_CLICK: |
|
642 nd->field = e->we.click.widget; |
|
643 switch (e->we.click.widget) { |
|
644 case 0: /* Close 'X' */ |
|
645 case 19: /* Cancel button */ |
|
646 ShowNetworkGameWindow(); |
|
647 break; |
|
648 |
|
649 case 4: /* Set password button */ |
|
650 ShowQueryString(BindCString(_network_server_password), STR_NETWORK_SET_PASSWORD, 20, 250, w, CS_ALPHANUMERAL); |
|
651 break; |
|
652 |
|
653 case 5: { /* Select map */ |
|
654 int y = (e->we.click.pt.y - NSSWND_START) / NSSWND_ROWSIZE; |
|
655 |
|
656 y += w->vscroll.pos; |
|
657 if (y >= w->vscroll.count) return; |
|
658 |
|
659 nd->map = (y == 0) ? NULL : _fios_list + y - 1; |
|
660 SetWindowDirty(w); |
|
661 } break; |
|
662 case 7: case 8: /* Connection type */ |
|
663 ShowDropDownMenu(w, _connection_types_dropdown, _network_advertise, 8, 0, 0); // do it for widget 8 |
|
664 break; |
|
665 case 9: case 10: /* Number of Players (hide 0 and 1 players) */ |
|
666 ShowDropDownMenu(w, _players_dropdown, _network_game_info.clients_max, 10, 0, 3); |
|
667 break; |
|
668 case 11: case 12: /* Number of Companies (hide 0, 9 and 10 companies; max is 8) */ |
|
669 ShowDropDownMenu(w, _players_dropdown, _network_game_info.companies_max, 12, 0, 1537); |
|
670 break; |
|
671 case 13: case 14: /* Number of Spectators */ |
|
672 ShowDropDownMenu(w, _players_dropdown, _network_game_info.spectators_max, 14, 0, 0); |
|
673 break; |
|
674 case 15: case 16: /* Language */ |
|
675 ShowDropDownMenu(w, _language_dropdown, _network_game_info.server_lang, 16, 0, 0); |
|
676 break; |
|
677 case 17: /* Start game */ |
|
678 _is_network_server = true; |
|
679 |
|
680 if (nd->map == NULL) { // start random new game |
|
681 ShowGenerateLandscape(); |
|
682 } else { // load a scenario |
|
683 char *name = FiosBrowseTo(nd->map); |
|
684 if (name != NULL) { |
|
685 SetFiosType(nd->map->type); |
|
686 ttd_strlcpy(_file_to_saveload.name, name, sizeof(_file_to_saveload.name)); |
|
687 ttd_strlcpy(_file_to_saveload.title, nd->map->title, sizeof(_file_to_saveload.title)); |
|
688 |
|
689 DeleteWindow(w); |
|
690 SwitchMode(SM_START_SCENARIO); |
|
691 } |
|
692 } |
|
693 break; |
|
694 case 18: /* Load game */ |
|
695 _is_network_server = true; |
|
696 /* XXX - WC_NETWORK_WINDOW should stay, but if it stays, it gets |
|
697 * copied all the elements of 'load game' and upon closing that, it segfaults */ |
|
698 DeleteWindowById(WC_NETWORK_WINDOW, 0); |
|
699 ShowSaveLoadDialog(SLD_LOAD_GAME); |
|
700 break; |
|
701 } |
|
702 break; |
|
703 |
|
704 case WE_DROPDOWN_SELECT: /* we have selected a dropdown item in the list */ |
|
705 switch (e->we.dropdown.button) { |
|
706 case 8: _network_advertise = (e->we.dropdown.index != 0); break; |
|
707 case 10: _network_game_info.clients_max = e->we.dropdown.index; break; |
|
708 case 12: _network_game_info.companies_max = e->we.dropdown.index; break; |
|
709 case 14: _network_game_info.spectators_max = e->we.dropdown.index; break; |
|
710 case 16: _network_game_info.server_lang = e->we.dropdown.index; break; |
|
711 } |
|
712 |
|
713 SetWindowDirty(w); |
|
714 break; |
|
715 |
|
716 case WE_MOUSELOOP: |
|
717 if (nd->field == 3) HandleEditBox(w, &WP(w, network_ql_d).q, 3); |
|
718 break; |
|
719 |
|
720 case WE_KEYPRESS: |
|
721 if (nd->field == 3) { |
|
722 if (HandleEditBoxKey(w, &WP(w, network_ql_d).q, 3, e) == 1) break; // enter pressed |
|
723 |
|
724 ttd_strlcpy(_network_server_name, WP(w, network_ql_d).q.text.buf, sizeof(_network_server_name)); |
|
725 UpdateTextBufferSize(&WP(w, network_ql_d).q.text); |
|
726 } |
|
727 break; |
|
728 |
|
729 case WE_ON_EDIT_TEXT: { |
|
730 ttd_strlcpy(_network_server_password, e->we.edittext.str, lengthof(_network_server_password)); |
|
731 _network_game_info.use_password = (_network_server_password[0] != '\0'); |
|
732 SetWindowDirty(w); |
|
733 } break; |
|
734 } |
|
735 } |
|
736 |
|
737 static const Widget _network_start_server_window_widgets[] = { |
|
738 { WWT_CLOSEBOX, RESIZE_NONE, BGC, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW }, |
|
739 { WWT_CAPTION, RESIZE_NONE, BGC, 11, 419, 0, 13, STR_NETWORK_START_GAME_WINDOW, STR_NULL}, |
|
740 { WWT_PANEL, RESIZE_NONE, BGC, 0, 419, 14, 243, 0x0, STR_NULL}, |
|
741 |
|
742 { WWT_PANEL, RESIZE_NONE, BGC, 100, 272, 22, 33, 0x0, STR_NETWORK_NEW_GAME_NAME_TIP}, |
|
743 { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 285, 405, 22, 33, STR_NETWORK_SET_PASSWORD, STR_NETWORK_PASSWORD_TIP}, |
|
744 |
|
745 { WWT_INSET, RESIZE_NONE, BGC, 10, 271, 62, 216, 0x0, STR_NETWORK_SELECT_MAP_TIP}, |
|
746 { WWT_SCROLLBAR, RESIZE_NONE, BGC, 259, 270, 63, 215, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST}, |
|
747 /* Combo boxes to control Connection Type / Max Clients / Max Companies / Max Observers / Language */ |
|
748 { WWT_INSET, RESIZE_NONE, BGC, 280, 410, 77, 88, STR_NETWORK_COMBO1, STR_NETWORK_CONNECTION_TIP}, |
|
749 { WWT_TEXTBTN, RESIZE_NONE, BGC, 399, 409, 78, 87, STR_0225, STR_NETWORK_CONNECTION_TIP}, |
|
750 { WWT_INSET, RESIZE_NONE, BGC, 280, 410, 109, 120, STR_NETWORK_COMBO2, STR_NETWORK_NUMBER_OF_CLIENTS_TIP}, |
|
751 { WWT_TEXTBTN, RESIZE_NONE, BGC, 399, 409, 110, 119, STR_0225, STR_NETWORK_NUMBER_OF_CLIENTS_TIP}, |
|
752 { WWT_INSET, RESIZE_NONE, BGC, 280, 410, 141, 152, STR_NETWORK_COMBO3, STR_NETWORK_NUMBER_OF_COMPANIES_TIP}, |
|
753 { WWT_TEXTBTN, RESIZE_NONE, BGC, 399, 409, 142, 151, STR_0225, STR_NETWORK_NUMBER_OF_COMPANIES_TIP}, |
|
754 { WWT_INSET, RESIZE_NONE, BGC, 280, 410, 173, 184, STR_NETWORK_COMBO4, STR_NETWORK_NUMBER_OF_SPECTATORS_TIP}, |
|
755 { WWT_TEXTBTN, RESIZE_NONE, BGC, 399, 409, 174, 183, STR_0225, STR_NETWORK_NUMBER_OF_SPECTATORS_TIP}, |
|
756 { WWT_INSET, RESIZE_NONE, BGC, 280, 410, 205, 216, STR_NETWORK_COMBO5, STR_NETWORK_LANGUAGE_TIP}, |
|
757 { WWT_TEXTBTN, RESIZE_NONE, BGC, 399, 409, 206, 215, STR_0225, STR_NETWORK_LANGUAGE_TIP}, |
|
758 |
|
759 { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 40, 140, 224, 235, STR_NETWORK_START_GAME, STR_NETWORK_START_GAME_TIP}, |
|
760 { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 150, 250, 224, 235, STR_NETWORK_LOAD_GAME, STR_NETWORK_LOAD_GAME_TIP}, |
|
761 { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 260, 360, 224, 235, STR_012E_CANCEL, STR_NULL}, |
|
762 { WIDGETS_END}, |
|
763 }; |
|
764 |
|
765 static const WindowDesc _network_start_server_window_desc = { |
|
766 WDP_CENTER, WDP_CENTER, 420, 244, |
|
767 WC_NETWORK_WINDOW,0, |
|
768 WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, |
|
769 _network_start_server_window_widgets, |
|
770 NetworkStartServerWindowWndProc, |
|
771 }; |
|
772 |
|
773 static void ShowNetworkStartServerWindow(void) |
|
774 { |
|
775 Window *w; |
|
776 DeleteWindowById(WC_NETWORK_WINDOW, 0); |
|
777 |
|
778 w = AllocateWindowDesc(&_network_start_server_window_desc); |
|
779 ttd_strlcpy(_edit_str_buf, _network_server_name, lengthof(_edit_str_buf)); |
|
780 |
|
781 _saveload_mode = SLD_NEW_GAME; |
|
782 BuildFileList(); |
|
783 w->vscroll.cap = 12; |
|
784 w->vscroll.count = _fios_num+1; |
|
785 |
|
786 WP(w, network_ql_d).q.afilter = CS_ALPHANUMERAL; |
|
787 InitializeTextBuffer(&WP(w, network_ql_d).q.text, _edit_str_buf, lengthof(_edit_str_buf), 160); |
|
788 } |
|
789 |
|
790 static byte NetworkLobbyFindCompanyIndex(byte pos) |
|
791 { |
|
792 byte i; |
|
793 |
|
794 /* Scroll through all _network_player_info and get the 'pos' item |
|
795 that is not empty */ |
|
796 for (i = 0; i < MAX_PLAYERS; i++) { |
|
797 if (_network_player_info[i].company_name[0] != '\0') { |
|
798 if (pos-- == 0) return i; |
|
799 } |
|
800 } |
|
801 |
|
802 return 0; |
|
803 } |
|
804 |
|
805 /* uses network_d WP macro */ |
|
806 static void NetworkLobbyWindowWndProc(Window *w, WindowEvent *e) |
|
807 { |
|
808 network_d *nd = &WP(w, network_d); |
|
809 |
|
810 switch (e->event) { |
|
811 case WE_CREATE: |
|
812 nd->company = (byte)-1; |
|
813 break; |
|
814 |
|
815 case WE_PAINT: { |
|
816 const NetworkGameInfo *gi = &nd->server->info; |
|
817 int y = NET_PRC__OFFSET_TOP_WIDGET_COMPANY, pos; |
|
818 |
|
819 SetWindowWidgetDisabledState(w, 7, nd->company == (byte)-1); |
|
820 SetWindowWidgetDisabledState(w, 8, gi->companies_on >= gi->companies_max); |
|
821 /* You can not join a server as spectator when it has no companies active.. |
|
822 * it causes some nasty crashes */ |
|
823 SetWindowWidgetDisabledState(w, 9, gi->spectators_on >= gi->spectators_max || |
|
824 gi->companies_on == 0); |
|
825 |
|
826 DrawWindowWidgets(w); |
|
827 |
|
828 SetDParamStr(0, gi->server_name); |
|
829 DrawString(10, 22, STR_NETWORK_PREPARE_TO_JOIN, 2); |
|
830 |
|
831 /* Draw company list */ |
|
832 pos = w->vscroll.pos; |
|
833 while (pos < gi->companies_on) { |
|
834 byte company = NetworkLobbyFindCompanyIndex(pos); |
|
835 bool income = false; |
|
836 if (nd->company == company) |
|
837 GfxFillRect(11, y - 1, 154, y + 10, 10); // show highlighted item with a different colour |
|
838 |
|
839 DoDrawStringTruncated(_network_player_info[company].company_name, 13, y, 16, 135 - 13); |
|
840 if (_network_player_info[company].use_password != 0) DrawSprite(SPR_LOCK, 135, y); |
|
841 |
|
842 /* If the company's income was positive puts a green dot else a red dot */ |
|
843 if (_network_player_info[company].income >= 0) income = true; |
|
844 DrawSprite(SPR_BLOT | (income ? PALETTE_TO_GREEN : PALETTE_TO_RED), 145, y); |
|
845 |
|
846 pos++; |
|
847 y += NET_PRC__SIZE_OF_ROW; |
|
848 if (pos >= w->vscroll.cap) break; |
|
849 } |
|
850 |
|
851 /* Draw info about selected company when it is selected in the left window */ |
|
852 GfxFillRect(174, 39, 403, 75, 157); |
|
853 DrawStringCentered(290, 50, STR_NETWORK_COMPANY_INFO, 0); |
|
854 if (nd->company != (byte)-1) { |
|
855 const uint x = 183; |
|
856 const uint trunc_width = w->widget[6].right - x; |
|
857 y = 80; |
|
858 |
|
859 SetDParam(0, nd->server->info.clients_on); |
|
860 SetDParam(1, nd->server->info.clients_max); |
|
861 SetDParam(2, nd->server->info.companies_on); |
|
862 SetDParam(3, nd->server->info.companies_max); |
|
863 DrawString(x, y, STR_NETWORK_CLIENTS, 2); |
|
864 y += 10; |
|
865 |
|
866 SetDParamStr(0, _network_player_info[nd->company].company_name); |
|
867 DrawStringTruncated(x, y, STR_NETWORK_COMPANY_NAME, 2, trunc_width); |
|
868 y += 10; |
|
869 |
|
870 SetDParam(0, _network_player_info[nd->company].inaugurated_year); |
|
871 DrawString(x, y, STR_NETWORK_INAUGURATION_YEAR, 2); // inauguration year |
|
872 y += 10; |
|
873 |
|
874 SetDParam64(0, _network_player_info[nd->company].company_value); |
|
875 DrawString(x, y, STR_NETWORK_VALUE, 2); // company value |
|
876 y += 10; |
|
877 |
|
878 SetDParam64(0, _network_player_info[nd->company].money); |
|
879 DrawString(x, y, STR_NETWORK_CURRENT_BALANCE, 2); // current balance |
|
880 y += 10; |
|
881 |
|
882 SetDParam64(0, _network_player_info[nd->company].income); |
|
883 DrawString(x, y, STR_NETWORK_LAST_YEARS_INCOME, 2); // last year's income |
|
884 y += 10; |
|
885 |
|
886 SetDParam(0, _network_player_info[nd->company].performance); |
|
887 DrawString(x, y, STR_NETWORK_PERFORMANCE, 2); // performance |
|
888 y += 10; |
|
889 |
|
890 SetDParam(0, _network_player_info[nd->company].num_vehicle[0]); |
|
891 SetDParam(1, _network_player_info[nd->company].num_vehicle[1]); |
|
892 SetDParam(2, _network_player_info[nd->company].num_vehicle[2]); |
|
893 SetDParam(3, _network_player_info[nd->company].num_vehicle[3]); |
|
894 SetDParam(4, _network_player_info[nd->company].num_vehicle[4]); |
|
895 DrawString(x, y, STR_NETWORK_VEHICLES, 2); // vehicles |
|
896 y += 10; |
|
897 |
|
898 SetDParam(0, _network_player_info[nd->company].num_station[0]); |
|
899 SetDParam(1, _network_player_info[nd->company].num_station[1]); |
|
900 SetDParam(2, _network_player_info[nd->company].num_station[2]); |
|
901 SetDParam(3, _network_player_info[nd->company].num_station[3]); |
|
902 SetDParam(4, _network_player_info[nd->company].num_station[4]); |
|
903 DrawString(x, y, STR_NETWORK_STATIONS, 2); // stations |
|
904 y += 10; |
|
905 |
|
906 SetDParamStr(0, _network_player_info[nd->company].players); |
|
907 DrawStringTruncated(x, y, STR_NETWORK_PLAYERS, 2, trunc_width); // players |
|
908 } |
|
909 } break; |
|
910 |
|
911 case WE_CLICK: |
|
912 switch (e->we.click.widget) { |
|
913 case 0: case 11: /* Close 'X' | Cancel button */ |
|
914 ShowNetworkGameWindow(); |
|
915 break; |
|
916 case 4: { /* Company list */ |
|
917 uint32 id_v = (e->we.click.pt.y - NET_PRC__OFFSET_TOP_WIDGET_COMPANY) / NET_PRC__SIZE_OF_ROW; |
|
918 |
|
919 if (id_v >= w->vscroll.cap) return; |
|
920 |
|
921 id_v += w->vscroll.pos; |
|
922 nd->company = (id_v >= nd->server->info.companies_on) ? (byte)-1 : NetworkLobbyFindCompanyIndex(id_v); |
|
923 SetWindowDirty(w); |
|
924 } break; |
|
925 case 7: /* Join company */ |
|
926 if (nd->company != (byte)-1) { |
|
927 _network_playas = nd->company; |
|
928 NetworkClientConnectGame(_network_last_host, _network_last_port); |
|
929 } |
|
930 break; |
|
931 case 8: /* New company */ |
|
932 _network_playas = PLAYER_NEW_COMPANY; |
|
933 NetworkClientConnectGame(_network_last_host, _network_last_port); |
|
934 break; |
|
935 case 9: /* Spectate game */ |
|
936 _network_playas = PLAYER_SPECTATOR; |
|
937 NetworkClientConnectGame(_network_last_host, _network_last_port); |
|
938 break; |
|
939 case 10: /* Refresh */ |
|
940 NetworkQueryServer(_network_last_host, _network_last_port, false); // company info |
|
941 NetworkUDPQueryServer(_network_last_host, _network_last_port); // general data |
|
942 break; |
|
943 } break; |
|
944 |
|
945 case WE_MESSAGE: |
|
946 SetWindowDirty(w); |
|
947 break; |
|
948 } |
|
949 } |
|
950 |
|
951 static const Widget _network_lobby_window_widgets[] = { |
|
952 { WWT_CLOSEBOX, RESIZE_NONE, BGC, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW }, |
|
953 { WWT_CAPTION, RESIZE_NONE, BGC, 11, 419, 0, 13, STR_NETWORK_GAME_LOBBY, STR_NULL}, |
|
954 { WWT_PANEL, RESIZE_NONE, BGC, 0, 419, 14, 234, 0x0, STR_NULL}, |
|
955 |
|
956 // company list |
|
957 { WWT_PANEL, RESIZE_NONE, BTC, 10, 155, 38, 49, 0x0, STR_NULL}, |
|
958 { WWT_MATRIX, RESIZE_NONE, BGC, 10, 155, 50, 190, (10 << 8) + 1, STR_NETWORK_COMPANY_LIST_TIP}, |
|
959 { WWT_SCROLLBAR, RESIZE_NONE, BGC, 156, 167, 38, 190, STR_NULL, STR_0190_SCROLL_BAR_SCROLLS_LIST}, |
|
960 |
|
961 // company/player info |
|
962 { WWT_PANEL, RESIZE_NONE, BGC, 173, 404, 38, 190, 0x0, STR_NULL}, |
|
963 |
|
964 // buttons |
|
965 { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 10, 151, 200, 211, STR_NETWORK_JOIN_COMPANY, STR_NETWORK_JOIN_COMPANY_TIP}, |
|
966 { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 10, 151, 215, 226, STR_NETWORK_NEW_COMPANY, STR_NETWORK_NEW_COMPANY_TIP}, |
|
967 { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 158, 268, 200, 211, STR_NETWORK_SPECTATE_GAME, STR_NETWORK_SPECTATE_GAME_TIP}, |
|
968 { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 158, 268, 215, 226, STR_NETWORK_REFRESH, STR_NETWORK_REFRESH_TIP}, |
|
969 { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 278, 388, 200, 211, STR_012E_CANCEL, STR_NULL}, |
|
970 |
|
971 { WIDGETS_END}, |
|
972 }; |
|
973 |
|
974 static const WindowDesc _network_lobby_window_desc = { |
|
975 WDP_CENTER, WDP_CENTER, 420, 235, |
|
976 WC_NETWORK_WINDOW,0, |
|
977 WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, |
|
978 _network_lobby_window_widgets, |
|
979 NetworkLobbyWindowWndProc, |
|
980 }; |
|
981 |
|
982 /* Show the networklobbywindow with the selected server |
|
983 * @param ngl Selected game pointer which is passed to the new window */ |
|
984 static void ShowNetworkLobbyWindow(NetworkGameList *ngl) |
|
985 { |
|
986 Window *w; |
|
987 DeleteWindowById(WC_NETWORK_WINDOW, 0); |
|
988 |
|
989 NetworkQueryServer(_network_last_host, _network_last_port, false); // company info |
|
990 NetworkUDPQueryServer(_network_last_host, _network_last_port); // general data |
|
991 |
|
992 w = AllocateWindowDesc(&_network_lobby_window_desc); |
|
993 if (w != NULL) { |
|
994 WP(w, network_ql_d).n.server = ngl; |
|
995 strcpy(_edit_str_buf, ""); |
|
996 w->vscroll.cap = 10; |
|
997 } |
|
998 } |
|
999 |
|
1000 // The window below gives information about the connected clients |
|
1001 // and also makes able to give money to them, kick them (if server) |
|
1002 // and stuff like that. |
|
1003 |
|
1004 extern void DrawPlayerIcon(PlayerID pid, int x, int y); |
|
1005 |
|
1006 // Every action must be of this form |
|
1007 typedef void ClientList_Action_Proc(byte client_no); |
|
1008 |
|
1009 // Max 10 actions per client |
|
1010 #define MAX_CLIENTLIST_ACTION 10 |
|
1011 |
|
1012 // Some standard bullshit.. defines variables ;) |
|
1013 static void ClientListWndProc(Window *w, WindowEvent *e); |
|
1014 static void ClientListPopupWndProc(Window *w, WindowEvent *e); |
|
1015 static byte _selected_clientlist_item = 255; |
|
1016 static byte _selected_clientlist_y = 0; |
|
1017 static char _clientlist_action[MAX_CLIENTLIST_ACTION][50]; |
|
1018 static ClientList_Action_Proc *_clientlist_proc[MAX_CLIENTLIST_ACTION]; |
|
1019 |
|
1020 enum { |
|
1021 CLNWND_OFFSET = 16, |
|
1022 CLNWND_ROWSIZE = 10 |
|
1023 }; |
|
1024 |
|
1025 static const Widget _client_list_widgets[] = { |
|
1026 { WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, |
|
1027 { WWT_CAPTION, RESIZE_NONE, 14, 11, 249, 0, 13, STR_NETWORK_CLIENT_LIST, STR_018C_WINDOW_TITLE_DRAG_THIS}, |
|
1028 |
|
1029 { WWT_PANEL, RESIZE_NONE, 14, 0, 249, 14, 14 + CLNWND_ROWSIZE + 1, 0x0, STR_NULL}, |
|
1030 { WIDGETS_END}, |
|
1031 }; |
|
1032 |
|
1033 static const Widget _client_list_popup_widgets[] = { |
|
1034 { WWT_PANEL, RESIZE_NONE, 14, 0, 99, 0, 0, 0, STR_NULL}, |
|
1035 { WIDGETS_END}, |
|
1036 }; |
|
1037 |
|
1038 static WindowDesc _client_list_desc = { |
|
1039 WDP_AUTO, WDP_AUTO, 250, 1, |
|
1040 WC_CLIENT_LIST,0, |
|
1041 WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET, |
|
1042 _client_list_widgets, |
|
1043 ClientListWndProc |
|
1044 }; |
|
1045 |
|
1046 // Finds the Xth client-info that is active |
|
1047 static const NetworkClientInfo *NetworkFindClientInfo(byte client_no) |
|
1048 { |
|
1049 const NetworkClientInfo *ci; |
|
1050 |
|
1051 FOR_ALL_ACTIVE_CLIENT_INFOS(ci) { |
|
1052 if (client_no == 0) return ci; |
|
1053 client_no--; |
|
1054 } |
|
1055 |
|
1056 return NULL; |
|
1057 } |
|
1058 |
|
1059 // Here we start to define the options out of the menu |
|
1060 static void ClientList_Kick(byte client_no) |
|
1061 { |
|
1062 if (client_no < MAX_PLAYERS) |
|
1063 SEND_COMMAND(PACKET_SERVER_ERROR)(DEREF_CLIENT(client_no), NETWORK_ERROR_KICKED); |
|
1064 } |
|
1065 |
|
1066 static void ClientList_Ban(byte client_no) |
|
1067 { |
|
1068 uint i; |
|
1069 uint32 ip = NetworkFindClientInfo(client_no)->client_ip; |
|
1070 |
|
1071 for (i = 0; i < lengthof(_network_ban_list); i++) { |
|
1072 if (_network_ban_list[i] == NULL) { |
|
1073 _network_ban_list[i] = strdup(inet_ntoa(*(struct in_addr *)&ip)); |
|
1074 break; |
|
1075 } |
|
1076 } |
|
1077 |
|
1078 if (client_no < MAX_PLAYERS) |
|
1079 SEND_COMMAND(PACKET_SERVER_ERROR)(DEREF_CLIENT(client_no), NETWORK_ERROR_KICKED); |
|
1080 } |
|
1081 |
|
1082 static void ClientList_GiveMoney(byte client_no) |
|
1083 { |
|
1084 if (NetworkFindClientInfo(client_no) != NULL) |
|
1085 ShowNetworkGiveMoneyWindow(NetworkFindClientInfo(client_no)->client_playas); |
|
1086 } |
|
1087 |
|
1088 static void ClientList_SpeakToClient(byte client_no) |
|
1089 { |
|
1090 if (NetworkFindClientInfo(client_no) != NULL) |
|
1091 ShowNetworkChatQueryWindow(DESTTYPE_CLIENT, NetworkFindClientInfo(client_no)->client_index); |
|
1092 } |
|
1093 |
|
1094 static void ClientList_SpeakToCompany(byte client_no) |
|
1095 { |
|
1096 if (NetworkFindClientInfo(client_no) != NULL) |
|
1097 ShowNetworkChatQueryWindow(DESTTYPE_TEAM, NetworkFindClientInfo(client_no)->client_playas); |
|
1098 } |
|
1099 |
|
1100 static void ClientList_SpeakToAll(byte client_no) |
|
1101 { |
|
1102 ShowNetworkChatQueryWindow(DESTTYPE_BROADCAST, 0); |
|
1103 } |
|
1104 |
|
1105 static void ClientList_None(byte client_no) |
|
1106 { |
|
1107 // No action ;) |
|
1108 } |
|
1109 |
|
1110 |
|
1111 |
|
1112 // Help, a action is clicked! What do we do? |
|
1113 static void HandleClientListPopupClick(byte index, byte clientno) { |
|
1114 // A click on the Popup of the ClientList.. handle the command |
|
1115 if (index < MAX_CLIENTLIST_ACTION && _clientlist_proc[index] != NULL) { |
|
1116 _clientlist_proc[index](clientno); |
|
1117 } |
|
1118 } |
|
1119 |
|
1120 // Finds the amount of clients and set the height correct |
|
1121 static bool CheckClientListHeight(Window *w) |
|
1122 { |
|
1123 int num = 0; |
|
1124 const NetworkClientInfo *ci; |
|
1125 |
|
1126 // Should be replaced with a loop through all clients |
|
1127 FOR_ALL_ACTIVE_CLIENT_INFOS(ci) { |
|
1128 num++; |
|
1129 } |
|
1130 |
|
1131 num *= CLNWND_ROWSIZE; |
|
1132 |
|
1133 // If height is changed |
|
1134 if (w->height != CLNWND_OFFSET + num + 1) { |
|
1135 // XXX - magic unfortunately; (num + 2) has to be one bigger than heigh (num + 1) |
|
1136 SetWindowDirty(w); |
|
1137 w->widget[2].bottom = w->widget[2].top + num + 2; |
|
1138 w->height = CLNWND_OFFSET + num + 1; |
|
1139 SetWindowDirty(w); |
|
1140 return false; |
|
1141 } |
|
1142 return true; |
|
1143 } |
|
1144 |
|
1145 // Finds the amount of actions in the popup and set the height correct |
|
1146 static uint ClientListPopupHeigth(void) { |
|
1147 int i, num = 0; |
|
1148 |
|
1149 // Find the amount of actions |
|
1150 for (i = 0; i < MAX_CLIENTLIST_ACTION; i++) { |
|
1151 if (_clientlist_action[i][0] == '\0') continue; |
|
1152 if (_clientlist_proc[i] == NULL) continue; |
|
1153 num++; |
|
1154 } |
|
1155 |
|
1156 num *= CLNWND_ROWSIZE; |
|
1157 |
|
1158 return num + 1; |
|
1159 } |
|
1160 |
|
1161 // Show the popup (action list) |
|
1162 static Window *PopupClientList(Window *w, int client_no, int x, int y) |
|
1163 { |
|
1164 int i, h; |
|
1165 const NetworkClientInfo *ci; |
|
1166 DeleteWindowById(WC_TOOLBAR_MENU, 0); |
|
1167 |
|
1168 // Clean the current actions |
|
1169 for (i = 0; i < MAX_CLIENTLIST_ACTION; i++) { |
|
1170 _clientlist_action[i][0] = '\0'; |
|
1171 _clientlist_proc[i] = NULL; |
|
1172 } |
|
1173 |
|
1174 // Fill the actions this client has |
|
1175 // Watch is, max 50 chars long! |
|
1176 |
|
1177 ci = NetworkFindClientInfo(client_no); |
|
1178 if (ci == NULL) return NULL; |
|
1179 |
|
1180 i = 0; |
|
1181 if (_network_own_client_index != ci->client_index) { |
|
1182 GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_SPEAK_TO_CLIENT, lastof(_clientlist_action[i])); |
|
1183 _clientlist_proc[i++] = &ClientList_SpeakToClient; |
|
1184 } |
|
1185 |
|
1186 if (IsValidPlayer(ci->client_playas) || ci->client_playas == PLAYER_SPECTATOR) { |
|
1187 GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_SPEAK_TO_COMPANY, lastof(_clientlist_action[i])); |
|
1188 _clientlist_proc[i++] = &ClientList_SpeakToCompany; |
|
1189 } |
|
1190 GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_SPEAK_TO_ALL, lastof(_clientlist_action[i])); |
|
1191 _clientlist_proc[i++] = &ClientList_SpeakToAll; |
|
1192 |
|
1193 if (_network_own_client_index != ci->client_index) { |
|
1194 /* We are no spectator and the player we want to give money to is no spectator */ |
|
1195 if (IsValidPlayer(_network_playas) && IsValidPlayer(ci->client_playas)) { |
|
1196 GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_GIVE_MONEY, lastof(_clientlist_action[i])); |
|
1197 _clientlist_proc[i++] = &ClientList_GiveMoney; |
|
1198 } |
|
1199 } |
|
1200 |
|
1201 // A server can kick clients (but not himself) |
|
1202 if (_network_server && _network_own_client_index != ci->client_index) { |
|
1203 GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_KICK, lastof(_clientlist_action[i])); |
|
1204 _clientlist_proc[i++] = &ClientList_Kick; |
|
1205 |
|
1206 sprintf(_clientlist_action[i],"Ban"); // XXX GetString? |
|
1207 _clientlist_proc[i++] = &ClientList_Ban; |
|
1208 } |
|
1209 |
|
1210 if (i == 0) { |
|
1211 GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_NONE, lastof(_clientlist_action[i])); |
|
1212 _clientlist_proc[i++] = &ClientList_None; |
|
1213 } |
|
1214 |
|
1215 /* Calculate the height */ |
|
1216 h = ClientListPopupHeigth(); |
|
1217 |
|
1218 // Allocate the popup |
|
1219 w = AllocateWindow(x, y, 150, h + 1, ClientListPopupWndProc, WC_TOOLBAR_MENU, _client_list_popup_widgets); |
|
1220 w->widget[0].bottom = w->widget[0].top + h; |
|
1221 w->widget[0].right = w->widget[0].left + 150; |
|
1222 |
|
1223 w->flags4 &= ~WF_WHITE_BORDER_MASK; |
|
1224 WP(w,menu_d).item_count = 0; |
|
1225 // Save our client |
|
1226 WP(w,menu_d).main_button = client_no; |
|
1227 WP(w,menu_d).sel_index = 0; |
|
1228 // We are a popup |
|
1229 _popup_menu_active = true; |
|
1230 |
|
1231 return w; |
|
1232 } |
|
1233 |
|
1234 /** Main handle for the client popup list |
|
1235 * uses menu_d WP macro */ |
|
1236 static void ClientListPopupWndProc(Window *w, WindowEvent *e) |
|
1237 { |
|
1238 switch (e->event) { |
|
1239 case WE_PAINT: { |
|
1240 int i, y, sel; |
|
1241 byte colour; |
|
1242 DrawWindowWidgets(w); |
|
1243 |
|
1244 // Draw the actions |
|
1245 sel = WP(w,menu_d).sel_index; |
|
1246 y = 1; |
|
1247 for (i = 0; i < MAX_CLIENTLIST_ACTION; i++, y += CLNWND_ROWSIZE) { |
|
1248 if (_clientlist_action[i][0] == '\0') continue; |
|
1249 if (_clientlist_proc[i] == NULL) continue; |
|
1250 |
|
1251 if (sel-- == 0) { // Selected item, highlight it |
|
1252 GfxFillRect(1, y, 150 - 2, y + CLNWND_ROWSIZE - 1, 0); |
|
1253 colour = 0xC; |
|
1254 } else { |
|
1255 colour = 0x10; |
|
1256 } |
|
1257 |
|
1258 DoDrawString(_clientlist_action[i], 4, y, colour); |
|
1259 } |
|
1260 } break; |
|
1261 |
|
1262 case WE_POPUPMENU_SELECT: { |
|
1263 // We selected an action |
|
1264 int index = (e->we.popupmenu.pt.y - w->top) / CLNWND_ROWSIZE; |
|
1265 |
|
1266 if (index >= 0 && e->we.popupmenu.pt.y >= w->top) |
|
1267 HandleClientListPopupClick(index, WP(w,menu_d).main_button); |
|
1268 |
|
1269 DeleteWindowById(WC_TOOLBAR_MENU, 0); |
|
1270 } break; |
|
1271 |
|
1272 case WE_POPUPMENU_OVER: { |
|
1273 // Our mouse hoovers over an action? Select it! |
|
1274 int index = (e->we.popupmenu.pt.y - w->top) / CLNWND_ROWSIZE; |
|
1275 |
|
1276 if (index == -1 || index == WP(w,menu_d).sel_index) return; |
|
1277 |
|
1278 WP(w,menu_d).sel_index = index; |
|
1279 SetWindowDirty(w); |
|
1280 } break; |
|
1281 |
|
1282 } |
|
1283 } |
|
1284 |
|
1285 // Main handle for clientlist |
|
1286 static void ClientListWndProc(Window *w, WindowEvent *e) |
|
1287 { |
|
1288 switch (e->event) { |
|
1289 case WE_PAINT: { |
|
1290 NetworkClientInfo *ci; |
|
1291 int y, i = 0; |
|
1292 byte colour; |
|
1293 |
|
1294 // Check if we need to reset the height |
|
1295 if (!CheckClientListHeight(w)) break; |
|
1296 |
|
1297 DrawWindowWidgets(w); |
|
1298 |
|
1299 y = CLNWND_OFFSET; |
|
1300 |
|
1301 FOR_ALL_ACTIVE_CLIENT_INFOS(ci) { |
|
1302 if (_selected_clientlist_item == i++) { // Selected item, highlight it |
|
1303 GfxFillRect(1, y, 248, y + CLNWND_ROWSIZE - 1, 0); |
|
1304 colour = 0xC; |
|
1305 } else { |
|
1306 colour = 0x10; |
|
1307 } |
|
1308 |
|
1309 if (ci->client_index == NETWORK_SERVER_INDEX) { |
|
1310 DrawString(4, y, STR_NETWORK_SERVER, colour); |
|
1311 } else { |
|
1312 DrawString(4, y, STR_NETWORK_CLIENT, colour); |
|
1313 } |
|
1314 |
|
1315 // Filter out spectators |
|
1316 if (IsValidPlayer(ci->client_playas)) DrawPlayerIcon(ci->client_playas, 64, y + 1); |
|
1317 |
|
1318 DoDrawString(ci->client_name, 81, y, colour); |
|
1319 |
|
1320 y += CLNWND_ROWSIZE; |
|
1321 } |
|
1322 } break; |
|
1323 |
|
1324 case WE_CLICK: |
|
1325 // Show the popup with option |
|
1326 if (_selected_clientlist_item != 255) { |
|
1327 PopupClientList(w, _selected_clientlist_item, e->we.click.pt.x + w->left, e->we.click.pt.y + w->top); |
|
1328 } |
|
1329 |
|
1330 break; |
|
1331 |
|
1332 case WE_MOUSEOVER: |
|
1333 // -1 means we left the current window |
|
1334 if (e->we.mouseover.pt.y == -1) { |
|
1335 _selected_clientlist_y = 0; |
|
1336 _selected_clientlist_item = 255; |
|
1337 SetWindowDirty(w); |
|
1338 break; |
|
1339 } |
|
1340 // It did not change.. no update! |
|
1341 if (e->we.mouseover.pt.y == _selected_clientlist_y) break; |
|
1342 |
|
1343 // Find the new selected item (if any) |
|
1344 _selected_clientlist_y = e->we.mouseover.pt.y; |
|
1345 if (e->we.mouseover.pt.y > CLNWND_OFFSET) { |
|
1346 _selected_clientlist_item = (e->we.mouseover.pt.y - CLNWND_OFFSET) / CLNWND_ROWSIZE; |
|
1347 } else { |
|
1348 _selected_clientlist_item = 255; |
|
1349 } |
|
1350 |
|
1351 // Repaint |
|
1352 SetWindowDirty(w); |
|
1353 break; |
|
1354 |
|
1355 case WE_DESTROY: case WE_CREATE: |
|
1356 // When created or destroyed, data is reset |
|
1357 _selected_clientlist_item = 255; |
|
1358 _selected_clientlist_y = 0; |
|
1359 break; |
|
1360 } |
|
1361 } |
|
1362 |
|
1363 void ShowClientList(void) |
|
1364 { |
|
1365 AllocateWindowDescFront(&_client_list_desc, 0); |
|
1366 } |
|
1367 |
|
1368 |
|
1369 static NetworkPasswordType pw_type; |
|
1370 |
|
1371 |
|
1372 void ShowNetworkNeedPassword(NetworkPasswordType npt) |
|
1373 { |
|
1374 StringID caption; |
|
1375 |
|
1376 pw_type = npt; |
|
1377 switch (npt) { |
|
1378 default: NOT_REACHED(); |
|
1379 case NETWORK_GAME_PASSWORD: caption = STR_NETWORK_NEED_GAME_PASSWORD_CAPTION; break; |
|
1380 case NETWORK_COMPANY_PASSWORD: caption = STR_NETWORK_NEED_COMPANY_PASSWORD_CAPTION; break; |
|
1381 } |
|
1382 ShowQueryString(STR_EMPTY, caption, 20, 180, FindWindowById(WC_NETWORK_STATUS_WINDOW, 0), CS_ALPHANUMERAL); |
|
1383 } |
|
1384 |
|
1385 |
|
1386 static void NetworkJoinStatusWindowWndProc(Window *w, WindowEvent *e) |
|
1387 { |
|
1388 switch (e->event) { |
|
1389 case WE_PAINT: { |
|
1390 uint8 progress; // used for progress bar |
|
1391 DrawWindowWidgets(w); |
|
1392 |
|
1393 DrawStringCentered(125, 35, STR_NETWORK_CONNECTING_1 + _network_join_status, 14); |
|
1394 switch (_network_join_status) { |
|
1395 case NETWORK_JOIN_STATUS_CONNECTING: case NETWORK_JOIN_STATUS_AUTHORIZING: |
|
1396 case NETWORK_JOIN_STATUS_GETTING_COMPANY_INFO: |
|
1397 progress = 10; // first two stages 10% |
|
1398 break; |
|
1399 case NETWORK_JOIN_STATUS_WAITING: |
|
1400 SetDParam(0, _network_join_waiting); |
|
1401 DrawStringCentered(125, 46, STR_NETWORK_CONNECTING_WAITING, 14); |
|
1402 progress = 15; // third stage is 15% |
|
1403 break; |
|
1404 case NETWORK_JOIN_STATUS_DOWNLOADING: |
|
1405 SetDParam(0, _network_join_kbytes); |
|
1406 SetDParam(1, _network_join_kbytes_total); |
|
1407 DrawStringCentered(125, 46, STR_NETWORK_CONNECTING_DOWNLOADING, 14); |
|
1408 /* Fallthrough */ |
|
1409 default: /* Waiting is 15%, so the resting receivement of map is maximum 70% */ |
|
1410 progress = 15 + _network_join_kbytes * (100 - 15) / _network_join_kbytes_total; |
|
1411 } |
|
1412 |
|
1413 /* Draw nice progress bar :) */ |
|
1414 DrawFrameRect(20, 18, (int)((w->width - 20) * progress / 100), 28, 10, 0); |
|
1415 } break; |
|
1416 |
|
1417 case WE_CLICK: |
|
1418 switch (e->we.click.widget) { |
|
1419 case 2: /* Disconnect button */ |
|
1420 NetworkDisconnect(); |
|
1421 DeleteWindow(w); |
|
1422 SwitchMode(SM_MENU); |
|
1423 ShowNetworkGameWindow(); |
|
1424 break; |
|
1425 } |
|
1426 break; |
|
1427 |
|
1428 /* If the server asks for a password, we need to fill it in */ |
|
1429 case WE_ON_EDIT_TEXT_CANCEL: |
|
1430 NetworkDisconnect(); |
|
1431 ShowNetworkGameWindow(); |
|
1432 break; |
|
1433 |
|
1434 case WE_ON_EDIT_TEXT: |
|
1435 SEND_COMMAND(PACKET_CLIENT_PASSWORD)(pw_type, e->we.edittext.str); |
|
1436 break; |
|
1437 } |
|
1438 } |
|
1439 |
|
1440 static const Widget _network_join_status_window_widget[] = { |
|
1441 { WWT_CAPTION, RESIZE_NONE, 14, 0, 249, 0, 13, STR_NETWORK_CONNECTING, STR_018C_WINDOW_TITLE_DRAG_THIS}, |
|
1442 { WWT_PANEL, RESIZE_NONE, 14, 0, 249, 14, 84, 0x0, STR_NULL}, |
|
1443 { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 75, 175, 69, 80, STR_NETWORK_DISCONNECT, STR_NULL}, |
|
1444 { WIDGETS_END}, |
|
1445 }; |
|
1446 |
|
1447 static const WindowDesc _network_join_status_window_desc = { |
|
1448 WDP_CENTER, WDP_CENTER, 250, 85, |
|
1449 WC_NETWORK_STATUS_WINDOW, 0, |
|
1450 WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_MODAL, |
|
1451 _network_join_status_window_widget, |
|
1452 NetworkJoinStatusWindowWndProc, |
|
1453 }; |
|
1454 |
|
1455 void ShowJoinStatusWindow(void) |
|
1456 { |
|
1457 Window *w; |
|
1458 DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0); |
|
1459 w = AllocateWindowDesc(&_network_join_status_window_desc); |
|
1460 /* Parent the status window to the lobby */ |
|
1461 if (w != NULL) w->parent = FindWindowById(WC_NETWORK_WINDOW, 0); |
|
1462 } |
|
1463 |
|
1464 static void SendChat(const char *buf, DestType type, byte dest) |
|
1465 { |
|
1466 if (buf[0] == '\0') return; |
|
1467 if (!_network_server) { |
|
1468 SEND_COMMAND(PACKET_CLIENT_CHAT)(NETWORK_ACTION_CHAT + type, type, dest, buf); |
|
1469 } else { |
|
1470 NetworkServer_HandleChat(NETWORK_ACTION_CHAT + type, type, dest, buf, NETWORK_SERVER_INDEX); |
|
1471 } |
|
1472 } |
|
1473 |
|
1474 /** |
|
1475 * Find the next item of the list of things that can be auto-completed. |
|
1476 * @param item The current indexed item to return. This function can, and most |
|
1477 * likely will, alter item, to skip empty items in the arrays. |
|
1478 * @return Returns the char that matched to the index. |
|
1479 */ |
|
1480 static const char *ChatTabCompletionNextItem(uint *item) |
|
1481 { |
|
1482 static char chat_tab_temp_buffer[64]; |
|
1483 |
|
1484 /* First, try clients */ |
|
1485 if (*item < MAX_CLIENT_INFO) { |
|
1486 /* Skip inactive clients */ |
|
1487 while (_network_client_info[*item].client_index == NETWORK_EMPTY_INDEX && *item < MAX_CLIENT_INFO) (*item)++; |
|
1488 if (*item < MAX_CLIENT_INFO) return _network_client_info[*item].client_name; |
|
1489 } |
|
1490 |
|
1491 /* Then, try townnames */ |
|
1492 /* Not that the following assumes all town indices are adjacent, ie no |
|
1493 * towns have been deleted. */ |
|
1494 if (*item <= (uint)MAX_CLIENT_INFO + GetMaxTownIndex()) { |
|
1495 const Town *t; |
|
1496 |
|
1497 FOR_ALL_TOWNS_FROM(t, *item - MAX_CLIENT_INFO) { |
|
1498 /* Get the town-name via the string-system */ |
|
1499 SetDParam(0, t->townnameparts); |
|
1500 GetString(chat_tab_temp_buffer, t->townnametype, lastof(chat_tab_temp_buffer)); |
|
1501 return &chat_tab_temp_buffer[0]; |
|
1502 } |
|
1503 } |
|
1504 |
|
1505 return NULL; |
|
1506 } |
|
1507 |
|
1508 /** |
|
1509 * Find what text to complete. It scans for a space from the left and marks |
|
1510 * the word right from that as to complete. It also writes a \0 at the |
|
1511 * position of the space (if any). If nothing found, buf is returned. |
|
1512 */ |
|
1513 static char *ChatTabCompletionFindText(char *buf) |
|
1514 { |
|
1515 char *p = strrchr(buf, ' '); |
|
1516 if (p == NULL) return buf; |
|
1517 |
|
1518 *p = '\0'; |
|
1519 return p + 1; |
|
1520 } |
|
1521 |
|
1522 /** |
|
1523 * See if we can auto-complete the current text of the user. |
|
1524 */ |
|
1525 static void ChatTabCompletion(Window *w) |
|
1526 { |
|
1527 static char _chat_tab_completion_buf[lengthof(_edit_str_buf)]; |
|
1528 Textbuf *tb = &WP(w, querystr_d).text; |
|
1529 uint len, tb_len; |
|
1530 uint item; |
|
1531 char *tb_buf, *pre_buf; |
|
1532 const char *cur_name; |
|
1533 bool second_scan = false; |
|
1534 |
|
1535 item = 0; |
|
1536 |
|
1537 /* Copy the buffer so we can modify it without damaging the real data */ |
|
1538 pre_buf = (_chat_tab_completion_active) ? strdup(_chat_tab_completion_buf) : strdup(tb->buf); |
|
1539 |
|
1540 tb_buf = ChatTabCompletionFindText(pre_buf); |
|
1541 tb_len = strlen(tb_buf); |
|
1542 |
|
1543 while ((cur_name = ChatTabCompletionNextItem(&item)) != NULL) { |
|
1544 item++; |
|
1545 |
|
1546 if (_chat_tab_completion_active) { |
|
1547 /* We are pressing TAB again on the same name, is there an other name |
|
1548 * that starts with this? */ |
|
1549 if (!second_scan) { |
|
1550 uint offset; |
|
1551 uint length; |
|
1552 |
|
1553 /* If we are completing at the begin of the line, skip the ': ' we added */ |
|
1554 if (tb_buf == pre_buf) { |
|
1555 offset = 0; |
|
1556 length = tb->length - 2; |
|
1557 } else { |
|
1558 /* Else, find the place we are completing at */ |
|
1559 offset = strlen(pre_buf) + 1; |
|
1560 length = tb->length - offset; |
|
1561 } |
|
1562 |
|
1563 /* Compare if we have a match */ |
|
1564 if (strlen(cur_name) == length && strncmp(cur_name, tb->buf + offset, length) == 0) second_scan = true; |
|
1565 |
|
1566 continue; |
|
1567 } |
|
1568 |
|
1569 /* Now any match we make on _chat_tab_completion_buf after this, is perfect */ |
|
1570 } |
|
1571 |
|
1572 len = strlen(cur_name); |
|
1573 if (tb_len < len && strncasecmp(cur_name, tb_buf, tb_len) == 0) { |
|
1574 /* Save the data it was before completion */ |
|
1575 if (!second_scan) snprintf(_chat_tab_completion_buf, lengthof(_chat_tab_completion_buf), "%s", tb->buf); |
|
1576 _chat_tab_completion_active = true; |
|
1577 |
|
1578 /* Change to the found name. Add ': ' if we are at the start of the line (pretty) */ |
|
1579 if (pre_buf == tb_buf) { |
|
1580 snprintf(tb->buf, lengthof(_edit_str_buf), "%s: ", cur_name); |
|
1581 } else { |
|
1582 snprintf(tb->buf, lengthof(_edit_str_buf), "%s %s", pre_buf, cur_name); |
|
1583 } |
|
1584 |
|
1585 /* Update the textbuffer */ |
|
1586 UpdateTextBufferSize(&WP(w, querystr_d).text); |
|
1587 |
|
1588 SetWindowDirty(w); |
|
1589 free(pre_buf); |
|
1590 return; |
|
1591 } |
|
1592 } |
|
1593 |
|
1594 if (second_scan) { |
|
1595 /* We walked all posibilities, and the user presses tab again.. revert to original text */ |
|
1596 strcpy(tb->buf, _chat_tab_completion_buf); |
|
1597 _chat_tab_completion_active = false; |
|
1598 |
|
1599 /* Update the textbuffer */ |
|
1600 UpdateTextBufferSize(&WP(w, querystr_d).text); |
|
1601 |
|
1602 SetWindowDirty(w); |
|
1603 } |
|
1604 free(pre_buf); |
|
1605 } |
|
1606 |
|
1607 /* uses querystr_d WP macro |
|
1608 * uses querystr_d->caption to store |
|
1609 * - type of chat message (Private/Team/All) in bytes 0-7 |
|
1610 * - destination of chat message in the case of Team/Private in bytes 8-15 */ |
|
1611 static void ChatWindowWndProc(Window *w, WindowEvent *e) |
|
1612 { |
|
1613 switch (e->event) { |
|
1614 case WE_CREATE: |
|
1615 SendWindowMessage(WC_NEWS_WINDOW, 0, WE_CREATE, w->height, 0); |
|
1616 SETBIT(_no_scroll, SCROLL_CHAT); // do not scroll the game with the arrow-keys |
|
1617 break; |
|
1618 |
|
1619 case WE_PAINT: { |
|
1620 static const StringID chat_captions[] = { |
|
1621 STR_NETWORK_CHAT_ALL_CAPTION, |
|
1622 STR_NETWORK_CHAT_COMPANY_CAPTION, |
|
1623 STR_NETWORK_CHAT_CLIENT_CAPTION |
|
1624 }; |
|
1625 StringID msg; |
|
1626 |
|
1627 DrawWindowWidgets(w); |
|
1628 |
|
1629 assert(GB(WP(w, querystr_d).caption, 0, 8) < lengthof(chat_captions)); |
|
1630 msg = chat_captions[GB(WP(w, querystr_d).caption, 0, 8)]; |
|
1631 DrawStringRightAligned(w->widget[2].left - 2, w->widget[2].top + 1, msg, 16); |
|
1632 DrawEditBox(w, &WP(w, querystr_d), 2); |
|
1633 } break; |
|
1634 |
|
1635 case WE_CLICK: |
|
1636 switch (e->we.click.widget) { |
|
1637 case 3: { /* Send */ |
|
1638 DestType type = GB(WP(w, querystr_d).caption, 0, 8); |
|
1639 byte dest = GB(WP(w, querystr_d).caption, 8, 8); |
|
1640 SendChat(WP(w, querystr_d).text.buf, type, dest); |
|
1641 } /* FALLTHROUGH */ |
|
1642 case 0: /* Cancel */ DeleteWindow(w); break; |
|
1643 } |
|
1644 break; |
|
1645 |
|
1646 case WE_MOUSELOOP: |
|
1647 HandleEditBox(w, &WP(w, querystr_d), 2); |
|
1648 break; |
|
1649 |
|
1650 case WE_KEYPRESS: |
|
1651 if (e->we.keypress.keycode == WKC_TAB) { |
|
1652 ChatTabCompletion(w); |
|
1653 } else { |
|
1654 _chat_tab_completion_active = false; |
|
1655 switch (HandleEditBoxKey(w, &WP(w, querystr_d), 2, e)) { |
|
1656 case 1: { /* Return */ |
|
1657 DestType type = GB(WP(w, querystr_d).caption, 0, 8); |
|
1658 byte dest = GB(WP(w, querystr_d).caption, 8, 8); |
|
1659 SendChat(WP(w, querystr_d).text.buf, type, dest); |
|
1660 } /* FALLTHROUGH */ |
|
1661 case 2: /* Escape */ DeleteWindow(w); break; |
|
1662 } |
|
1663 } |
|
1664 break; |
|
1665 |
|
1666 case WE_DESTROY: |
|
1667 SendWindowMessage(WC_NEWS_WINDOW, 0, WE_DESTROY, 0, 0); |
|
1668 CLRBIT(_no_scroll, SCROLL_CHAT); |
|
1669 break; |
|
1670 } |
|
1671 } |
|
1672 |
|
1673 static const Widget _chat_window_widgets[] = { |
|
1674 { WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, |
|
1675 { WWT_PANEL, RESIZE_NONE, 14, 11, 639, 0, 13, 0x0, STR_NULL}, // background |
|
1676 { WWT_PANEL, RESIZE_NONE, 14, 75, 577, 1, 12, 0x0, STR_NULL}, // text box |
|
1677 { WWT_PUSHTXTBTN, RESIZE_NONE, 14, 578, 639, 1, 12, STR_NETWORK_SEND, STR_NULL}, // send button |
|
1678 { WIDGETS_END}, |
|
1679 }; |
|
1680 |
|
1681 static const WindowDesc _chat_window_desc = { |
|
1682 WDP_CENTER, -26, 640, 14, // x, y, width, height |
|
1683 WC_SEND_NETWORK_MSG,0, |
|
1684 WDF_STD_TOOLTIPS | WDF_DEF_WIDGET, |
|
1685 _chat_window_widgets, |
|
1686 ChatWindowWndProc |
|
1687 }; |
|
1688 |
|
1689 void ShowNetworkChatQueryWindow(DestType type, byte dest) |
|
1690 { |
|
1691 Window *w; |
|
1692 |
|
1693 DeleteWindowById(WC_SEND_NETWORK_MSG, 0); |
|
1694 |
|
1695 _edit_str_buf[0] = '\0'; |
|
1696 _chat_tab_completion_active = false; |
|
1697 |
|
1698 w = AllocateWindowDesc(&_chat_window_desc); |
|
1699 |
|
1700 LowerWindowWidget(w, 2); |
|
1701 WP(w, querystr_d).caption = GB(type, 0, 8) | (dest << 8); // Misuse of caption |
|
1702 WP(w, querystr_d).afilter = CS_ALPHANUMERAL; |
|
1703 InitializeTextBuffer(&WP(w, querystr_d).text, _edit_str_buf, lengthof(_edit_str_buf), 0); |
|
1704 } |
|
1705 |
|
1706 #endif /* ENABLE_NETWORK */ |