26 #define BGC 5 |
26 #define BGC 5 |
27 #define BTC 15 |
27 #define BTC 15 |
28 #define MAX_QUERYSTR_LEN 64 |
28 #define MAX_QUERYSTR_LEN 64 |
29 |
29 |
30 typedef struct network_d { |
30 typedef struct network_d { |
31 byte company; |
31 byte company; // select company in network lobby |
32 byte field; |
32 byte field; // select text-field in start-server and game-listing |
33 NetworkGameList *server; |
33 NetworkGameList *server; // selected server in lobby and game-listing |
34 FiosItem *map; |
34 FiosItem *map; // selected map in start-server |
35 } network_d; |
35 } network_d; |
36 assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(network_d)); |
36 assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(network_d)); |
37 |
37 |
38 typedef struct network_q_d { |
38 typedef struct network_ql_d { |
39 network_d n; |
39 network_d n; // see above; general stuff |
40 querystr_d q; |
40 querystr_d q; // text-input in start-server and game-listing |
41 } network_q_d; |
41 NetworkGameList **sort_list; // list of games (sorted) |
42 assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(network_q_d)); |
42 list_d l; // accompanying list-administration |
|
43 } network_ql_d; |
|
44 assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(network_ql_d)); |
|
45 |
|
46 typedef struct NetworkGameSorting { |
|
47 bool order; // Ascending / Descending |
|
48 byte criteria; // Sorted by name/clients/connectivity |
|
49 } NetworkGameSorting; |
|
50 |
|
51 /* Global to remember sorting after window has been closed */ |
|
52 static NetworkGameSorting _ng_sorting; |
43 |
53 |
44 static char _edit_str_buf[MAX_QUERYSTR_LEN*2]; |
54 static char _edit_str_buf[MAX_QUERYSTR_LEN*2]; |
45 static void ShowNetworkStartServerWindow(void); |
55 static void ShowNetworkStartServerWindow(void); |
46 static void ShowNetworkLobbyWindow(NetworkGameList *ngl); |
56 static void ShowNetworkLobbyWindow(NetworkGameList *ngl); |
47 |
57 |
90 void UpdateNetworkGameWindow(bool unselect) |
100 void UpdateNetworkGameWindow(bool unselect) |
91 { |
101 { |
92 Window* w = FindWindowById(WC_NETWORK_WINDOW, 0); |
102 Window* w = FindWindowById(WC_NETWORK_WINDOW, 0); |
93 |
103 |
94 if (w != NULL) { |
104 if (w != NULL) { |
95 if (unselect) WP(w, network_q_d).n.server = NULL; |
105 if (unselect) WP(w, network_ql_d).n.server = NULL; |
96 SendWindowMessage(WC_NETWORK_WINDOW, 0, true, 0, 0); |
106 SendWindowMessage(WC_NETWORK_WINDOW, 0, true, 0, 0); |
97 } |
107 } |
98 } |
108 } |
99 |
109 |
100 /* uses network_q_d (network_d and querystr_d) WP macro */ |
110 static bool _internal_sort_order; // Used for Qsort order-flipping |
|
111 typedef int CDECL NGameNameSortFunction(const void*, const void*); |
|
112 |
|
113 /** Qsort function to sort by name. */ |
|
114 static int CDECL NGameNameSorter(const void *a, const void *b) |
|
115 { |
|
116 const NetworkGameList *cmp1 = *(const NetworkGameList**)a; |
|
117 const NetworkGameList *cmp2 = *(const NetworkGameList**)b; |
|
118 int r = stricmp(cmp1->info.server_name, cmp2->info.server_name); |
|
119 |
|
120 return (_internal_sort_order & 1) ? -r : r; |
|
121 } |
|
122 |
|
123 /** Qsort function to sort by the amount of clients online on a |
|
124 * server. If the two servers have the same amount, the one with the |
|
125 * higher maximum is preferred. */ |
|
126 static int CDECL NGameClientSorter(const void *a, const void *b) |
|
127 { |
|
128 const NetworkGameList *cmp1 = *(const NetworkGameList**)a; |
|
129 const NetworkGameList *cmp2 = *(const NetworkGameList**)b; |
|
130 /* Reverse as per default we are interested in most-clients first */ |
|
131 int r = cmp2->info.clients_on - cmp1->info.clients_on; |
|
132 |
|
133 if (r == 0) r = cmp1->info.clients_max - cmp2->info.clients_max; |
|
134 |
|
135 return (_internal_sort_order & 1) ? -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 |
|
149 return (_internal_sort_order & 1) ? -r : r; |
|
150 } |
|
151 |
|
152 static NGameNameSortFunction* const _ngame_sorter[] = { |
|
153 &NGameNameSorter, |
|
154 &NGameClientSorter, |
|
155 &NGameAllowedSorter |
|
156 }; |
|
157 |
|
158 /** (Re)build the network game list as its amount has changed because |
|
159 * an item has been added or deleted for example |
|
160 * @param ngl list_d struct that contains all necessary information for sorting */ |
|
161 static void BuildNetworkGameList(network_ql_d *nqld) |
|
162 { |
|
163 NetworkGameList *ngl_temp; |
|
164 uint n = 0; |
|
165 |
|
166 if (!(nqld->l.flags & VL_REBUILD)) return; |
|
167 |
|
168 /* Count the number of games in the list */ |
|
169 for (ngl_temp = _network_game_list; ngl_temp != NULL; ngl_temp = ngl_temp->next) n++; |
|
170 if (n == 0) return; |
|
171 |
|
172 /* Create temporary array of games to use for listing */ |
|
173 free(nqld->sort_list); |
|
174 nqld->sort_list = malloc(n * sizeof(nqld->sort_list[0])); |
|
175 if (nqld->sort_list == NULL) error("Could not allocate memory for the network-game-sorting-list"); |
|
176 nqld->l.list_length = n; |
|
177 |
|
178 for (n = 0, ngl_temp = _network_game_list; ngl_temp != NULL; ngl_temp = ngl_temp->next) { |
|
179 nqld->sort_list[n++] = ngl_temp; |
|
180 } |
|
181 |
|
182 /* Force resort */ |
|
183 nqld->l.flags &= ~VL_REBUILD; |
|
184 nqld->l.flags |= VL_RESORT; |
|
185 } |
|
186 |
|
187 static void SortNetworkGameList(network_ql_d *nqld) |
|
188 { |
|
189 NetworkGameList *item; |
|
190 uint i; |
|
191 |
|
192 if (!(nqld->l.flags & VL_RESORT)) return; |
|
193 |
|
194 _internal_sort_order = nqld->l.flags & VL_DESC; |
|
195 qsort(nqld->sort_list, nqld->l.list_length, sizeof(nqld->sort_list[0]), _ngame_sorter[nqld->l.sort_type]); |
|
196 |
|
197 /* After sorting ngl->sort_list contains the sorted items. Put these back |
|
198 * into the original list. Basically nothing has changed, we are only |
|
199 * shuffling the ->next pointers */ |
|
200 _network_game_list = nqld->sort_list[0]; |
|
201 for (item = _network_game_list, i = 1; i != nqld->l.list_length; i++) { |
|
202 item->next = nqld->sort_list[i]; |
|
203 item = item->next; |
|
204 } |
|
205 item->next = NULL; |
|
206 |
|
207 nqld->l.flags &= ~VL_RESORT; |
|
208 } |
|
209 |
|
210 /* Uses network_ql_d (network_d, querystr_d and list_d) WP macro */ |
101 static void NetworkGameWindowWndProc(Window *w, WindowEvent *e) |
211 static void NetworkGameWindowWndProc(Window *w, WindowEvent *e) |
102 { |
212 { |
103 network_d *nd = &WP(w, network_q_d).n; |
213 network_d *nd = &WP(w, network_ql_d).n; |
|
214 list_d *ld = &WP(w, network_ql_d).l; |
104 |
215 |
105 switch (e->event) { |
216 switch (e->event) { |
106 case WE_CREATE: /* focus input box */ |
217 case WE_CREATE: /* Focus input box */ |
107 nd->field = 3; |
218 nd->field = 3; |
108 nd->server = NULL; |
219 nd->server = NULL; |
|
220 |
|
221 WP(w, network_ql_d).sort_list = NULL; |
|
222 ld->flags = VL_REBUILD | (_ng_sorting.order << (VL_DESC - 1)); |
|
223 ld->sort_type = _ng_sorting.criteria; |
109 break; |
224 break; |
110 |
225 |
111 case WE_PAINT: { |
226 case WE_PAINT: { |
112 const NetworkGameList *sel = nd->server; |
227 const NetworkGameList *sel = nd->server; |
|
228 const char *arrow = (ld->flags & VL_DESC) ? DOWNARROW : UPARROW; |
|
229 |
|
230 if (ld->flags & VL_REBUILD) { |
|
231 BuildNetworkGameList(&WP(w, network_ql_d)); |
|
232 SetVScrollCount(w, ld->list_length); |
|
233 } |
|
234 if (ld->flags & VL_RESORT) SortNetworkGameList(&WP(w, network_ql_d)); |
113 |
235 |
114 w->disabled_state = 0; |
236 w->disabled_state = 0; |
115 |
237 |
116 if (sel == NULL) { |
238 if (sel == NULL) { |
117 SETBIT(w->disabled_state, 16); SETBIT(w->disabled_state, 17); |
239 SETBIT(w->disabled_state, 16); SETBIT(w->disabled_state, 17); |
126 |
248 |
127 SetDParam(0, 0x00); |
249 SetDParam(0, 0x00); |
128 SetDParam(7, _lan_internet_types_dropdown[_network_lan_internet]); |
250 SetDParam(7, _lan_internet_types_dropdown[_network_lan_internet]); |
129 DrawWindowWidgets(w); |
251 DrawWindowWidgets(w); |
130 |
252 |
131 DrawEditBox(w, &WP(w, network_q_d).q, 3); |
253 DrawEditBox(w, &WP(w, network_ql_d).q, 3); |
132 |
254 |
133 DrawString(9, 23, STR_NETWORK_CONNECTION, 2); |
255 DrawString(9, 23, STR_NETWORK_CONNECTION, 2); |
134 DrawString(210, 23, STR_NETWORK_PLAYER_NAME, 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 } |
135 |
264 |
136 { // draw list of games |
265 { // draw list of games |
137 uint16 y = NET_PRC__OFFSET_TOP_WIDGET + 3; |
266 uint16 y = NET_PRC__OFFSET_TOP_WIDGET + 3; |
138 int32 n = 0; |
267 int32 n = 0; |
139 int32 pos = w->vscroll.pos; |
268 int32 pos = w->vscroll.pos; |
256 case 0: case 14: /* Close 'X' | Cancel button */ |
385 case 0: case 14: /* Close 'X' | Cancel button */ |
257 DeleteWindowById(WC_NETWORK_WINDOW, 0); |
386 DeleteWindowById(WC_NETWORK_WINDOW, 0); |
258 break; |
387 break; |
259 case 4: case 5: |
388 case 4: case 5: |
260 ShowDropDownMenu(w, _lan_internet_types_dropdown, _network_lan_internet, 5, 0, 0); // do it for widget 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->click.widget - 6) ld->flags ^= VL_DESC; |
|
395 ld->flags |= VL_RESORT; |
|
396 ld->sort_type = e->click.widget - 6; |
|
397 |
|
398 _ng_sorting.order = !!(ld->flags & VL_DESC); |
|
399 _ng_sorting.criteria = ld->sort_type; |
|
400 SetWindowDirty(w); |
261 break; |
401 break; |
262 case 9: { /* Matrix to show networkgames */ |
402 case 9: { /* Matrix to show networkgames */ |
263 NetworkGameList *cur_item; |
403 NetworkGameList *cur_item; |
264 uint32 id_v = (e->click.pt.y - NET_PRC__OFFSET_TOP_WIDGET) / NET_PRC__SIZE_OF_ROW; |
404 uint32 id_v = (e->click.pt.y - NET_PRC__OFFSET_TOP_WIDGET) / NET_PRC__SIZE_OF_ROW; |
265 |
405 |
460 SetDParam(13, _players_dropdown[_network_game_info.spectators_max]); |
598 SetDParam(13, _players_dropdown[_network_game_info.spectators_max]); |
461 SetDParam(15, _language_dropdown[_network_game_info.server_lang]); |
599 SetDParam(15, _language_dropdown[_network_game_info.server_lang]); |
462 DrawWindowWidgets(w); |
600 DrawWindowWidgets(w); |
463 |
601 |
464 GfxFillRect(11, 63, 258, 215, 0xD7); |
602 GfxFillRect(11, 63, 258, 215, 0xD7); |
465 DrawEditBox(w, &WP(w, network_q_d).q, 3); |
603 DrawEditBox(w, &WP(w, network_ql_d).q, 3); |
466 |
604 |
467 DrawString(10, 22, STR_NETWORK_NEW_GAME_NAME, 2); |
605 DrawString(10, 22, STR_NETWORK_NEW_GAME_NAME, 2); |
468 |
606 |
469 DrawString(10, 43, STR_NETWORK_SELECT_MAP, 2); |
607 DrawString(10, 43, STR_NETWORK_SELECT_MAP, 2); |
470 |
608 |
567 |
705 |
568 SetWindowDirty(w); |
706 SetWindowDirty(w); |
569 break; |
707 break; |
570 |
708 |
571 case WE_MOUSELOOP: |
709 case WE_MOUSELOOP: |
572 if (nd->field == 3) HandleEditBox(w, &WP(w, network_q_d).q, 3); |
710 if (nd->field == 3) HandleEditBox(w, &WP(w, network_ql_d).q, 3); |
573 break; |
711 break; |
574 |
712 |
575 case WE_KEYPRESS: |
713 case WE_KEYPRESS: |
576 if (nd->field == 3) { |
714 if (nd->field == 3) { |
577 if (HandleEditBoxKey(w, &WP(w, network_q_d).q, 3, e) == 1) break; // enter pressed |
715 if (HandleEditBoxKey(w, &WP(w, network_ql_d).q, 3, e) == 1) break; // enter pressed |
578 |
716 |
579 ttd_strlcpy(_network_server_name, WP(w, network_q_d).q.text.buf, sizeof(_network_server_name)); |
717 ttd_strlcpy(_network_server_name, WP(w, network_ql_d).q.text.buf, sizeof(_network_server_name)); |
580 UpdateTextBufferSize(&WP(w, network_q_d).q.text); |
718 UpdateTextBufferSize(&WP(w, network_ql_d).q.text); |
581 } |
719 } |
582 break; |
720 break; |
583 |
721 |
584 case WE_ON_EDIT_TEXT: { |
722 case WE_ON_EDIT_TEXT: { |
585 ttd_strlcpy(_network_server_password, e->edittext.str, lengthof(_network_server_password)); |
723 ttd_strlcpy(_network_server_password, e->edittext.str, lengthof(_network_server_password)); |
837 WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, |
975 WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, |
838 _network_lobby_window_widgets, |
976 _network_lobby_window_widgets, |
839 NetworkLobbyWindowWndProc, |
977 NetworkLobbyWindowWndProc, |
840 }; |
978 }; |
841 |
979 |
842 |
980 /* Show the networklobbywindow with the selected server |
|
981 * @param ngl Selected game pointer which is passed to the new window */ |
843 static void ShowNetworkLobbyWindow(NetworkGameList *ngl) |
982 static void ShowNetworkLobbyWindow(NetworkGameList *ngl) |
844 { |
983 { |
845 Window *w; |
984 Window *w; |
846 DeleteWindowById(WC_NETWORK_WINDOW, 0); |
985 DeleteWindowById(WC_NETWORK_WINDOW, 0); |
847 |
986 |
848 NetworkQueryServer(_network_last_host, _network_last_port, false); |
987 NetworkQueryServer(_network_last_host, _network_last_port, false); |
849 |
988 |
850 w = AllocateWindowDesc(&_network_lobby_window_desc); |
989 w = AllocateWindowDesc(&_network_lobby_window_desc); |
851 if (w != NULL) { |
990 if (w != NULL) { |
852 WP(w, network_q_d).n.server = ngl; |
991 WP(w, network_ql_d).n.server = ngl; |
853 strcpy(_edit_str_buf, ""); |
992 strcpy(_edit_str_buf, ""); |
854 w->vscroll.cap = 10; |
993 w->vscroll.cap = 10; |
855 } |
994 } |
856 } |
995 } |
857 |
996 |