network_gui.cpp

00001 /* $Id: network_gui.cpp 12370 2008-03-15 19:50:06Z peter1138 $ */
00002 
00003 #ifdef ENABLE_NETWORK
00004 #include "../stdafx.h"
00005 #include "../openttd.h"
00006 #include "../strings_func.h"
00007 #include "network.h"
00008 #include "../date_func.h"
00009 #include "../fios.h"
00010 #include "network_data.h"
00011 #include "network_client.h"
00012 #include "network_gui.h"
00013 #include "network_gamelist.h"
00014 #include "../gui.h"
00015 #include "../window_gui.h"
00016 #include "../textbuf_gui.h"
00017 #include "../variables.h"
00018 #include "network_server.h"
00019 #include "network_udp.h"
00020 #include "../town.h"
00021 #include "../newgrf.h"
00022 #include "../functions.h"
00023 #include "../window_func.h"
00024 #include "../core/alloc_func.hpp"
00025 #include "../string_func.h"
00026 #include "../gfx_func.h"
00027 #include "../player_func.h"
00028 #include "../settings_type.h"
00029 #include "../widgets/dropdown_func.h"
00030 
00031 #include "table/strings.h"
00032 #include "../table/sprites.h"
00033 
00034 #define BGC 5
00035 #define BTC 15
00036 
00037 struct chatquerystr_d : public querystr_d {
00038   int dest;
00039 };
00040 assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(chatquerystr_d));
00041 
00042 struct network_d {
00043   PlayerID company;        // select company in network lobby
00044   byte field;              // select text-field in start-server and game-listing
00045   byte widget_id;          
00046   NetworkGameList *server; // selected server in lobby and game-listing
00047   FiosItem *map;           // selected map in start-server
00048 };
00049 assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(network_d));
00050 
00051 struct network_ql_d {
00052   network_d n;                 // see above; general stuff
00053   querystr_d q;                // text-input in start-server and game-listing
00054   NetworkGameList **sort_list; // list of games (sorted)
00055   list_d l;                    // accompanying list-administration
00056 };
00057 assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(network_ql_d));
00058 
00059 /* Global to remember sorting after window has been closed */
00060 static Listing _ng_sorting;
00061 
00062 static char _edit_str_buf[150];
00063 static bool _chat_tab_completion_active;
00064 
00065 static void ShowNetworkStartServerWindow();
00066 static void ShowNetworkLobbyWindow(NetworkGameList *ngl);
00067 extern void SwitchMode(int new_mode);
00068 
00069 static const StringID _connection_types_dropdown[] = {
00070   STR_NETWORK_LAN_INTERNET,
00071   STR_NETWORK_INTERNET_ADVERTISE,
00072   INVALID_STRING_ID
00073 };
00074 
00075 static const StringID _lan_internet_types_dropdown[] = {
00076   STR_NETWORK_LAN,
00077   STR_NETWORK_INTERNET,
00078   INVALID_STRING_ID
00079 };
00080 
00081 static StringID _language_dropdown[NETLANG_COUNT + 1] = {STR_NULL};
00082 
00083 void SortNetworkLanguages()
00084 {
00085   /* Init the strings */
00086   if (_language_dropdown[0] == STR_NULL) {
00087     for (int i = 0; i < NETLANG_COUNT; i++) _language_dropdown[i] = STR_NETWORK_LANG_ANY + i;
00088     _language_dropdown[NETLANG_COUNT] = INVALID_STRING_ID;
00089   }
00090 
00091   /* Sort the strings (we don't move 'any' and the 'invalid' one) */
00092   qsort(&_language_dropdown[1], NETLANG_COUNT - 1, sizeof(StringID), &StringIDSorter);
00093 }
00094 
00095 enum {
00096   NET_PRC__OFFSET_TOP_WIDGET          = 54,
00097   NET_PRC__OFFSET_TOP_WIDGET_COMPANY  = 52,
00098   NET_PRC__SIZE_OF_ROW                = 14,
00099 };
00100 
00104 void UpdateNetworkGameWindow(bool unselect)
00105 {
00106   SendWindowMessage(WC_NETWORK_WINDOW, 0, unselect, 0, 0);
00107 }
00108 
00109 static bool _internal_sort_order; // Used for Qsort order-flipping
00110 typedef int CDECL NGameNameSortFunction(const void*, const void*);
00111 
00113 static int CDECL NGameNameSorter(const void *a, const void *b)
00114 {
00115   const NetworkGameList *cmp1 = *(const NetworkGameList**)a;
00116   const NetworkGameList *cmp2 = *(const NetworkGameList**)b;
00117   int r = strcasecmp(cmp1->info.server_name, cmp2->info.server_name);
00118 
00119   return _internal_sort_order ? -r : r;
00120 }
00121 
00125 static int CDECL NGameClientSorter(const void *a, const void *b)
00126 {
00127   const NetworkGameList *cmp1 = *(const NetworkGameList**)a;
00128   const NetworkGameList *cmp2 = *(const NetworkGameList**)b;
00129   /* Reverse as per default we are interested in most-clients first */
00130   int r = cmp1->info.clients_on - cmp2->info.clients_on;
00131 
00132   if (r == 0) r = cmp1->info.clients_max - cmp2->info.clients_max;
00133   if (r == 0) r = strcasecmp(cmp1->info.server_name, cmp2->info.server_name);
00134 
00135   return _internal_sort_order ? -r : r;
00136 }
00137 
00140 static int CDECL NGameAllowedSorter(const void *a, const void *b)
00141 {
00142   const NetworkGameList *cmp1 = *(const NetworkGameList**)a;
00143   const NetworkGameList *cmp2 = *(const NetworkGameList**)b;
00144 
00145   /* The servers we do not know anything about (the ones that did not reply) should be at the bottom) */
00146   int r = StrEmpty(cmp1->info.server_revision) - StrEmpty(cmp2->info.server_revision);
00147 
00148   /* Reverse default as we are interested in version-compatible clients first */
00149   if (r == 0) r = cmp2->info.version_compatible - cmp1->info.version_compatible;
00150   /* The version-compatible ones are then sorted with NewGRF compatible first, incompatible last */
00151   if (r == 0) r = cmp2->info.compatible - cmp1->info.compatible;
00152   /* Passworded servers should be below unpassworded servers */
00153   if (r == 0) r = cmp1->info.use_password - cmp2->info.use_password;
00154   /* Finally sort on the name of the server */
00155   if (r == 0) r = strcasecmp(cmp1->info.server_name, cmp2->info.server_name);
00156 
00157   return _internal_sort_order ? -r : r;
00158 }
00159 
00163 static void BuildNetworkGameList(network_ql_d *nqld)
00164 {
00165   NetworkGameList *ngl_temp;
00166   uint n = 0;
00167 
00168   if (!(nqld->l.flags & VL_REBUILD)) return;
00169 
00170   /* Count the number of games in the list */
00171   for (ngl_temp = _network_game_list; ngl_temp != NULL; ngl_temp = ngl_temp->next) n++;
00172   if (n == 0) return;
00173 
00174   /* Create temporary array of games to use for listing */
00175   free(nqld->sort_list);
00176   nqld->sort_list = MallocT<NetworkGameList*>(n);
00177   nqld->l.list_length = n;
00178 
00179   for (n = 0, ngl_temp = _network_game_list; ngl_temp != NULL; ngl_temp = ngl_temp->next) {
00180     nqld->sort_list[n++] = ngl_temp;
00181   }
00182 
00183   /* Force resort */
00184   nqld->l.flags &= ~VL_REBUILD;
00185   nqld->l.flags |= VL_RESORT;
00186 }
00187 
00188 static void SortNetworkGameList(network_ql_d *nqld)
00189 {
00190   static NGameNameSortFunction * const ngame_sorter[] = {
00191     &NGameNameSorter,
00192     &NGameClientSorter,
00193     &NGameAllowedSorter
00194   };
00195 
00196   NetworkGameList *item;
00197   uint i;
00198 
00199   if (!(nqld->l.flags & VL_RESORT)) return;
00200   if (nqld->l.list_length == 0) return;
00201 
00202   _internal_sort_order = !!(nqld->l.flags & VL_DESC);
00203   qsort(nqld->sort_list, nqld->l.list_length, sizeof(nqld->sort_list[0]), ngame_sorter[nqld->l.sort_type]);
00204 
00205   /* After sorting ngl->sort_list contains the sorted items. Put these back
00206    * into the original list. Basically nothing has changed, we are only
00207    * shuffling the ->next pointers */
00208   _network_game_list = nqld->sort_list[0];
00209   for (item = _network_game_list, i = 1; i != nqld->l.list_length; i++) {
00210     item->next = nqld->sort_list[i];
00211     item = item->next;
00212   }
00213   item->next = NULL;
00214 
00215   nqld->l.flags &= ~VL_RESORT;
00216 }
00217 
00219 enum NetworkGameWindowWidgets {
00220   NGWW_CLOSE    = 0,  
00221   NGWW_CONN_BTN = 4,  
00222   NGWW_PLAYER   = 5,  
00223 
00224   NGWW_NAME     = 6,  
00225   NGWW_CLIENTS,       
00226   NGWW_INFO,          
00227 
00228   NGWW_MATRIX   = 9,  
00229 
00230   NGWW_DETAILS  = 11, 
00231   NGWW_JOIN     = 12, 
00232   NGWW_REFRESH  = 13, 
00233   NGWW_NEWGRF   = 14, 
00234 
00235   NGWW_FIND     = 15, 
00236   NGWW_ADD,           
00237   NGWW_START,         
00238   NGWW_CANCEL,        
00239 };
00240 
00251 static void NetworkGameWindowWndProc(Window *w, WindowEvent *e)
00252 {
00253   network_d *nd = &WP(w, network_ql_d).n;
00254   list_d *ld = &WP(w, network_ql_d).l;
00255 
00256   switch (e->event) {
00257   case WE_CREATE: // Focus input box
00258     w->vscroll.cap = 13;
00259     w->resize.step_height = NET_PRC__SIZE_OF_ROW;
00260 
00261     nd->field = NGWW_PLAYER;
00262     nd->server = NULL;
00263 
00264     WP(w, network_ql_d).sort_list = NULL;
00265     ld->flags = VL_REBUILD | (_ng_sorting.order ? VL_DESC : VL_NONE);
00266     ld->sort_type = _ng_sorting.criteria;
00267     break;
00268 
00269   case WE_PAINT: {
00270     const NetworkGameList *sel = nd->server;
00271     const SortButtonState arrow = (ld->flags & VL_DESC) ? SBS_DOWN : SBS_UP;
00272 
00273     if (ld->flags & VL_REBUILD) {
00274       BuildNetworkGameList(&WP(w, network_ql_d));
00275       SetVScrollCount(w, ld->list_length);
00276     }
00277     if (ld->flags & VL_RESORT) SortNetworkGameList(&WP(w, network_ql_d));
00278 
00279     /* 'Refresh' button invisible if no server selected */
00280     w->SetWidgetDisabledState(NGWW_REFRESH, sel == NULL);
00281     /* 'Join' button disabling conditions */
00282     w->SetWidgetDisabledState(NGWW_JOIN, sel == NULL || // no Selected Server
00283         !sel->online || // Server offline
00284         sel->info.clients_on >= sel->info.clients_max || // Server full
00285         !sel->info.compatible); // Revision mismatch
00286 
00287     /* 'NewGRF Settings' button invisible if no NewGRF is used */
00288     w->SetWidgetHiddenState(NGWW_NEWGRF, sel == NULL ||
00289         !sel->online ||
00290         sel->info.grfconfig == NULL);
00291 
00292     SetDParam(0, 0x00);
00293     SetDParam(1, _lan_internet_types_dropdown[_network_lan_internet]);
00294     DrawWindowWidgets(w);
00295 
00296     /* Edit box to set player name */
00297     DrawEditBox(w, &WP(w, network_ql_d).q, NGWW_PLAYER);
00298 
00299     DrawString(w->widget[NGWW_PLAYER].left - 100, 23, STR_NETWORK_PLAYER_NAME, TC_GOLD);
00300 
00301     /* Sort based on widgets: name, clients, compatibility */
00302     switch (ld->sort_type) {
00303       case NGWW_NAME    - NGWW_NAME: DrawSortButtonState(w, NGWW_NAME,    arrow); break;
00304       case NGWW_CLIENTS - NGWW_NAME: DrawSortButtonState(w, NGWW_CLIENTS, arrow); break;
00305       case NGWW_INFO    - NGWW_NAME: DrawSortButtonState(w, NGWW_INFO,    arrow); break;
00306     }
00307 
00308     { // draw list of games
00309       uint16 y = NET_PRC__OFFSET_TOP_WIDGET + 3;
00310       int32 n = 0;
00311       int32 pos = w->vscroll.pos;
00312       uint max_name_width = w->widget[NGWW_NAME].right - w->widget[NGWW_NAME].left - 5;
00313       const NetworkGameList *cur_item = _network_game_list;
00314 
00315       while (pos > 0 && cur_item != NULL) {
00316         pos--;
00317         cur_item = cur_item->next;
00318       }
00319 
00320       while (cur_item != NULL) {
00321         /* show highlighted item with a different colour */
00322         if (cur_item == sel) GfxFillRect(w->widget[NGWW_NAME].left + 1, y - 2, w->widget[NGWW_INFO].right - 1, y + 9, 10);
00323 
00324         SetDParamStr(0, cur_item->info.server_name);
00325         DrawStringTruncated(w->widget[NGWW_NAME].left + 5, y, STR_02BD, TC_BLACK, max_name_width);
00326 
00327         SetDParam(0, cur_item->info.clients_on);
00328         SetDParam(1, cur_item->info.clients_max);
00329         SetDParam(2, cur_item->info.companies_on);
00330         SetDParam(3, cur_item->info.companies_max);
00331         DrawStringCentered(w->widget[NGWW_CLIENTS].left + 39, y, STR_NETWORK_GENERAL_ONLINE, TC_GOLD);
00332 
00333         /* only draw icons if the server is online */
00334         if (cur_item->online) {
00335           /* draw a lock if the server is password protected */
00336           if (cur_item->info.use_password) DrawSprite(SPR_LOCK, PAL_NONE, w->widget[NGWW_INFO].left + 5, y - 1);
00337 
00338           /* draw red or green icon, depending on compatibility with server */
00339           DrawSprite(SPR_BLOT, (cur_item->info.compatible ? PALETTE_TO_GREEN : (cur_item->info.version_compatible ? PALETTE_TO_YELLOW : PALETTE_TO_RED)), w->widget[NGWW_INFO].left + 15, y);
00340 
00341           /* draw flag according to server language */
00342           DrawSprite(SPR_FLAGS_BASE + cur_item->info.server_lang, PAL_NONE, w->widget[NGWW_INFO].left + 25, y);
00343         }
00344 
00345         cur_item = cur_item->next;
00346         y += NET_PRC__SIZE_OF_ROW;
00347         if (++n == w->vscroll.cap) break; // max number of games in the window
00348       }
00349     }
00350 
00351     /* Draw the right menu */
00352     GfxFillRect(w->widget[NGWW_DETAILS].left + 1, 43, w->widget[NGWW_DETAILS].right - 1, 92, 157);
00353     if (sel == NULL) {
00354       DrawStringCentered(w->widget[NGWW_DETAILS].left + 115, 58, STR_NETWORK_GAME_INFO, TC_FROMSTRING);
00355     } else if (!sel->online) {
00356       SetDParamStr(0, sel->info.server_name);
00357       DrawStringCentered(w->widget[NGWW_DETAILS].left + 115, 68, STR_ORANGE, TC_FROMSTRING); // game name
00358 
00359       DrawStringCentered(w->widget[NGWW_DETAILS].left + 115, 132, STR_NETWORK_SERVER_OFFLINE, TC_FROMSTRING); // server offline
00360     } else { // show game info
00361       uint16 y = 100;
00362       const uint16 x = w->widget[NGWW_DETAILS].left + 5;
00363 
00364       DrawStringCentered(w->widget[NGWW_DETAILS].left + 115, 48, STR_NETWORK_GAME_INFO, TC_FROMSTRING);
00365 
00366 
00367       SetDParamStr(0, sel->info.server_name);
00368       DrawStringCenteredTruncated(w->widget[NGWW_DETAILS].left, w->widget[NGWW_DETAILS].right, 62, STR_ORANGE, TC_BLACK); // game name
00369 
00370       SetDParamStr(0, sel->info.map_name);
00371       DrawStringCenteredTruncated(w->widget[NGWW_DETAILS].left, w->widget[NGWW_DETAILS].right, 74, STR_02BD, TC_BLACK); // map name
00372 
00373       SetDParam(0, sel->info.clients_on);
00374       SetDParam(1, sel->info.clients_max);
00375       SetDParam(2, sel->info.companies_on);
00376       SetDParam(3, sel->info.companies_max);
00377       DrawString(x, y, STR_NETWORK_CLIENTS, TC_GOLD);
00378       y += 10;
00379 
00380       SetDParam(0, STR_NETWORK_LANG_ANY + sel->info.server_lang);
00381       DrawString(x, y, STR_NETWORK_LANGUAGE, TC_GOLD); // server language
00382       y += 10;
00383 
00384       SetDParam(0, STR_TEMPERATE_LANDSCAPE + sel->info.map_set);
00385       DrawString(x, y, STR_NETWORK_TILESET, TC_GOLD); // tileset
00386       y += 10;
00387 
00388       SetDParam(0, sel->info.map_width);
00389       SetDParam(1, sel->info.map_height);
00390       DrawString(x, y, STR_NETWORK_MAP_SIZE, TC_GOLD); // map size
00391       y += 10;
00392 
00393       SetDParamStr(0, sel->info.server_revision);
00394       DrawString(x, y, STR_NETWORK_SERVER_VERSION, TC_GOLD); // server version
00395       y += 10;
00396 
00397       SetDParamStr(0, sel->info.hostname);
00398       SetDParam(1, sel->port);
00399       DrawString(x, y, STR_NETWORK_SERVER_ADDRESS, TC_GOLD); // server address
00400       y += 10;
00401 
00402       SetDParam(0, sel->info.start_date);
00403       DrawString(x, y, STR_NETWORK_START_DATE, TC_GOLD); // start date
00404       y += 10;
00405 
00406       SetDParam(0, sel->info.game_date);
00407       DrawString(x, y, STR_NETWORK_CURRENT_DATE, TC_GOLD); // current date
00408       y += 10;
00409 
00410       y += 2;
00411 
00412       if (!sel->info.compatible) {
00413         DrawStringCentered(w->widget[NGWW_DETAILS].left + 115, y, sel->info.version_compatible ? STR_NETWORK_GRF_MISMATCH : STR_NETWORK_VERSION_MISMATCH, TC_FROMSTRING); // server mismatch
00414       } else if (sel->info.clients_on == sel->info.clients_max) {
00415         /* Show: server full, when clients_on == clients_max */
00416         DrawStringCentered(w->widget[NGWW_DETAILS].left + 115, y, STR_NETWORK_SERVER_FULL, TC_FROMSTRING); // server full
00417       } else if (sel->info.use_password) {
00418         DrawStringCentered(w->widget[NGWW_DETAILS].left + 115, y, STR_NETWORK_PASSWORD, TC_FROMSTRING); // password warning
00419       }
00420 
00421       y += 10;
00422     }
00423   } break;
00424 
00425   case WE_CLICK:
00426     nd->field = e->we.click.widget;
00427     switch (e->we.click.widget) {
00428     case NGWW_CANCEL: // Cancel button
00429       DeleteWindowById(WC_NETWORK_WINDOW, 0);
00430       break;
00431     case NGWW_CONN_BTN: // 'Connection' droplist
00432       ShowDropDownMenu(w, _lan_internet_types_dropdown, _network_lan_internet, NGWW_CONN_BTN, 0, 0); // do it for widget NSSW_CONN_BTN
00433       break;
00434     case NGWW_NAME: // Sort by name
00435     case NGWW_CLIENTS: // Sort by connected clients
00436     case NGWW_INFO: // Connectivity (green dot)
00437       if (ld->sort_type == e->we.click.widget - NGWW_NAME) ld->flags ^= VL_DESC;
00438       ld->flags |= VL_RESORT;
00439       ld->sort_type = e->we.click.widget - NGWW_NAME;
00440 
00441       _ng_sorting.order = !!(ld->flags & VL_DESC);
00442       _ng_sorting.criteria = ld->sort_type;
00443       SetWindowDirty(w);
00444       break;
00445     case NGWW_MATRIX: { // Matrix to show networkgames
00446       NetworkGameList *cur_item;
00447       uint32 id_v = (e->we.click.pt.y - NET_PRC__OFFSET_TOP_WIDGET) / NET_PRC__SIZE_OF_ROW;
00448 
00449       if (id_v >= w->vscroll.cap) return; // click out of bounds
00450       id_v += w->vscroll.pos;
00451 
00452       cur_item = _network_game_list;
00453       for (; id_v > 0 && cur_item != NULL; id_v--) cur_item = cur_item->next;
00454 
00455       nd->server = cur_item;
00456       SetWindowDirty(w);
00457     } break;
00458     case NGWW_FIND: // Find server automatically
00459       switch (_network_lan_internet) {
00460         case 0: NetworkUDPSearchGame(); break;
00461         case 1: NetworkUDPQueryMasterServer(); break;
00462       }
00463       break;
00464     case NGWW_ADD: { // Add a server
00465       ShowQueryString(
00466         BindCString(_network_default_ip),
00467         STR_NETWORK_ENTER_IP,
00468         31 | 0x1000,  // maximum number of characters OR
00469         250, // characters up to this width pixels, whichever is satisfied first
00470         w, CS_ALPHANUMERAL);
00471     } break;
00472     case NGWW_START: // Start server
00473       ShowNetworkStartServerWindow();
00474       break;
00475     case NGWW_JOIN: // Join Game
00476       if (nd->server != NULL) {
00477         snprintf(_network_last_host, sizeof(_network_last_host), "%s", inet_ntoa(*(struct in_addr *)&nd->server->ip));
00478         _network_last_port = nd->server->port;
00479         ShowNetworkLobbyWindow(nd->server);
00480       }
00481       break;
00482     case NGWW_REFRESH: // Refresh
00483       if (nd->server != NULL)
00484         NetworkUDPQueryServer(nd->server->info.hostname, nd->server->port);
00485       break;
00486     case NGWW_NEWGRF: // NewGRF Settings
00487       if (nd->server != NULL) ShowNewGRFSettings(false, false, false, &nd->server->info.grfconfig);
00488       break;
00489 
00490   } break;
00491 
00492   case WE_DROPDOWN_SELECT: // we have selected a dropdown item in the list
00493     switch (e->we.dropdown.button) {
00494       case NGWW_CONN_BTN:
00495         _network_lan_internet = e->we.dropdown.index;
00496         break;
00497       default:
00498         NOT_REACHED();
00499     }
00500 
00501     SetWindowDirty(w);
00502     break;
00503 
00504   case WE_MOUSELOOP:
00505     if (nd->field == NGWW_PLAYER) HandleEditBox(w, &WP(w, network_ql_d).q, NGWW_PLAYER);
00506     break;
00507 
00508   case WE_MESSAGE:
00509     if (e->we.message.msg != 0) nd->server = NULL;
00510     ld->flags |= VL_REBUILD;
00511     SetWindowDirty(w);
00512     break;
00513 
00514   case WE_KEYPRESS:
00515     if (nd->field != NGWW_PLAYER) {
00516       if (nd->server != NULL) {
00517         if (e->we.keypress.keycode == WKC_DELETE) { // Press 'delete' to remove servers
00518           NetworkGameListRemoveItem(nd->server);
00519           NetworkRebuildHostList();
00520           nd->server = NULL;
00521         }
00522       }
00523       break;
00524     }
00525 
00526     if (HandleEditBoxKey(w, &WP(w, network_ql_d).q, NGWW_PLAYER, e) == 1) break; // enter pressed
00527 
00528     /* The name is only allowed when it starts with a letter! */
00529     if (_edit_str_buf[0] != '\0' && _edit_str_buf[0] != ' ') {
00530       ttd_strlcpy(_network_player_name, _edit_str_buf, lengthof(_network_player_name));
00531     } else {
00532       ttd_strlcpy(_network_player_name, "Player", lengthof(_network_player_name));
00533     }
00534 
00535     break;
00536 
00537   case WE_ON_EDIT_TEXT:
00538     NetworkAddServer(e->we.edittext.str);
00539     NetworkRebuildHostList();
00540     break;
00541 
00542   case WE_RESIZE: {
00543     w->vscroll.cap += e->we.sizing.diff.y / (int)w->resize.step_height;
00544 
00545     w->widget[NGWW_MATRIX].data = (w->vscroll.cap << 8) + 1;
00546 
00547     SetVScrollCount(w, ld->list_length);
00548 
00549     int widget_width = w->widget[NGWW_FIND].right - w->widget[NGWW_FIND].left;
00550     int space = (w->width - 4 * widget_width - 25) / 3;
00551 
00552     int offset = 10;
00553     for (uint i = 0; i < 4; i++) {
00554       w->widget[NGWW_FIND + i].left  = offset;
00555       offset += widget_width;
00556       w->widget[NGWW_FIND + i].right = offset;
00557       offset += space;
00558     }
00559   } break;
00560 
00561   case WE_DESTROY: // Nicely clean up the sort-list
00562     free(WP(w, network_ql_d).sort_list);
00563     break;
00564   }
00565 }
00566 
00567 static const Widget _network_game_window_widgets[] = {
00568 /* TOP */
00569 {   WWT_CLOSEBOX,   RESIZE_NONE,   BGC,     0,    10,     0,    13, STR_00C5,                       STR_018B_CLOSE_WINDOW},            // NGWW_CLOSE
00570 {    WWT_CAPTION,   RESIZE_RIGHT,  BGC,    11,   449,     0,    13, STR_NETWORK_MULTIPLAYER,        STR_NULL},
00571 {      WWT_PANEL,   RESIZE_RB,     BGC,     0,   449,    14,   263, 0x0,                            STR_NULL},
00572 
00573 {       WWT_TEXT,   RESIZE_NONE,   BGC,     9,    85,    23,    35, STR_NETWORK_CONNECTION,         STR_NULL},
00574 { WWT_DROPDOWNIN,   RESIZE_NONE,   BGC,    90,   181,    22,    33, STR_NETWORK_LAN_INTERNET_COMBO, STR_NETWORK_CONNECTION_TIP},       // NGWW_CONN_BTN
00575 
00576 {      WWT_PANEL,   RESIZE_LR,     BGC,   290,   440,    22,    33, 0x0,                            STR_NETWORK_ENTER_NAME_TIP},       // NGWW_PLAYER
00577 
00578 /* LEFT SIDE */
00579 { WWT_PUSHTXTBTN,   RESIZE_RIGHT,  BTC,    10,    70,    42,    53, STR_NETWORK_GAME_NAME,          STR_NETWORK_GAME_NAME_TIP},        // NGWW_NAME
00580 { WWT_PUSHTXTBTN,   RESIZE_LR,     BTC,    71,   150,    42,    53, STR_NETWORK_CLIENTS_CAPTION,    STR_NETWORK_CLIENTS_CAPTION_TIP},  // NGWW_CLIENTS
00581 { WWT_PUSHTXTBTN,   RESIZE_LR,     BTC,   151,   190,    42,    53, STR_EMPTY,                      STR_NETWORK_INFO_ICONS_TIP},       // NGWW_INFO
00582 
00583 {     WWT_MATRIX,   RESIZE_RB,     BGC,    10,   190,    54,   236, (13 << 8) + 1,                  STR_NETWORK_CLICK_GAME_TO_SELECT}, // NGWW_MATRIX
00584 {  WWT_SCROLLBAR,   RESIZE_LRB,    BGC,   191,   202,    42,   236, 0x0,                            STR_0190_SCROLL_BAR_SCROLLS_LIST},
00585 
00586 /* RIGHT SIDE */
00587 {      WWT_PANEL,   RESIZE_LRB,    BGC,   210,   440,    42,   236, 0x0,                            STR_NULL},                         // NGWW_DETAILS
00588 
00589 { WWT_PUSHTXTBTN,   RESIZE_LRTB,   BTC,   215,   315,   215,   226, STR_NETWORK_JOIN_GAME,          STR_NULL},                         // NGWW_JOIN
00590 { WWT_PUSHTXTBTN,   RESIZE_LRTB,   BTC,   330,   435,   215,   226, STR_NETWORK_REFRESH,            STR_NETWORK_REFRESH_TIP},          // NGWW_REFRESH
00591 
00592 { WWT_PUSHTXTBTN,   RESIZE_LRTB,   BTC,   330,   435,   197,   208, STR_NEWGRF_SETTINGS_BUTTON,     STR_NULL},                         // NGWW_NEWGRF
00593 
00594 /* BOTTOM */
00595 { WWT_PUSHTXTBTN,   RESIZE_TB,     BTC,    10,   110,   246,   257, STR_NETWORK_FIND_SERVER,        STR_NETWORK_FIND_SERVER_TIP},      // NGWW_FIND
00596 { WWT_PUSHTXTBTN,   RESIZE_TB,     BTC,   118,   218,   246,   257, STR_NETWORK_ADD_SERVER,         STR_NETWORK_ADD_SERVER_TIP},       // NGWW_ADD
00597 { WWT_PUSHTXTBTN,   RESIZE_TB,     BTC,   226,   326,   246,   257, STR_NETWORK_START_SERVER,       STR_NETWORK_START_SERVER_TIP},     // NGWW_START
00598 { WWT_PUSHTXTBTN,   RESIZE_TB,     BTC,   334,   434,   246,   257, STR_012E_CANCEL,                STR_NULL},                         // NGWW_CANCEL
00599 
00600 {  WWT_RESIZEBOX,   RESIZE_LRTB,   BGC,   438,   449,   252,   263, 0x0,                            STR_RESIZE_BUTTON },
00601 
00602 {   WIDGETS_END},
00603 };
00604 
00605 static const WindowDesc _network_game_window_desc = {
00606   WDP_CENTER, WDP_CENTER, 450, 264, 550, 264,
00607   WC_NETWORK_WINDOW, WC_NONE,
00608   WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_STD_BTN | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE,
00609   _network_game_window_widgets,
00610   NetworkGameWindowWndProc,
00611 };
00612 
00613 void ShowNetworkGameWindow()
00614 {
00615   static bool first = true;
00616   Window *w;
00617   DeleteWindowById(WC_NETWORK_WINDOW, 0);
00618 
00619   /* Only show once */
00620   if (first) {
00621     char* const *srv;
00622 
00623     first = false;
00624     // add all servers from the config file to our list
00625     for (srv = &_network_host_list[0]; srv != endof(_network_host_list) && *srv != NULL; srv++) {
00626       NetworkAddServer(*srv);
00627     }
00628 
00629     _ng_sorting.criteria = 2; // sort default by collectivity (green-dots on top)
00630     _ng_sorting.order = 0;    // sort ascending by default
00631   }
00632 
00633   w = AllocateWindowDesc(&_network_game_window_desc);
00634   if (w != NULL) {
00635     querystr_d *querystr = &WP(w, network_ql_d).q;
00636 
00637     ttd_strlcpy(_edit_str_buf, _network_player_name, lengthof(_edit_str_buf));
00638     querystr->afilter = CS_ALPHANUMERAL;
00639     InitializeTextBuffer(&querystr->text, _edit_str_buf, lengthof(_edit_str_buf), 120);
00640 
00641     UpdateNetworkGameWindow(true);
00642   }
00643 }
00644 
00645 enum {
00646   NSSWND_START = 64,
00647   NSSWND_ROWSIZE = 12
00648 };
00649 
00651 enum NetworkStartServerWidgets {
00652   NSSW_CLOSE           =  0,   
00653   NSSW_GAMENAME        =  4,   
00654   NSSW_SETPWD          =  5,   
00655   NSSW_SELMAP          =  7,   
00656   NSSW_CONNTYPE_BTN    = 10,   
00657   NSSW_CLIENTS_BTND    = 12,   
00658   NSSW_CLIENTS_TXT     = 13,   
00659   NSSW_CLIENTS_BTNU    = 14,   
00660   NSSW_COMPANIES_BTND  = 16,   
00661   NSSW_COMPANIES_TXT   = 17,   
00662   NSSW_COMPANIES_BTNU  = 18,   
00663   NSSW_SPECTATORS_BTND = 20,   
00664   NSSW_SPECTATORS_TXT  = 21,   
00665   NSSW_SPECTATORS_BTNU = 22,   
00666   NSSW_LANGUAGE_BTN    = 24,   
00667   NSSW_START           = 25,   
00668   NSSW_LOAD            = 26,   
00669   NSSW_CANCEL          = 27,   
00670 };
00671 
00681 static void NetworkStartServerWindowWndProc(Window *w, WindowEvent *e)
00682 {
00683   network_d *nd = &WP(w, network_ql_d).n;
00684 
00685   switch (e->event) {
00686   case WE_CREATE: // focus input box
00687     nd->field = NSSW_GAMENAME;
00688     _network_game_info.use_password = (_network_server_password[0] != '\0');
00689     break;
00690 
00691   case WE_PAINT: {
00692     int y = NSSWND_START, pos;
00693     const FiosItem *item;
00694 
00695     /* draw basic widgets */
00696     SetDParam(1, _connection_types_dropdown[_network_advertise]);
00697     SetDParam(2, _network_game_info.clients_max);
00698     SetDParam(3, _network_game_info.companies_max);
00699     SetDParam(4, _network_game_info.spectators_max);
00700     SetDParam(5, STR_NETWORK_LANG_ANY + _network_game_info.server_lang);
00701     DrawWindowWidgets(w);
00702 
00703     /* editbox to set game name */
00704     DrawEditBox(w, &WP(w, network_ql_d).q, NSSW_GAMENAME);
00705 
00706     /* if password is set, draw red '*' next to 'Set password' button */
00707     if (_network_game_info.use_password) DoDrawString("*", 408, 23, TC_RED);
00708 
00709     /* draw list of maps */
00710     GfxFillRect(11, 63, 258, 215, 0xD7);  // black background of maps list
00711 
00712     pos = w->vscroll.pos;
00713     while (pos < _fios_num + 1) {
00714       item = _fios_list + pos - 1;
00715       if (item == nd->map || (pos == 0 && nd->map == NULL))
00716         GfxFillRect(11, y - 1, 258, y + 10, 155); // show highlighted item with a different colour
00717 
00718       if (pos == 0) {
00719         DrawString(14, y, STR_4010_GENERATE_RANDOM_NEW_GAME, TC_DARK_GREEN);
00720       } else {
00721         DoDrawString(item->title, 14, y, _fios_colors[item->type] );
00722       }
00723       pos++;
00724       y += NSSWND_ROWSIZE;
00725 
00726       if (y >= w->vscroll.cap * NSSWND_ROWSIZE + NSSWND_START) break;
00727     }
00728   } break;
00729 
00730   case WE_CLICK:
00731     if (e->we.click.widget != NSSW_CONNTYPE_BTN && e->we.click.widget != NSSW_LANGUAGE_BTN) HideDropDownMenu(w);
00732     nd->field = e->we.click.widget;
00733     switch (e->we.click.widget) {
00734     case NSSW_CLOSE:  // Close 'X'
00735     case NSSW_CANCEL: // Cancel button
00736       ShowNetworkGameWindow();
00737       break;
00738 
00739     case NSSW_SETPWD: // Set password button
00740       nd->widget_id = NSSW_SETPWD;
00741       ShowQueryString(BindCString(_network_server_password), STR_NETWORK_SET_PASSWORD, 20, 250, w, CS_ALPHANUMERAL);
00742       break;
00743 
00744     case NSSW_SELMAP: { // Select map
00745       int y = (e->we.click.pt.y - NSSWND_START) / NSSWND_ROWSIZE;
00746 
00747       y += w->vscroll.pos;
00748       if (y >= w->vscroll.count) return;
00749 
00750       nd->map = (y == 0) ? NULL : _fios_list + y - 1;
00751       SetWindowDirty(w);
00752       } break;
00753     case NSSW_CONNTYPE_BTN: // Connection type
00754       ShowDropDownMenu(w, _connection_types_dropdown, _network_advertise, NSSW_CONNTYPE_BTN, 0, 0); // do it for widget NSSW_CONNTYPE_BTN
00755       break;
00756     case NSSW_CLIENTS_BTND:    case NSSW_CLIENTS_BTNU:    // Click on up/down button for number of clients
00757     case NSSW_COMPANIES_BTND:  case NSSW_COMPANIES_BTNU:  // Click on up/down button for number of companies
00758     case NSSW_SPECTATORS_BTND: case NSSW_SPECTATORS_BTNU: // Click on up/down button for number of spectators
00759       /* Don't allow too fast scrolling */
00760       if ((w->flags4 & WF_TIMEOUT_MASK) <= 2 << WF_TIMEOUT_SHL) {
00761         w->HandleButtonClick(e->we.click.widget);
00762         SetWindowDirty(w);
00763         switch (e->we.click.widget) {
00764           default: NOT_REACHED();
00765           case NSSW_CLIENTS_BTND: case NSSW_CLIENTS_BTNU:
00766             _network_game_info.clients_max    = Clamp(_network_game_info.clients_max    + e->we.click.widget - NSSW_CLIENTS_TXT,    2, MAX_CLIENTS);
00767             break;
00768           case NSSW_COMPANIES_BTND: case NSSW_COMPANIES_BTNU:
00769             _network_game_info.companies_max  = Clamp(_network_game_info.companies_max  + e->we.click.widget - NSSW_COMPANIES_TXT,  1, MAX_PLAYERS);
00770             break;
00771           case NSSW_SPECTATORS_BTND: case NSSW_SPECTATORS_BTNU:
00772             _network_game_info.spectators_max = Clamp(_network_game_info.spectators_max + e->we.click.widget - NSSW_SPECTATORS_TXT, 0, MAX_CLIENTS);
00773             break;
00774         }
00775       }
00776       _left_button_clicked = false;
00777       break;
00778     case NSSW_CLIENTS_TXT:    // Click on number of players
00779       nd->widget_id = NSSW_CLIENTS_TXT;
00780       SetDParam(0, _network_game_info.clients_max);
00781       ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_NETWORK_NUMBER_OF_CLIENTS,    3, 50, w, CS_NUMERAL);
00782       break;
00783     case NSSW_COMPANIES_TXT:  // Click on number of companies
00784       nd->widget_id = NSSW_COMPANIES_TXT;
00785       SetDParam(0, _network_game_info.companies_max);
00786       ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_NETWORK_NUMBER_OF_COMPANIES,  3, 50, w, CS_NUMERAL);
00787       break;
00788     case NSSW_SPECTATORS_TXT: // Click on number of spectators
00789       nd->widget_id = NSSW_SPECTATORS_TXT;
00790       SetDParam(0, _network_game_info.spectators_max);
00791       ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_NETWORK_NUMBER_OF_SPECTATORS, 3, 50, w, CS_NUMERAL);
00792       break;
00793     case NSSW_LANGUAGE_BTN: { // Language
00794       uint sel = 0;
00795       for (uint i = 0; i < lengthof(_language_dropdown) - 1; i++) {
00796         if (_language_dropdown[i] == STR_NETWORK_LANG_ANY + _network_game_info.server_lang) {
00797           sel = i;
00798           break;
00799         }
00800       }
00801       ShowDropDownMenu(w, _language_dropdown, sel, NSSW_LANGUAGE_BTN, 0, 0);
00802       break;
00803     }
00804     case NSSW_START: // Start game
00805       _is_network_server = true;
00806 
00807       if (nd->map == NULL) { // start random new game
00808         ShowGenerateLandscape();
00809       } else { // load a scenario
00810         char *name = FiosBrowseTo(nd->map);
00811         if (name != NULL) {
00812           SetFiosType(nd->map->type);
00813           _file_to_saveload.filetype = FT_SCENARIO;
00814           ttd_strlcpy(_file_to_saveload.name, name, sizeof(_file_to_saveload.name));
00815           ttd_strlcpy(_file_to_saveload.title, nd->map->title, sizeof(_file_to_saveload.title));
00816 
00817           DeleteWindow(w);
00818           SwitchMode(SM_START_SCENARIO);
00819         }
00820       }
00821       break;
00822     case NSSW_LOAD: // Load game
00823       _is_network_server = true;
00824       /* XXX - WC_NETWORK_WINDOW (this window) should stay, but if it stays, it gets
00825        * copied all the elements of 'load game' and upon closing that, it segfaults */
00826       DeleteWindow(w);
00827       ShowSaveLoadDialog(SLD_LOAD_GAME);
00828       break;
00829     }
00830     break;
00831 
00832   case WE_DROPDOWN_SELECT: // we have selected a dropdown item in the list
00833     switch (e->we.dropdown.button) {
00834       case NSSW_CONNTYPE_BTN:
00835         _network_advertise = (e->we.dropdown.index != 0);
00836         break;
00837       case NSSW_LANGUAGE_BTN:
00838         _network_game_info.server_lang = _language_dropdown[e->we.dropdown.index] - STR_NETWORK_LANG_ANY;
00839         break;
00840       default:
00841         NOT_REACHED();
00842     }
00843 
00844     SetWindowDirty(w);
00845     break;
00846 
00847   case WE_MOUSELOOP:
00848     if (nd->field == NSSW_GAMENAME) HandleEditBox(w, &WP(w, network_ql_d).q, NSSW_GAMENAME);
00849     break;
00850 
00851   case WE_KEYPRESS:
00852     if (nd->field == NSSW_GAMENAME) {
00853       if (HandleEditBoxKey(w, &WP(w, network_ql_d).q, NSSW_GAMENAME, e) == 1) break; // enter pressed
00854 
00855       ttd_strlcpy(_network_server_name, WP(w, network_ql_d).q.text.buf, sizeof(_network_server_name));
00856     }
00857     break;
00858 
00859   case WE_ON_EDIT_TEXT:
00860     if (e->we.edittext.str == NULL) break;
00861 
00862     if (nd->widget_id == NSSW_SETPWD) {
00863       ttd_strlcpy(_network_server_password, e->we.edittext.str, lengthof(_network_server_password));
00864       _network_game_info.use_password = (_network_server_password[0] != '\0');
00865     } else {
00866       int32 value = atoi(e->we.edittext.str);
00867       w->InvalidateWidget(nd->widget_id);
00868       switch (nd->widget_id) {
00869         default: NOT_REACHED();
00870         case NSSW_CLIENTS_TXT:    _network_game_info.clients_max    = Clamp(value, 2, MAX_CLIENTS); break;
00871         case NSSW_COMPANIES_TXT:  _network_game_info.companies_max  = Clamp(value, 1, MAX_PLAYERS); break;
00872         case NSSW_SPECTATORS_TXT: _network_game_info.spectators_max = Clamp(value, 0, MAX_CLIENTS); break;
00873       }
00874     }
00875 
00876     SetWindowDirty(w);
00877     break;
00878   }
00879 }
00880 
00881 static const Widget _network_start_server_window_widgets[] = {
00882 /* Window decoration and background panel */
00883 {   WWT_CLOSEBOX,   RESIZE_NONE,   BGC,     0,    10,     0,    13, STR_00C5,                         STR_018B_CLOSE_WINDOW },               // NSSW_CLOSE
00884 {    WWT_CAPTION,   RESIZE_NONE,   BGC,    11,   419,     0,    13, STR_NETWORK_START_GAME_WINDOW,    STR_NULL},
00885 {      WWT_PANEL,   RESIZE_NONE,   BGC,     0,   419,    14,   243, 0x0,                              STR_NULL},
00886 
00887 /* Set game name and password widgets */
00888 {       WWT_TEXT,   RESIZE_NONE,   BGC,    10,    90,    22,    34, STR_NETWORK_NEW_GAME_NAME,        STR_NULL},
00889 {      WWT_PANEL,   RESIZE_NONE,   BGC,   100,   272,    22,    33, 0x0,                              STR_NETWORK_NEW_GAME_NAME_TIP},        // NSSW_GAMENAME
00890 { WWT_PUSHTXTBTN,   RESIZE_NONE,   BTC,   285,   405,    22,    33, STR_NETWORK_SET_PASSWORD,         STR_NETWORK_PASSWORD_TIP},             // NSSW_SETPWD
00891 
00892 /* List of playable scenarios */
00893 {       WWT_TEXT,   RESIZE_NONE,   BGC,    10,   110,    43,    55, STR_NETWORK_SELECT_MAP,           STR_NULL},
00894 {      WWT_INSET,   RESIZE_NONE,   BGC,    10,   271,    62,   216, STR_NULL,                         STR_NETWORK_SELECT_MAP_TIP},           // NSSW_SELMAP
00895 {  WWT_SCROLLBAR,   RESIZE_NONE,   BGC,   259,   270,    63,   215, 0x0,                              STR_0190_SCROLL_BAR_SCROLLS_LIST},
00896 
00897 /* Combo/selection boxes to control Connection Type / Max Clients / Max Companies / Max Observers / Language */
00898 {       WWT_TEXT,   RESIZE_NONE,   BGC,   280,   419,    63,    75, STR_NETWORK_CONNECTION,           STR_NULL},
00899 { WWT_DROPDOWNIN,   RESIZE_NONE,   BGC,   280,   410,    77,    88, STR_NETWORK_LAN_INTERNET_COMBO,   STR_NETWORK_CONNECTION_TIP},           // NSSW_CONNTYPE_BTN
00900 
00901 {       WWT_TEXT,   RESIZE_NONE,   BGC,   280,   419,    95,   107, STR_NETWORK_NUMBER_OF_CLIENTS,    STR_NULL},
00902 {     WWT_IMGBTN,   RESIZE_NONE,   BGC,   280,   291,   109,   120, SPR_ARROW_DOWN,                   STR_NETWORK_NUMBER_OF_CLIENTS_TIP},    // NSSW_CLIENTS_BTND
00903 { WWT_PUSHTXTBTN,   RESIZE_NONE,   BGC,   292,   397,   109,   120, STR_NETWORK_CLIENTS_SELECT,       STR_NETWORK_NUMBER_OF_CLIENTS_TIP},    // NSSW_CLIENTS_TXT
00904 {     WWT_IMGBTN,   RESIZE_NONE,   BGC,   398,   410,   109,   120, SPR_ARROW_UP,                     STR_NETWORK_NUMBER_OF_CLIENTS_TIP},    // NSSW_CLIENTS_BTNU
00905 
00906 {       WWT_TEXT,   RESIZE_NONE,   BGC,   280,   419,   127,   139, STR_NETWORK_NUMBER_OF_COMPANIES,  STR_NULL},
00907 {     WWT_IMGBTN,   RESIZE_NONE,   BGC,   280,   291,   141,   152, SPR_ARROW_DOWN,                   STR_NETWORK_NUMBER_OF_COMPANIES_TIP},  // NSSW_COMPANIES_BTND
00908 { WWT_PUSHTXTBTN,   RESIZE_NONE,   BGC,   292,   397,   141,   152, STR_NETWORK_COMPANIES_SELECT,     STR_NETWORK_NUMBER_OF_COMPANIES_TIP},  // NSSW_COMPANIES_TXT
00909 {     WWT_IMGBTN,   RESIZE_NONE,   BGC,   398,   410,   141,   152, SPR_ARROW_UP,                     STR_NETWORK_NUMBER_OF_COMPANIES_TIP},  // NSSW_COMPANIES_BTNU
00910 
00911 {       WWT_TEXT,   RESIZE_NONE,   BGC,   280,   419,   159,   171, STR_NETWORK_NUMBER_OF_SPECTATORS, STR_NULL},
00912 {     WWT_IMGBTN,   RESIZE_NONE,   BGC,   280,   291,   173,   184, SPR_ARROW_DOWN,                   STR_NETWORK_NUMBER_OF_SPECTATORS_TIP}, // NSSW_SPECTATORS_BTND
00913 { WWT_PUSHTXTBTN,   RESIZE_NONE,   BGC,   292,   397,   173,   184, STR_NETWORK_SPECTATORS_SELECT,    STR_NETWORK_NUMBER_OF_SPECTATORS_TIP}, // NSSW_SPECTATORS_TXT
00914 {     WWT_IMGBTN,   RESIZE_NONE,   BGC,   398,   410,   173,   184, SPR_ARROW_UP,                     STR_NETWORK_NUMBER_OF_SPECTATORS_TIP}, // NSSW_SPECTATORS_BTNU
00915 
00916 {       WWT_TEXT,   RESIZE_NONE,   BGC,   280,   419,   191,   203, STR_NETWORK_LANGUAGE_SPOKEN,      STR_NULL},
00917 { WWT_DROPDOWNIN,   RESIZE_NONE,   BGC,   280,   410,   205,   216, STR_NETWORK_LANGUAGE_COMBO,       STR_NETWORK_LANGUAGE_TIP},             // NSSW_LANGUAGE_BTN
00918 
00919 /* Buttons Start / Load / Cancel */
00920 { WWT_PUSHTXTBTN,   RESIZE_NONE,   BTC,    40,   140,   224,   235, STR_NETWORK_START_GAME,           STR_NETWORK_START_GAME_TIP},           // NSSW_START
00921 { WWT_PUSHTXTBTN,   RESIZE_NONE,   BTC,   150,   250,   224,   235, STR_NETWORK_LOAD_GAME,            STR_NETWORK_LOAD_GAME_TIP},            // NSSW_LOAD
00922 { WWT_PUSHTXTBTN,   RESIZE_NONE,   BTC,   260,   360,   224,   235, STR_012E_CANCEL,                  STR_NULL},                             // NSSW_CANCEL
00923 
00924 {   WIDGETS_END},
00925 };
00926 
00927 static const WindowDesc _network_start_server_window_desc = {
00928   WDP_CENTER, WDP_CENTER, 420, 244, 420, 244,
00929   WC_NETWORK_WINDOW, WC_NONE,
00930   WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
00931   _network_start_server_window_widgets,
00932   NetworkStartServerWindowWndProc,
00933 };
00934 
00935 static void ShowNetworkStartServerWindow()
00936 {
00937   Window *w;
00938   DeleteWindowById(WC_NETWORK_WINDOW, 0);
00939 
00940   w = AllocateWindowDesc(&_network_start_server_window_desc);
00941   ttd_strlcpy(_edit_str_buf, _network_server_name, lengthof(_edit_str_buf));
00942 
00943   _saveload_mode = SLD_NEW_GAME;
00944   BuildFileList();
00945   w->vscroll.cap = 12;
00946   w->vscroll.count = _fios_num + 1;
00947 
00948   WP(w, network_ql_d).q.afilter = CS_ALPHANUMERAL;
00949   InitializeTextBuffer(&WP(w, network_ql_d).q.text, _edit_str_buf, lengthof(_edit_str_buf), 160);
00950 }
00951 
00952 static PlayerID NetworkLobbyFindCompanyIndex(byte pos)
00953 {
00954   PlayerID i;
00955 
00956   /* Scroll through all _network_player_info and get the 'pos' item
00957       that is not empty */
00958   for (i = PLAYER_FIRST; i < MAX_PLAYERS; i++) {
00959     if (_network_player_info[i].company_name[0] != '\0') {
00960       if (pos-- == 0) return i;
00961     }
00962   }
00963 
00964   return PLAYER_FIRST;
00965 }
00966 
00968 enum NetworkLobbyWindowWidgets {
00969   NLWW_CLOSE    =  0, 
00970   NLWW_MATRIX   =  5, 
00971   NLWW_DETAILS  =  7, 
00972   NLWW_JOIN     =  8, 
00973   NLWW_NEW      =  9, 
00974   NLWW_SPECTATE = 10, 
00975   NLWW_REFRESH  = 11, 
00976   NLWW_CANCEL   = 12, 
00977 };
00978 
00988 static void NetworkLobbyWindowWndProc(Window *w, WindowEvent *e)
00989 {
00990   network_d *nd = &WP(w, network_d);
00991 
00992   switch (e->event) {
00993   case WE_CREATE:
00994     nd->company = INVALID_PLAYER;
00995     break;
00996 
00997   case WE_PAINT: {
00998     const NetworkGameInfo *gi = &nd->server->info;
00999     int y = NET_PRC__OFFSET_TOP_WIDGET_COMPANY, pos;
01000 
01001     /* Join button is disabled when no company is selected */
01002     w->SetWidgetDisabledState(NLWW_JOIN, nd->company == INVALID_PLAYER);
01003     /* Cannot start new company if there are too many */
01004     w->SetWidgetDisabledState(NLWW_NEW, gi->companies_on >= gi->companies_max);
01005     /* Cannot spectate if there are too many spectators */
01006     w->SetWidgetDisabledState(NLWW_SPECTATE, gi->spectators_on >= gi->spectators_max);
01007 
01008     /* Draw window widgets */
01009     SetDParamStr(0, gi->server_name);
01010     DrawWindowWidgets(w);
01011 
01012     /* Draw company list */
01013     pos = w->vscroll.pos;
01014     while (pos < gi->companies_on) {
01015       byte company = NetworkLobbyFindCompanyIndex(pos);
01016       bool income = false;
01017       if (nd->company == company)
01018         GfxFillRect(11, y - 1, 154, y + 10, 10); // show highlighted item with a different colour
01019 
01020       DoDrawStringTruncated(_network_player_info[company].company_name, 13, y, TC_BLACK, 135 - 13);
01021       if (_network_player_info[company].use_password != 0) DrawSprite(SPR_LOCK, PAL_NONE, 135, y);
01022 
01023       /* If the company's income was positive puts a green dot else a red dot */
01024       if (_network_player_info[company].income >= 0) income = true;
01025       DrawSprite(SPR_BLOT, income ? PALETTE_TO_GREEN : PALETTE_TO_RED, 145, y);
01026 
01027       pos++;
01028       y += NET_PRC__SIZE_OF_ROW;
01029       if (pos >= w->vscroll.cap) break;
01030     }
01031 
01032     /* Draw info about selected company when it is selected in the left window */
01033     GfxFillRect(174, 39, 403, 75, 157);
01034     DrawStringCentered(290, 50, STR_NETWORK_COMPANY_INFO, TC_FROMSTRING);
01035     if (nd->company != INVALID_PLAYER) {
01036       const uint x = 183;
01037       const uint trunc_width = w->widget[NLWW_DETAILS].right - x;
01038       y = 80;
01039 
01040       SetDParam(0, nd->server->info.clients_on);
01041       SetDParam(1, nd->server->info.clients_max);
01042       SetDParam(2, nd->server->info.companies_on);
01043       SetDParam(3, nd->server->info.companies_max);
01044       DrawString(x, y, STR_NETWORK_CLIENTS, TC_GOLD);
01045       y += 10;
01046 
01047       SetDParamStr(0, _network_player_info[nd->company].company_name);
01048       DrawStringTruncated(x, y, STR_NETWORK_COMPANY_NAME, TC_GOLD, trunc_width);
01049       y += 10;
01050 
01051       SetDParam(0, _network_player_info[nd->company].inaugurated_year);
01052       DrawString(x, y, STR_NETWORK_INAUGURATION_YEAR, TC_GOLD); // inauguration year
01053       y += 10;
01054 
01055       SetDParam(0, _network_player_info[nd->company].company_value);
01056       DrawString(x, y, STR_NETWORK_VALUE, TC_GOLD); // company value
01057       y += 10;
01058 
01059       SetDParam(0, _network_player_info[nd->company].money);
01060       DrawString(x, y, STR_NETWORK_CURRENT_BALANCE, TC_GOLD); // current balance
01061       y += 10;
01062 
01063       SetDParam(0, _network_player_info[nd->company].income);
01064       DrawString(x, y, STR_NETWORK_LAST_YEARS_INCOME, TC_GOLD); // last year's income
01065       y += 10;
01066 
01067       SetDParam(0, _network_player_info[nd->company].performance);
01068       DrawString(x, y, STR_NETWORK_PERFORMANCE, TC_GOLD); // performance
01069       y += 10;
01070 
01071       SetDParam(0, _network_player_info[nd->company].num_vehicle[0]);
01072       SetDParam(1, _network_player_info[nd->company].num_vehicle[1]);
01073       SetDParam(2, _network_player_info[nd->company].num_vehicle[2]);
01074       SetDParam(3, _network_player_info[nd->company].num_vehicle[3]);
01075       SetDParam(4, _network_player_info[nd->company].num_vehicle[4]);
01076       DrawString(x, y, STR_NETWORK_VEHICLES, TC_GOLD); // vehicles
01077       y += 10;
01078 
01079       SetDParam(0, _network_player_info[nd->company].num_station[0]);
01080       SetDParam(1, _network_player_info[nd->company].num_station[1]);
01081       SetDParam(2, _network_player_info[nd->company].num_station[2]);
01082       SetDParam(3, _network_player_info[nd->company].num_station[3]);
01083       SetDParam(4, _network_player_info[nd->company].num_station[4]);
01084       DrawString(x, y, STR_NETWORK_STATIONS, TC_GOLD); // stations
01085       y += 10;
01086 
01087       SetDParamStr(0, _network_player_info[nd->company].players);
01088       DrawStringTruncated(x, y, STR_NETWORK_PLAYERS, TC_GOLD, trunc_width); // players
01089     }
01090   } break;
01091 
01092   case WE_CLICK:
01093     switch (e->we.click.widget) {
01094     case NLWW_CLOSE:    // Close 'X'
01095     case NLWW_CANCEL:   // Cancel button
01096       ShowNetworkGameWindow();
01097       break;
01098     case NLWW_MATRIX: { // Company list
01099       uint32 id_v = (e->we.click.pt.y - NET_PRC__OFFSET_TOP_WIDGET_COMPANY) / NET_PRC__SIZE_OF_ROW;
01100 
01101       if (id_v >= w->vscroll.cap) break;
01102 
01103       id_v += w->vscroll.pos;
01104       nd->company = (id_v >= nd->server->info.companies_on) ? INVALID_PLAYER : NetworkLobbyFindCompanyIndex(id_v);
01105       SetWindowDirty(w);
01106     } break;
01107     case NLWW_JOIN:     // Join company
01108       /* Button can be clicked only when it is enabled */
01109       _network_playas = nd->company;
01110       NetworkClientConnectGame(_network_last_host, _network_last_port);
01111       break;
01112     case NLWW_NEW:      // New company
01113       _network_playas = PLAYER_NEW_COMPANY;
01114       NetworkClientConnectGame(_network_last_host, _network_last_port);
01115       break;
01116     case NLWW_SPECTATE: // Spectate game
01117       _network_playas = PLAYER_SPECTATOR;
01118       NetworkClientConnectGame(_network_last_host, _network_last_port);
01119       break;
01120     case NLWW_REFRESH:  // Refresh
01121       NetworkTCPQueryServer(_network_last_host, _network_last_port); // company info
01122       NetworkUDPQueryServer(_network_last_host, _network_last_port); // general data
01123       break;
01124     } break;
01125 
01126   case WE_MESSAGE:
01127     SetWindowDirty(w);
01128     break;
01129   }
01130 }
01131 
01132 static const Widget _network_lobby_window_widgets[] = {
01133 {   WWT_CLOSEBOX,   RESIZE_NONE,   BGC,     0,    10,     0,    13, STR_00C5,                    STR_018B_CLOSE_WINDOW },           // NLWW_CLOSE
01134 {    WWT_CAPTION,   RESIZE_NONE,   BGC,    11,   419,     0,    13, STR_NETWORK_GAME_LOBBY,      STR_NULL},
01135 {      WWT_PANEL,   RESIZE_NONE,   BGC,     0,   419,    14,   234, 0x0,                         STR_NULL},
01136 {       WWT_TEXT,   RESIZE_NONE,   BGC,    10,   419,    22,    34, STR_NETWORK_PREPARE_TO_JOIN, STR_NULL},
01137 
01138 /* company list */
01139 {      WWT_PANEL,   RESIZE_NONE,   BTC,    10,   155,    38,    49, 0x0,                         STR_NULL},
01140 {     WWT_MATRIX,   RESIZE_NONE,   BGC,    10,   155,    50,   190, (10 << 8) + 1,               STR_NETWORK_COMPANY_LIST_TIP},     // NLWW_MATRIX
01141 {  WWT_SCROLLBAR,   RESIZE_NONE,   BGC,   156,   167,    38,   190, 0x0,                         STR_0190_SCROLL_BAR_SCROLLS_LIST},
01142 
01143 /* company/player info */
01144 {      WWT_PANEL,   RESIZE_NONE,   BGC,   173,   404,    38,   190, 0x0,                         STR_NULL},                         // NLWW_DETAILS
01145 
01146 /* buttons */
01147 { WWT_PUSHTXTBTN,   RESIZE_NONE,   BTC,    10,   151,   200,   211, STR_NETWORK_JOIN_COMPANY,    STR_NETWORK_JOIN_COMPANY_TIP},     // NLWW_JOIN
01148 { WWT_PUSHTXTBTN,   RESIZE_NONE,   BTC,    10,   151,   215,   226, STR_NETWORK_NEW_COMPANY,     STR_NETWORK_NEW_COMPANY_TIP},      // NLWW_NEW
01149 { WWT_PUSHTXTBTN,   RESIZE_NONE,   BTC,   158,   268,   200,   211, STR_NETWORK_SPECTATE_GAME,   STR_NETWORK_SPECTATE_GAME_TIP},    // NLWW_SPECTATE
01150 { WWT_PUSHTXTBTN,   RESIZE_NONE,   BTC,   158,   268,   215,   226, STR_NETWORK_REFRESH,         STR_NETWORK_REFRESH_TIP},          // NLWW_REFRESH
01151 { WWT_PUSHTXTBTN,   RESIZE_NONE,   BTC,   278,   388,   200,   211, STR_012E_CANCEL,             STR_NULL},                         // NLWW_CANCEL
01152 
01153 {   WIDGETS_END},
01154 };
01155 
01156 static const WindowDesc _network_lobby_window_desc = {
01157   WDP_CENTER, WDP_CENTER, 420, 235, 420, 235,
01158   WC_NETWORK_WINDOW, WC_NONE,
01159   WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
01160   _network_lobby_window_widgets,
01161   NetworkLobbyWindowWndProc,
01162 };
01163 
01164 /* Show the networklobbywindow with the selected server
01165  * @param ngl Selected game pointer which is passed to the new window */
01166 static void ShowNetworkLobbyWindow(NetworkGameList *ngl)
01167 {
01168   Window *w;
01169   DeleteWindowById(WC_NETWORK_WINDOW, 0);
01170 
01171   NetworkTCPQueryServer(_network_last_host, _network_last_port); // company info
01172   NetworkUDPQueryServer(_network_last_host, _network_last_port); // general data
01173 
01174   w = AllocateWindowDesc(&_network_lobby_window_desc);
01175   if (w != NULL) {
01176     WP(w, network_ql_d).n.server = ngl;
01177     strcpy(_edit_str_buf, "");
01178     w->vscroll.cap = 10;
01179   }
01180 }
01181 
01182 // The window below gives information about the connected clients
01183 //  and also makes able to give money to them, kick them (if server)
01184 //  and stuff like that.
01185 
01186 extern void DrawPlayerIcon(PlayerID pid, int x, int y);
01187 
01188 // Every action must be of this form
01189 typedef void ClientList_Action_Proc(byte client_no);
01190 
01191 // Max 10 actions per client
01192 #define MAX_CLIENTLIST_ACTION 10
01193 
01194 // Some standard bullshit.. defines variables ;)
01195 static void ClientListWndProc(Window *w, WindowEvent *e);
01196 static void ClientListPopupWndProc(Window *w, WindowEvent *e);
01197 static byte _selected_clientlist_item = 255;
01198 static byte _selected_clientlist_y = 0;
01199 static char _clientlist_action[MAX_CLIENTLIST_ACTION][50];
01200 static ClientList_Action_Proc *_clientlist_proc[MAX_CLIENTLIST_ACTION];
01201 
01202 enum {
01203   CLNWND_OFFSET = 16,
01204   CLNWND_ROWSIZE = 10
01205 };
01206 
01207 static const Widget _client_list_widgets[] = {
01208 {   WWT_CLOSEBOX,   RESIZE_NONE,    14,     0,    10,     0,    13, STR_00C5,                 STR_018B_CLOSE_WINDOW},
01209 {    WWT_CAPTION,   RESIZE_NONE,    14,    11,   237,     0,    13, STR_NETWORK_CLIENT_LIST,  STR_018C_WINDOW_TITLE_DRAG_THIS},
01210 {  WWT_STICKYBOX,   RESIZE_NONE,    14,   238,   249,     0,    13, STR_NULL,                 STR_STICKY_BUTTON},
01211 
01212 {      WWT_PANEL,   RESIZE_NONE,    14,     0,   249,    14,    14 + CLNWND_ROWSIZE + 1, 0x0, STR_NULL},
01213 {   WIDGETS_END},
01214 };
01215 
01216 static const Widget _client_list_popup_widgets[] = {
01217 {      WWT_PANEL,   RESIZE_NONE,    14,     0,   99,     0,     0,     0, STR_NULL},
01218 {   WIDGETS_END},
01219 };
01220 
01221 static WindowDesc _client_list_desc = {
01222   WDP_AUTO, WDP_AUTO, 250, 1, 250, 1,
01223   WC_CLIENT_LIST, WC_NONE,
01224   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON,
01225   _client_list_widgets,
01226   ClientListWndProc
01227 };
01228 
01229 // Finds the Xth client-info that is active
01230 static const NetworkClientInfo *NetworkFindClientInfo(byte client_no)
01231 {
01232   const NetworkClientInfo *ci;
01233 
01234   FOR_ALL_ACTIVE_CLIENT_INFOS(ci) {
01235     if (client_no == 0) return ci;
01236     client_no--;
01237   }
01238 
01239   return NULL;
01240 }
01241 
01242 // Here we start to define the options out of the menu
01243 static void ClientList_Kick(byte client_no)
01244 {
01245   if (client_no < MAX_PLAYERS)
01246     SEND_COMMAND(PACKET_SERVER_ERROR)(DEREF_CLIENT(client_no), NETWORK_ERROR_KICKED);
01247 }
01248 
01249 static void ClientList_Ban(byte client_no)
01250 {
01251   uint i;
01252   uint32 ip = NetworkFindClientInfo(client_no)->client_ip;
01253 
01254   for (i = 0; i < lengthof(_network_ban_list); i++) {
01255     if (_network_ban_list[i] == NULL) {
01256       _network_ban_list[i] = strdup(inet_ntoa(*(struct in_addr *)&ip));
01257       break;
01258     }
01259   }
01260 
01261   if (client_no < MAX_PLAYERS)
01262     SEND_COMMAND(PACKET_SERVER_ERROR)(DEREF_CLIENT(client_no), NETWORK_ERROR_KICKED);
01263 }
01264 
01265 static void ClientList_GiveMoney(byte client_no)
01266 {
01267   if (NetworkFindClientInfo(client_no) != NULL)
01268     ShowNetworkGiveMoneyWindow(NetworkFindClientInfo(client_no)->client_playas);
01269 }
01270 
01271 static void ClientList_SpeakToClient(byte client_no)
01272 {
01273   if (NetworkFindClientInfo(client_no) != NULL)
01274     ShowNetworkChatQueryWindow(DESTTYPE_CLIENT, NetworkFindClientInfo(client_no)->client_index);
01275 }
01276 
01277 static void ClientList_SpeakToCompany(byte client_no)
01278 {
01279   if (NetworkFindClientInfo(client_no) != NULL)
01280     ShowNetworkChatQueryWindow(DESTTYPE_TEAM, NetworkFindClientInfo(client_no)->client_playas);
01281 }
01282 
01283 static void ClientList_SpeakToAll(byte client_no)
01284 {
01285   ShowNetworkChatQueryWindow(DESTTYPE_BROADCAST, 0);
01286 }
01287 
01288 static void ClientList_None(byte client_no)
01289 {
01290   // No action ;)
01291 }
01292 
01293 
01294 
01295 // Help, a action is clicked! What do we do?
01296 static void HandleClientListPopupClick(byte index, byte clientno)
01297 {
01298   // A click on the Popup of the ClientList.. handle the command
01299   if (index < MAX_CLIENTLIST_ACTION && _clientlist_proc[index] != NULL) {
01300     _clientlist_proc[index](clientno);
01301   }
01302 }
01303 
01304 // Finds the amount of clients and set the height correct
01305 static bool CheckClientListHeight(Window *w)
01306 {
01307   int num = 0;
01308   const NetworkClientInfo *ci;
01309 
01310   // Should be replaced with a loop through all clients
01311   FOR_ALL_ACTIVE_CLIENT_INFOS(ci) {
01312     num++;
01313   }
01314 
01315   num *= CLNWND_ROWSIZE;
01316 
01317   // If height is changed
01318   if (w->height != CLNWND_OFFSET + num + 1) {
01319     // XXX - magic unfortunately; (num + 2) has to be one bigger than heigh (num + 1)
01320     SetWindowDirty(w);
01321     w->widget[3].bottom = w->widget[3].top + num + 2;
01322     w->height = CLNWND_OFFSET + num + 1;
01323     SetWindowDirty(w);
01324     return false;
01325   }
01326   return true;
01327 }
01328 
01329 // Finds the amount of actions in the popup and set the height correct
01330 static uint ClientListPopupHeight()
01331 {
01332   int i, num = 0;
01333 
01334   // Find the amount of actions
01335   for (i = 0; i < MAX_CLIENTLIST_ACTION; i++) {
01336     if (_clientlist_action[i][0] == '\0') continue;
01337     if (_clientlist_proc[i] == NULL) continue;
01338     num++;
01339   }
01340 
01341   num *= CLNWND_ROWSIZE;
01342 
01343   return num + 1;
01344 }
01345 
01346 // Show the popup (action list)
01347 static Window *PopupClientList(Window *w, int client_no, int x, int y)
01348 {
01349   int i, h;
01350   const NetworkClientInfo *ci;
01351   DeleteWindowById(WC_TOOLBAR_MENU, 0);
01352 
01353   // Clean the current actions
01354   for (i = 0; i < MAX_CLIENTLIST_ACTION; i++) {
01355     _clientlist_action[i][0] = '\0';
01356     _clientlist_proc[i] = NULL;
01357   }
01358 
01359   // Fill the actions this client has
01360   // Watch is, max 50 chars long!
01361 
01362   ci = NetworkFindClientInfo(client_no);
01363   if (ci == NULL) return NULL;
01364 
01365   i = 0;
01366   if (_network_own_client_index != ci->client_index) {
01367     GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_SPEAK_TO_CLIENT, lastof(_clientlist_action[i]));
01368     _clientlist_proc[i++] = &ClientList_SpeakToClient;
01369   }
01370 
01371   if (IsValidPlayer(ci->client_playas) || ci->client_playas == PLAYER_SPECTATOR) {
01372     GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_SPEAK_TO_COMPANY, lastof(_clientlist_action[i]));
01373     _clientlist_proc[i++] = &ClientList_SpeakToCompany;
01374   }
01375   GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_SPEAK_TO_ALL, lastof(_clientlist_action[i]));
01376   _clientlist_proc[i++] = &ClientList_SpeakToAll;
01377 
01378   if (_network_own_client_index != ci->client_index) {
01379     /* We are no spectator and the player we want to give money to is no spectator and money gifts are allowed */
01380     if (IsValidPlayer(_network_playas) && IsValidPlayer(ci->client_playas) && _patches.give_money) {
01381       GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_GIVE_MONEY, lastof(_clientlist_action[i]));
01382       _clientlist_proc[i++] = &ClientList_GiveMoney;
01383     }
01384   }
01385 
01386   // A server can kick clients (but not himself)
01387   if (_network_server && _network_own_client_index != ci->client_index) {
01388     GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_KICK, lastof(_clientlist_action[i]));
01389     _clientlist_proc[i++] = &ClientList_Kick;
01390 
01391     sprintf(_clientlist_action[i],"Ban"); // XXX GetString?
01392     _clientlist_proc[i++] = &ClientList_Ban;
01393   }
01394 
01395   if (i == 0) {
01396     GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_NONE, lastof(_clientlist_action[i]));
01397     _clientlist_proc[i++] = &ClientList_None;
01398   }
01399 
01400   /* Calculate the height */
01401   h = ClientListPopupHeight();
01402 
01403   // Allocate the popup
01404   w = AllocateWindow(x, y, 150, h + 1, ClientListPopupWndProc, WC_TOOLBAR_MENU, _client_list_popup_widgets);
01405   w->widget[0].bottom = w->widget[0].top + h;
01406   w->widget[0].right = w->widget[0].left + 150;
01407 
01408   w->flags4 &= ~WF_WHITE_BORDER_MASK;
01409   WP(w, menu_d).item_count = 0;
01410   // Save our client
01411   WP(w, menu_d).main_button = client_no;
01412   WP(w, menu_d).sel_index = 0;
01413   // We are a popup
01414   _popup_menu_active = true;
01415 
01416   return w;
01417 }
01418 
01421 static void ClientListPopupWndProc(Window *w, WindowEvent *e)
01422 {
01423   switch (e->event) {
01424   case WE_PAINT: {
01425     int i, y, sel;
01426     TextColour colour;
01427     DrawWindowWidgets(w);
01428 
01429     // Draw the actions
01430     sel = WP(w, menu_d).sel_index;
01431     y = 1;
01432     for (i = 0; i < MAX_CLIENTLIST_ACTION; i++, y += CLNWND_ROWSIZE) {
01433       if (_clientlist_action[i][0] == '\0') continue;
01434       if (_clientlist_proc[i] == NULL) continue;
01435 
01436       if (sel-- == 0) { // Selected item, highlight it
01437         GfxFillRect(1, y, 150 - 2, y + CLNWND_ROWSIZE - 1, 0);
01438         colour = TC_WHITE;
01439       } else {
01440         colour = TC_BLACK;
01441       }
01442 
01443       DoDrawString(_clientlist_action[i], 4, y, colour);
01444     }
01445   } break;
01446 
01447   case WE_POPUPMENU_SELECT: {
01448     // We selected an action
01449     int index = (e->we.popupmenu.pt.y - w->top) / CLNWND_ROWSIZE;
01450 
01451     if (index >= 0 && e->we.popupmenu.pt.y >= w->top)
01452       HandleClientListPopupClick(index, WP(w, menu_d).main_button);
01453 
01454     DeleteWindowById(WC_TOOLBAR_MENU, 0);
01455   } break;
01456 
01457   case WE_POPUPMENU_OVER: {
01458     // Our mouse hoovers over an action? Select it!
01459     int index = (e->we.popupmenu.pt.y - w->top) / CLNWND_ROWSIZE;
01460 
01461     if (index == -1 || index == WP(w, menu_d).sel_index) return;
01462 
01463     WP(w, menu_d).sel_index = index;
01464     SetWindowDirty(w);
01465   } break;
01466 
01467   }
01468 }
01469 
01470 // Main handle for clientlist
01471 static void ClientListWndProc(Window *w, WindowEvent *e)
01472 {
01473   switch (e->event) {
01474   case WE_PAINT: {
01475     NetworkClientInfo *ci;
01476     int y, i = 0;
01477     TextColour colour;
01478 
01479     // Check if we need to reset the height
01480     if (!CheckClientListHeight(w)) break;
01481 
01482     DrawWindowWidgets(w);
01483 
01484     y = CLNWND_OFFSET;
01485 
01486     FOR_ALL_ACTIVE_CLIENT_INFOS(ci) {
01487       if (_selected_clientlist_item == i++) { // Selected item, highlight it
01488         GfxFillRect(1, y, 248, y + CLNWND_ROWSIZE - 1, 0);
01489         colour = TC_WHITE;
01490       } else {
01491         colour = TC_BLACK;
01492       }
01493 
01494       if (ci->client_index == NETWORK_SERVER_INDEX) {
01495         DrawString(4, y, STR_NETWORK_SERVER, colour);
01496       } else {
01497         DrawString(4, y, STR_NETWORK_CLIENT, colour);
01498       }
01499 
01500       // Filter out spectators
01501       if (IsValidPlayer(ci->client_playas)) DrawPlayerIcon(ci->client_playas, 64, y + 1);
01502 
01503       DoDrawString(ci->client_name, 81, y, colour);
01504 
01505       y += CLNWND_ROWSIZE;
01506     }
01507   } break;
01508 
01509   case WE_CLICK:
01510     // Show the popup with option
01511     if (_selected_clientlist_item != 255) {
01512       PopupClientList(w, _selected_clientlist_item, e->we.click.pt.x + w->left, e->we.click.pt.y + w->top);
01513     }
01514 
01515     break;
01516 
01517   case WE_MOUSEOVER:
01518     // -1 means we left the current window
01519     if (e->we.mouseover.pt.y == -1) {
01520       _selected_clientlist_y = 0;
01521       _selected_clientlist_item = 255;
01522       SetWindowDirty(w);
01523       break;
01524     }
01525     // It did not change.. no update!
01526     if (e->we.mouseover.pt.y == _selected_clientlist_y) break;
01527 
01528     // Find the new selected item (if any)
01529     _selected_clientlist_y = e->we.mouseover.pt.y;
01530     if (e->we.mouseover.pt.y > CLNWND_OFFSET) {
01531       _selected_clientlist_item = (e->we.mouseover.pt.y - CLNWND_OFFSET) / CLNWND_ROWSIZE;
01532     } else {
01533       _selected_clientlist_item = 255;
01534     }
01535 
01536     // Repaint
01537     SetWindowDirty(w);
01538     break;
01539 
01540   case WE_DESTROY: case WE_CREATE:
01541     // When created or destroyed, data is reset
01542     _selected_clientlist_item = 255;
01543     _selected_clientlist_y = 0;
01544     break;
01545   }
01546 }
01547 
01548 void ShowClientList()
01549 {
01550   AllocateWindowDescFront(&_client_list_desc, 0);
01551 }
01552 
01553 
01554 static NetworkPasswordType pw_type;
01555 
01556 
01557 void ShowNetworkNeedPassword(NetworkPasswordType npt)
01558 {
01559   StringID caption;
01560 
01561   pw_type = npt;
01562   switch (npt) {
01563     default: NOT_REACHED();
01564     case NETWORK_GAME_PASSWORD:    caption = STR_NETWORK_NEED_GAME_PASSWORD_CAPTION; break;
01565     case NETWORK_COMPANY_PASSWORD: caption = STR_NETWORK_NEED_COMPANY_PASSWORD_CAPTION; break;
01566   }
01567   ShowQueryString(STR_EMPTY, caption, 20, 180, FindWindowById(WC_NETWORK_STATUS_WINDOW, 0), CS_ALPHANUMERAL);
01568 }
01569 
01570 
01571 static void NetworkJoinStatusWindowWndProc(Window *w, WindowEvent *e)
01572 {
01573   switch (e->event) {
01574   case WE_PAINT: {
01575     uint8 progress; // used for progress bar
01576     DrawWindowWidgets(w);
01577 
01578     DrawStringCentered(125, 35, STR_NETWORK_CONNECTING_1 + _network_join_status, TC_GREY);
01579     switch (_network_join_status) {
01580       case NETWORK_JOIN_STATUS_CONNECTING: case NETWORK_JOIN_STATUS_AUTHORIZING:
01581       case NETWORK_JOIN_STATUS_GETTING_COMPANY_INFO:
01582         progress = 10; // first two stages 10%
01583         break;
01584       case NETWORK_JOIN_STATUS_WAITING:
01585         SetDParam(0, _network_join_waiting);
01586         DrawStringCentered(125, 46, STR_NETWORK_CONNECTING_WAITING, TC_GREY);
01587         progress = 15; // third stage is 15%
01588         break;
01589       case NETWORK_JOIN_STATUS_DOWNLOADING:
01590         SetDParam(0, _network_join_kbytes);
01591         SetDParam(1, _network_join_kbytes_total);
01592         DrawStringCentered(125, 46, STR_NETWORK_CONNECTING_DOWNLOADING, TC_GREY);
01593         /* Fallthrough */
01594       default: /* Waiting is 15%, so the resting receivement of map is maximum 70% */
01595         progress = 15 + _network_join_kbytes * (100 - 15) / _network_join_kbytes_total;
01596     }
01597 
01598     /* Draw nice progress bar :) */
01599     DrawFrameRect(20, 18, (int)((w->width - 20) * progress / 100), 28, 10, FR_NONE);
01600   } break;
01601 
01602   case WE_CLICK:
01603     switch (e->we.click.widget) {
01604       case 2: /* Disconnect button */
01605         NetworkDisconnect();
01606         DeleteWindow(w);
01607         SwitchMode(SM_MENU);
01608         ShowNetworkGameWindow();
01609         break;
01610     }
01611     break;
01612 
01613     /* If the server asks for a password, we need to fill it in */
01614     case WE_ON_EDIT_TEXT_CANCEL:
01615       NetworkDisconnect();
01616       ShowNetworkGameWindow();
01617       break;
01618 
01619     case WE_ON_EDIT_TEXT:
01620       SEND_COMMAND(PACKET_CLIENT_PASSWORD)(pw_type, e->we.edittext.str);
01621       break;
01622   }
01623 }
01624 
01625 static const Widget _network_join_status_window_widget[] = {
01626 {    WWT_CAPTION,   RESIZE_NONE,    14,     0,   249,     0,    13, STR_NETWORK_CONNECTING, STR_018C_WINDOW_TITLE_DRAG_THIS},
01627 {      WWT_PANEL,   RESIZE_NONE,    14,     0,   249,    14,    84, 0x0,                    STR_NULL},
01628 { WWT_PUSHTXTBTN,   RESIZE_NONE,   BTC,    75,   175,    69,    80, STR_NETWORK_DISCONNECT, STR_NULL},
01629 {   WIDGETS_END},
01630 };
01631 
01632 static const WindowDesc _network_join_status_window_desc = {
01633   WDP_CENTER, WDP_CENTER, 250, 85, 250, 85,
01634   WC_NETWORK_STATUS_WINDOW, WC_NONE,
01635   WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_MODAL,
01636   _network_join_status_window_widget,
01637   NetworkJoinStatusWindowWndProc,
01638 };
01639 
01640 void ShowJoinStatusWindow()
01641 {
01642   Window *w;
01643   DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0);
01644   w = AllocateWindowDesc(&_network_join_status_window_desc);
01645   /* Parent the status window to the lobby */
01646   if (w != NULL) w->parent = FindWindowById(WC_NETWORK_WINDOW, 0);
01647 }
01648 
01649 static void SendChat(const char *buf, DestType type, int dest)
01650 {
01651   if (StrEmpty(buf)) return;
01652   if (!_network_server) {
01653     SEND_COMMAND(PACKET_CLIENT_CHAT)((NetworkAction)(NETWORK_ACTION_CHAT + type), type, dest, buf);
01654   } else {
01655     NetworkServer_HandleChat((NetworkAction)(NETWORK_ACTION_CHAT + type), type, dest, buf, NETWORK_SERVER_INDEX);
01656   }
01657 }
01658 
01665 static const char *ChatTabCompletionNextItem(uint *item)
01666 {
01667   static char chat_tab_temp_buffer[64];
01668 
01669   /* First, try clients */
01670   if (*item < MAX_CLIENT_INFO) {
01671     /* Skip inactive clients */
01672     while (_network_client_info[*item].client_index == NETWORK_EMPTY_INDEX && *item < MAX_CLIENT_INFO) (*item)++;
01673     if (*item < MAX_CLIENT_INFO) return _network_client_info[*item].client_name;
01674   }
01675 
01676   /* Then, try townnames */
01677   /* Not that the following assumes all town indices are adjacent, ie no
01678    * towns have been deleted. */
01679   if (*item <= (uint)MAX_CLIENT_INFO + GetMaxTownIndex()) {
01680     const Town *t;
01681 
01682     FOR_ALL_TOWNS_FROM(t, *item - MAX_CLIENT_INFO) {
01683       /* Get the town-name via the string-system */
01684       SetDParam(0, t->index);
01685       GetString(chat_tab_temp_buffer, STR_TOWN, lastof(chat_tab_temp_buffer));
01686       return &chat_tab_temp_buffer[0];
01687     }
01688   }
01689 
01690   return NULL;
01691 }
01692 
01698 static char *ChatTabCompletionFindText(char *buf)
01699 {
01700   char *p = strrchr(buf, ' ');
01701   if (p == NULL) return buf;
01702 
01703   *p = '\0';
01704   return p + 1;
01705 }
01706 
01710 static void ChatTabCompletion(Window *w)
01711 {
01712   static char _chat_tab_completion_buf[lengthof(_edit_str_buf)];
01713   Textbuf *tb = &WP(w, chatquerystr_d).text;
01714   uint len, tb_len;
01715   uint item;
01716   char *tb_buf, *pre_buf;
01717   const char *cur_name;
01718   bool second_scan = false;
01719 
01720   item = 0;
01721 
01722   /* Copy the buffer so we can modify it without damaging the real data */
01723   pre_buf = (_chat_tab_completion_active) ? strdup(_chat_tab_completion_buf) : strdup(tb->buf);
01724 
01725   tb_buf  = ChatTabCompletionFindText(pre_buf);
01726   tb_len  = strlen(tb_buf);
01727 
01728   while ((cur_name = ChatTabCompletionNextItem(&item)) != NULL) {
01729     item++;
01730 
01731     if (_chat_tab_completion_active) {
01732       /* We are pressing TAB again on the same name, is there an other name
01733        *  that starts with this? */
01734       if (!second_scan) {
01735         uint offset;
01736         uint length;
01737 
01738         /* If we are completing at the begin of the line, skip the ': ' we added */
01739         if (tb_buf == pre_buf) {
01740           offset = 0;
01741           length = tb->length - 2;
01742         } else {
01743           /* Else, find the place we are completing at */
01744           offset = strlen(pre_buf) + 1;
01745           length = tb->length - offset;
01746         }
01747 
01748         /* Compare if we have a match */
01749         if (strlen(cur_name) == length && strncmp(cur_name, tb->buf + offset, length) == 0) second_scan = true;
01750 
01751         continue;
01752       }
01753 
01754       /* Now any match we make on _chat_tab_completion_buf after this, is perfect */
01755     }
01756 
01757     len = strlen(cur_name);
01758     if (tb_len < len && strncasecmp(cur_name, tb_buf, tb_len) == 0) {
01759       /* Save the data it was before completion */
01760       if (!second_scan) snprintf(_chat_tab_completion_buf, lengthof(_chat_tab_completion_buf), "%s", tb->buf);
01761       _chat_tab_completion_active = true;
01762 
01763       /* Change to the found name. Add ': ' if we are at the start of the line (pretty) */
01764       if (pre_buf == tb_buf) {
01765         snprintf(tb->buf, lengthof(_edit_str_buf), "%s: ", cur_name);
01766       } else {
01767         snprintf(tb->buf, lengthof(_edit_str_buf), "%s %s", pre_buf, cur_name);
01768       }
01769 
01770       /* Update the textbuffer */
01771       UpdateTextBufferSize(&WP(w, chatquerystr_d).text);
01772 
01773       SetWindowDirty(w);
01774       free(pre_buf);
01775       return;
01776     }
01777   }
01778 
01779   if (second_scan) {
01780     /* We walked all posibilities, and the user presses tab again.. revert to original text */
01781     strcpy(tb->buf, _chat_tab_completion_buf);
01782     _chat_tab_completion_active = false;
01783 
01784     /* Update the textbuffer */
01785     UpdateTextBufferSize(&WP(w, chatquerystr_d).text);
01786 
01787     SetWindowDirty(w);
01788   }
01789   free(pre_buf);
01790 }
01791 
01792 /*
01793  * uses chatquerystr_d WP macro
01794  * uses chatquerystr_d->caption to store type of chat message (Private/Team/All)
01795  */
01796 static void ChatWindowWndProc(Window *w, WindowEvent *e)
01797 {
01798   switch (e->event) {
01799   case WE_CREATE:
01800     SendWindowMessage(WC_NEWS_WINDOW, 0, WE_CREATE, w->height, 0);
01801     SetBit(_no_scroll, SCROLL_CHAT); // do not scroll the game with the arrow-keys
01802     break;
01803 
01804   case WE_PAINT: {
01805     static const StringID chat_captions[] = {
01806       STR_NETWORK_CHAT_ALL_CAPTION,
01807       STR_NETWORK_CHAT_COMPANY_CAPTION,
01808       STR_NETWORK_CHAT_CLIENT_CAPTION
01809     };
01810     StringID msg;
01811 
01812     DrawWindowWidgets(w);
01813 
01814     assert(WP(w, chatquerystr_d).caption < lengthof(chat_captions));
01815     msg = chat_captions[WP(w, chatquerystr_d).caption];
01816     DrawStringRightAligned(w->widget[2].left - 2, w->widget[2].top + 1, msg, TC_BLACK);
01817     DrawEditBox(w, &WP(w, chatquerystr_d), 2);
01818   } break;
01819 
01820   case WE_CLICK:
01821     switch (e->we.click.widget) {
01822       case 3: { /* Send */
01823         DestType type = (DestType)WP(w, chatquerystr_d).caption;
01824         int dest = WP(w, chatquerystr_d).dest;
01825         SendChat(WP(w, chatquerystr_d).text.buf, type, dest);
01826       } /* FALLTHROUGH */
01827       case 0: /* Cancel */ DeleteWindow(w); break;
01828     }
01829     break;
01830 
01831   case WE_MOUSELOOP:
01832     HandleEditBox(w, &WP(w, chatquerystr_d), 2);
01833     break;
01834 
01835   case WE_KEYPRESS:
01836     if (e->we.keypress.keycode == WKC_TAB) {
01837       ChatTabCompletion(w);
01838     } else {
01839       _chat_tab_completion_active = false;
01840       switch (HandleEditBoxKey(w, &WP(w, chatquerystr_d), 2, e)) {
01841         case 1: { /* Return */
01842         DestType type = (DestType)WP(w, chatquerystr_d).caption;
01843         int dest = WP(w, chatquerystr_d).dest;
01844         SendChat(WP(w, chatquerystr_d).text.buf, type, dest);
01845       } /* FALLTHROUGH */
01846         case 2: /* Escape */ DeleteWindow(w); break;
01847       }
01848     }
01849     break;
01850 
01851   case WE_DESTROY:
01852     SendWindowMessage(WC_NEWS_WINDOW, 0, WE_DESTROY, 0, 0);
01853     ClrBit(_no_scroll, SCROLL_CHAT);
01854     break;
01855   }
01856 }
01857 
01858 static const Widget _chat_window_widgets[] = {
01859 {   WWT_CLOSEBOX, RESIZE_NONE,  14,   0,  10,  0, 13, STR_00C5,         STR_018B_CLOSE_WINDOW},
01860 {      WWT_PANEL, RESIZE_RIGHT, 14,  11, 319,  0, 13, 0x0,              STR_NULL}, // background
01861 {      WWT_PANEL, RESIZE_RIGHT, 14,  75, 257,  1, 12, 0x0,              STR_NULL}, // text box
01862 { WWT_PUSHTXTBTN, RESIZE_LR,    14, 258, 319,  1, 12, STR_NETWORK_SEND, STR_NULL}, // send button
01863 {   WIDGETS_END},
01864 };
01865 
01866 static const WindowDesc _chat_window_desc = {
01867   WDP_CENTER, -26, 320, 14, 640, 14, // x, y, width, height
01868   WC_SEND_NETWORK_MSG, WC_NONE,
01869   WDF_STD_TOOLTIPS | WDF_DEF_WIDGET,
01870   _chat_window_widgets,
01871   ChatWindowWndProc
01872 };
01873 
01874 void ShowNetworkChatQueryWindow(DestType type, int dest)
01875 {
01876   Window *w;
01877 
01878   DeleteWindowById(WC_SEND_NETWORK_MSG, 0);
01879 
01880   _edit_str_buf[0] = '\0';
01881   _chat_tab_completion_active = false;
01882 
01883   w = AllocateWindowDesc(&_chat_window_desc);
01884 
01885   w->LowerWidget(2);
01886   WP(w, chatquerystr_d).caption = type; // Misuse of caption
01887   WP(w, chatquerystr_d).dest    = dest;
01888   WP(w, chatquerystr_d).afilter = CS_ALPHANUMERAL;
01889   InitializeTextBuffer(&WP(w, chatquerystr_d).text, _edit_str_buf, lengthof(_edit_str_buf), 0);
01890 }
01891 
01893 enum NetworkCompanyPasswordWindowWidgets {
01894   NCPWW_CLOSE,                    
01895   NCPWW_CAPTION,                  
01896   NCPWW_BACKGROUND,               
01897   NCPWW_LABEL,                    
01898   NCPWW_PASSWORD,                 
01899   NCPWW_SAVE_AS_DEFAULT_PASSWORD, 
01900   NCPWW_CANCEL,                   
01901   NCPWW_OK,                       
01902 };
01903 
01904 static void NetworkCompanyPasswordWindowWndProc(Window *w, WindowEvent *e)
01905 {
01906   switch (e->event) {
01907     case WE_PAINT:
01908       DrawWindowWidgets(w);
01909       DrawEditBox(w, &WP(w, chatquerystr_d), 4);
01910       break;
01911 
01912     case WE_CLICK:
01913       switch (e->we.click.widget) {
01914         case NCPWW_OK: {
01915           if (w->IsWidgetLowered(NCPWW_SAVE_AS_DEFAULT_PASSWORD)) {
01916             snprintf(_network_default_company_pass, lengthof(_network_default_company_pass), "%s", _edit_str_buf);
01917           }
01918 
01919           /* empty password is a '*' because of console argument */
01920           if (StrEmpty(_edit_str_buf)) snprintf(_edit_str_buf, lengthof(_edit_str_buf), "*");
01921           char *password = _edit_str_buf;
01922           NetworkChangeCompanyPassword(1, &password);
01923         }
01924 
01925         /* FALL THROUGH */
01926         case NCPWW_CANCEL:
01927           DeleteWindow(w);
01928           break;
01929 
01930         case NCPWW_SAVE_AS_DEFAULT_PASSWORD:
01931           w->ToggleWidgetLoweredState(NCPWW_SAVE_AS_DEFAULT_PASSWORD);
01932           SetWindowDirty(w);
01933           break;
01934       }
01935       break;
01936 
01937     case WE_MOUSELOOP:
01938       HandleEditBox(w, &WP(w, chatquerystr_d), 4);
01939       break;
01940 
01941     case WE_KEYPRESS:
01942       switch (HandleEditBoxKey(w, &WP(w, chatquerystr_d), 4, e)) {
01943         case 1: // Return
01944           e->event = WE_CLICK;
01945           e->we.click.widget = NCPWW_OK;
01946           NetworkCompanyPasswordWindowWndProc(w, e);
01947           break;
01948 
01949         case 2: // Escape
01950           DeleteWindow(w);
01951           break;
01952       }
01953       break;
01954   }
01955 }
01956 
01957 static const Widget _ncp_window_widgets[] = {
01958 {   WWT_CLOSEBOX, RESIZE_NONE, 14,   0,  10,  0, 13, STR_00C5,                          STR_018B_CLOSE_WINDOW},
01959 {    WWT_CAPTION, RESIZE_NONE, 14,  11, 299,  0, 13, STR_COMPANY_PASSWORD_CAPTION,      STR_018C_WINDOW_TITLE_DRAG_THIS},
01960 {      WWT_PANEL, RESIZE_NONE, 14,   0, 299, 14, 50, 0x0,                               STR_NULL},
01961 {       WWT_TEXT, RESIZE_NONE, 14,   5, 100, 19, 30, STR_COMPANY_PASSWORD,              STR_NULL},
01962 {      WWT_PANEL, RESIZE_NONE, 14, 101, 294, 19, 30, 0x0,                               STR_NULL},
01963 {    WWT_TEXTBTN, RESIZE_NONE, 14, 101, 294, 35, 46, STR_MAKE_DEFAULT_COMPANY_PASSWORD, STR_MAKE_DEFAULT_COMPANY_PASSWORD_TIP},
01964 { WWT_PUSHTXTBTN, RESIZE_NONE, 14,   0, 149, 51, 62, STR_012E_CANCEL,                   STR_COMPANY_PASSWORD_CANCEL},
01965 { WWT_PUSHTXTBTN, RESIZE_NONE, 14, 150, 299, 51, 62, STR_012F_OK,                       STR_COMPANY_PASSWORD_OK},
01966 {   WIDGETS_END},
01967 };
01968 
01969 static const WindowDesc _ncp_window_desc = {
01970   WDP_AUTO, WDP_AUTO, 300, 63, 300, 63,
01971   WC_COMPANY_PASSWORD_WINDOW, WC_NONE,
01972   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON,
01973   _ncp_window_widgets,
01974   NetworkCompanyPasswordWindowWndProc
01975 };
01976 
01977 void ShowNetworkCompanyPasswordWindow()
01978 {
01979   DeleteWindowById(WC_COMPANY_PASSWORD_WINDOW, 0);
01980 
01981   _edit_str_buf[0] = '\0';
01982   Window *w = AllocateWindowDesc(&_ncp_window_desc);
01983   WP(w, chatquerystr_d).afilter = CS_ALPHANUMERAL;
01984   InitializeTextBuffer(&WP(w, chatquerystr_d).text, _edit_str_buf, min(lengthof(_network_default_company_pass), lengthof(_edit_str_buf)), 0);
01985 }
01986 
01987 #endif /* ENABLE_NETWORK */

Generated on Mon Sep 22 20:34:16 2008 for openttd by  doxygen 1.5.6