tron@2186: /* $Id$ */ tron@2186: Darkvater@4826: #ifdef ENABLE_NETWORK rubidium@5720: #include "../stdafx.h" rubidium@5720: #include "../openttd.h" rubidium@8610: #include "../strings_func.h" truelight@543: #include "network.h" rubidium@8636: #include "../date_func.h" rubidium@5720: #include "../fios.h" truelight@543: #include "network_data.h" tron@4013: #include "network_client.h" Darkvater@4826: #include "network_gui.h" dominik@738: #include "network_gamelist.h" rubidium@5720: #include "../gui.h" rubidium@8603: #include "../window_gui.h" rubidium@8603: #include "../textbuf_gui.h" rubidium@5720: #include "../variables.h" truelight@543: #include "network_server.h" truelight@543: #include "network_udp.h" rubidium@5720: #include "../town.h" rubidium@5720: #include "../newgrf.h" rubidium@8627: #include "../functions.h" rubidium@8627: #include "../window_func.h" rubidium@8709: #include "../core/alloc_func.hpp" rubidium@8710: #include "../string_func.h" rubidium@8720: #include "../gfx_func.h" rubidium@8750: #include "../player_func.h" rubidium@8766: #include "../settings_type.h" peter1138@8780: #include "../widgets/dropdown_func.h" truelight@0: rubidium@8760: #include "table/strings.h" rubidium@8760: #include "../table/sprites.h" rubidium@8760: truelight@0: #define BGC 5 truelight@0: #define BTC 15 Darkvater@2887: rubidium@8603: struct chatquerystr_d : public querystr_d { rubidium@8603: int dest; rubidium@8603: }; rubidium@8603: assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(chatquerystr_d)); rubidium@8603: rubidium@6574: struct network_d { Darkvater@3692: PlayerID company; // select company in network lobby Darkvater@2888: byte field; // select text-field in start-server and game-listing rubidium@7937: byte widget_id; ///< The widget that has the pop-up input menu Darkvater@2888: NetworkGameList *server; // selected server in lobby and game-listing Darkvater@2888: FiosItem *map; // selected map in start-server rubidium@6574: }; Darkvater@2887: assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(network_d)); Darkvater@2887: rubidium@6574: struct network_ql_d { Darkvater@2888: network_d n; // see above; general stuff Darkvater@2888: querystr_d q; // text-input in start-server and game-listing Darkvater@2888: NetworkGameList **sort_list; // list of games (sorted) Darkvater@2888: list_d l; // accompanying list-administration rubidium@6574: }; Darkvater@2888: assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(network_ql_d)); Darkvater@2888: Darkvater@2888: /* Global to remember sorting after window has been closed */ Darkvater@4542: static Listing _ng_sorting; Darkvater@2887: Darkvater@4957: static char _edit_str_buf[150]; truelight@4315: static bool _chat_tab_completion_active; tron@3470: rubidium@6573: static void ShowNetworkStartServerWindow(); Darkvater@2887: static void ShowNetworkLobbyWindow(NetworkGameList *ngl); truelight@4300: extern void SwitchMode(int new_mode); truelight@0: truelight@0: static const StringID _connection_types_dropdown[] = { truelight@675: STR_NETWORK_LAN_INTERNET, truelight@675: STR_NETWORK_INTERNET_ADVERTISE, truelight@0: INVALID_STRING_ID truelight@0: }; truelight@0: truelight@764: static const StringID _lan_internet_types_dropdown[] = { truelight@764: STR_NETWORK_LAN, truelight@764: STR_NETWORK_INTERNET, truelight@764: INVALID_STRING_ID truelight@764: }; truelight@764: glx@7292: static StringID _language_dropdown[NETLANG_COUNT + 1] = {STR_NULL}; glx@7289: rubidium@7817: void SortNetworkLanguages() rubidium@7817: { glx@7292: /* Init the strings */ glx@7292: if (_language_dropdown[0] == STR_NULL) { glx@7292: for (int i = 0; i < NETLANG_COUNT; i++) _language_dropdown[i] = STR_NETWORK_LANG_ANY + i; glx@7292: _language_dropdown[NETLANG_COUNT] = INVALID_STRING_ID; glx@7292: } glx@7289: glx@7292: /* Sort the strings (we don't move 'any' and the 'invalid' one) */ glx@7292: qsort(&_language_dropdown[1], NETLANG_COUNT - 1, sizeof(StringID), &StringIDSorter); glx@7289: } glx@7276: darkvater@211: enum { Darkvater@2881: NET_PRC__OFFSET_TOP_WIDGET = 54, Darkvater@2887: NET_PRC__OFFSET_TOP_WIDGET_COMPANY = 52, Darkvater@2881: NET_PRC__SIZE_OF_ROW = 14, darkvater@211: }; darkvater@211: Darkvater@5035: /** Update the network new window because a new server is Darkvater@5035: * found on the network. Darkvater@5035: * @param unselect unselect the currently selected item */ dominik@897: void UpdateNetworkGameWindow(bool unselect) dominik@897: { Darkvater@5035: SendWindowMessage(WC_NETWORK_WINDOW, 0, unselect, 0, 0); dominik@897: } dominik@897: Darkvater@2888: static bool _internal_sort_order; // Used for Qsort order-flipping Darkvater@2888: typedef int CDECL NGameNameSortFunction(const void*, const void*); Darkvater@2888: Darkvater@2888: /** Qsort function to sort by name. */ Darkvater@2888: static int CDECL NGameNameSorter(const void *a, const void *b) Darkvater@2888: { Darkvater@2888: const NetworkGameList *cmp1 = *(const NetworkGameList**)a; Darkvater@2888: const NetworkGameList *cmp2 = *(const NetworkGameList**)b; Darkvater@2889: int r = strcasecmp(cmp1->info.server_name, cmp2->info.server_name); Darkvater@2888: Darkvater@4880: return _internal_sort_order ? -r : r; Darkvater@2888: } Darkvater@2888: Darkvater@2888: /** Qsort function to sort by the amount of clients online on a Darkvater@2888: * server. If the two servers have the same amount, the one with the Darkvater@2888: * higher maximum is preferred. */ Darkvater@2888: static int CDECL NGameClientSorter(const void *a, const void *b) Darkvater@2888: { Darkvater@2888: const NetworkGameList *cmp1 = *(const NetworkGameList**)a; Darkvater@2888: const NetworkGameList *cmp2 = *(const NetworkGameList**)b; Darkvater@2888: /* Reverse as per default we are interested in most-clients first */ Darkvater@4195: int r = cmp1->info.clients_on - cmp2->info.clients_on; Darkvater@2888: Darkvater@2888: if (r == 0) r = cmp1->info.clients_max - cmp2->info.clients_max; Darkvater@2922: if (r == 0) r = strcasecmp(cmp1->info.server_name, cmp2->info.server_name); Darkvater@2888: Darkvater@4880: return _internal_sort_order ? -r : r; Darkvater@2888: } Darkvater@2888: Darkvater@2888: /** Qsort function to sort by joinability. If both servers are the Darkvater@2888: * same, prefer the non-passworded server first. */ Darkvater@2888: static int CDECL NGameAllowedSorter(const void *a, const void *b) Darkvater@2888: { Darkvater@2888: const NetworkGameList *cmp1 = *(const NetworkGameList**)a; Darkvater@2888: const NetworkGameList *cmp2 = *(const NetworkGameList**)b; Darkvater@2888: rubidium@5909: /* The servers we do not know anything about (the ones that did not reply) should be at the bottom) */ rubidium@5909: int r = StrEmpty(cmp1->info.server_revision) - StrEmpty(cmp2->info.server_revision); rubidium@5909: rubidium@5909: /* Reverse default as we are interested in version-compatible clients first */ rubidium@5909: if (r == 0) r = cmp2->info.version_compatible - cmp1->info.version_compatible; rubidium@5909: /* The version-compatible ones are then sorted with NewGRF compatible first, incompatible last */ rubidium@5909: if (r == 0) r = cmp2->info.compatible - cmp1->info.compatible; rubidium@5909: /* Passworded servers should be below unpassworded servers */ Darkvater@2888: if (r == 0) r = cmp1->info.use_password - cmp2->info.use_password; rubidium@5909: /* Finally sort on the name of the server */ Darkvater@2922: if (r == 0) r = strcasecmp(cmp1->info.server_name, cmp2->info.server_name); Darkvater@2888: Darkvater@4880: return _internal_sort_order ? -r : r; Darkvater@2888: } Darkvater@2888: Darkvater@2888: /** (Re)build the network game list as its amount has changed because Darkvater@2888: * an item has been added or deleted for example Darkvater@2888: * @param ngl list_d struct that contains all necessary information for sorting */ Darkvater@2888: static void BuildNetworkGameList(network_ql_d *nqld) Darkvater@2888: { Darkvater@2888: NetworkGameList *ngl_temp; Darkvater@2888: uint n = 0; Darkvater@2888: Darkvater@2888: if (!(nqld->l.flags & VL_REBUILD)) return; Darkvater@2888: Darkvater@2888: /* Count the number of games in the list */ Darkvater@2888: for (ngl_temp = _network_game_list; ngl_temp != NULL; ngl_temp = ngl_temp->next) n++; Darkvater@2888: if (n == 0) return; Darkvater@2888: Darkvater@2888: /* Create temporary array of games to use for listing */ Darkvater@2888: free(nqld->sort_list); KUDr@5860: nqld->sort_list = MallocT(n); Darkvater@2888: nqld->l.list_length = n; Darkvater@2888: Darkvater@2888: for (n = 0, ngl_temp = _network_game_list; ngl_temp != NULL; ngl_temp = ngl_temp->next) { Darkvater@2888: nqld->sort_list[n++] = ngl_temp; Darkvater@2888: } Darkvater@2888: Darkvater@2888: /* Force resort */ Darkvater@2888: nqld->l.flags &= ~VL_REBUILD; Darkvater@2888: nqld->l.flags |= VL_RESORT; Darkvater@2888: } Darkvater@2888: Darkvater@2888: static void SortNetworkGameList(network_ql_d *nqld) Darkvater@2888: { belugas@4171: static NGameNameSortFunction * const ngame_sorter[] = { Darkvater@3860: &NGameNameSorter, Darkvater@3860: &NGameClientSorter, Darkvater@3860: &NGameAllowedSorter Darkvater@3860: }; Darkvater@3860: Darkvater@2888: NetworkGameList *item; Darkvater@2888: uint i; Darkvater@2888: Darkvater@2888: if (!(nqld->l.flags & VL_RESORT)) return; Darkvater@3860: if (nqld->l.list_length == 0) return; Darkvater@2888: Darkvater@4880: _internal_sort_order = !!(nqld->l.flags & VL_DESC); Darkvater@3860: qsort(nqld->sort_list, nqld->l.list_length, sizeof(nqld->sort_list[0]), ngame_sorter[nqld->l.sort_type]); Darkvater@2888: Darkvater@2888: /* After sorting ngl->sort_list contains the sorted items. Put these back Darkvater@2888: * into the original list. Basically nothing has changed, we are only Darkvater@2888: * shuffling the ->next pointers */ Darkvater@2888: _network_game_list = nqld->sort_list[0]; Darkvater@2888: for (item = _network_game_list, i = 1; i != nqld->l.list_length; i++) { Darkvater@2888: item->next = nqld->sort_list[i]; Darkvater@2888: item = item->next; Darkvater@2888: } Darkvater@2888: item->next = NULL; Darkvater@2888: Darkvater@2888: nqld->l.flags &= ~VL_RESORT; Darkvater@2888: } Darkvater@2888: smatz@8456: /** Enum for NetworkGameWindow, referring to _network_game_window_widgets */ smatz@8456: enum NetworkGameWindowWidgets { smatz@8456: NGWW_CLOSE = 0, ///< Close 'X' button peter1138@8839: NGWW_CONN_BTN = 4, ///< 'Connection' droplist button peter1138@8839: NGWW_PLAYER = 5, ///< Panel with editbox to set player name smatz@8456: peter1138@8839: NGWW_NAME = 6, ///< 'Name' button smatz@8456: NGWW_CLIENTS, ///< 'Clients' button smatz@8456: NGWW_INFO, ///< Third button in the game list panel smatz@8456: peter1138@8839: NGWW_MATRIX = 9, ///< Panel with list of games smatz@8456: peter1138@8839: NGWW_DETAILS = 11, ///< Panel with game details peter1138@8839: NGWW_JOIN = 12, ///< 'Join game' button peter1138@8839: NGWW_REFRESH = 13, ///< 'Refresh server' button peter1138@8839: NGWW_NEWGRF = 14, ///< 'NewGRF Settings' button smatz@8456: peter1138@8839: NGWW_FIND = 15, ///< 'Find server' button smatz@8456: NGWW_ADD, ///< 'Add server' button smatz@8456: NGWW_START, ///< 'Start server' button smatz@8456: NGWW_CANCEL, ///< 'Cancel' button smatz@8456: }; smatz@8456: smatz@8456: /** smatz@8456: * Handler of actions done in the NetworkStartServer window smatz@8456: * smatz@8456: * @param w pointer to the Window structure smatz@8456: * @param e pointer to window event smatz@8456: * @note Uses network_ql_d (network_d, querystr_d and list_d) WP macro smatz@8456: * @see struct _network_game_window_widgets smatz@8456: * @see enum NetworkGameWindowWidgets smatz@8456: */ smatz@8456: truelight@0: static void NetworkGameWindowWndProc(Window *w, WindowEvent *e) truelight@0: { Darkvater@2888: network_d *nd = &WP(w, network_ql_d).n; Darkvater@2888: list_d *ld = &WP(w, network_ql_d).l; Darkvater@2887: Darkvater@1653: switch (e->event) { smatz@8456: case WE_CREATE: // Focus input box rubidium@7839: w->vscroll.cap = 13; rubidium@7839: w->resize.step_height = NET_PRC__SIZE_OF_ROW; rubidium@7839: smatz@8456: nd->field = NGWW_PLAYER; Darkvater@2887: nd->server = NULL; Darkvater@2888: Darkvater@2888: WP(w, network_ql_d).sort_list = NULL; rubidium@5838: ld->flags = VL_REBUILD | (_ng_sorting.order ? VL_DESC : VL_NONE); Darkvater@2888: ld->sort_type = _ng_sorting.criteria; Darkvater@1653: break; tron@2630: truelight@0: case WE_PAINT: { Darkvater@2887: const NetworkGameList *sel = nd->server; peter1138@8845: const SortButtonState arrow = (ld->flags & VL_DESC) ? SBS_DOWN : SBS_UP; Darkvater@2888: Darkvater@2888: if (ld->flags & VL_REBUILD) { Darkvater@2888: BuildNetworkGameList(&WP(w, network_ql_d)); Darkvater@2888: SetVScrollCount(w, ld->list_length); Darkvater@2888: } Darkvater@2888: if (ld->flags & VL_RESORT) SortNetworkGameList(&WP(w, network_ql_d)); ludde@2071: smatz@8456: /* 'Refresh' button invisible if no server selected */ rubidium@8493: w->SetWidgetDisabledState(NGWW_REFRESH, sel == NULL); smatz@8456: /* 'Join' button disabling conditions */ rubidium@8493: w->SetWidgetDisabledState(NGWW_JOIN, sel == NULL || // no Selected Server belugas@4709: !sel->online || // Server offline belugas@4709: sel->info.clients_on >= sel->info.clients_max || // Server full belugas@4709: !sel->info.compatible); // Revision mismatch truelight@193: smatz@8456: /* 'NewGRF Settings' button invisible if no NewGRF is used */ rubidium@8493: w->SetWidgetHiddenState(NGWW_NEWGRF, sel == NULL || rubidium@5339: !sel->online || rubidium@5339: sel->info.grfconfig == NULL); rubidium@5339: tron@534: SetDParam(0, 0x00); rubidium@7935: SetDParam(1, _lan_internet_types_dropdown[_network_lan_internet]); truelight@0: DrawWindowWidgets(w); truelight@193: smatz@8456: /* Edit box to set player name */ smatz@8456: DrawEditBox(w, &WP(w, network_ql_d).q, NGWW_PLAYER); truelight@193: smatz@8456: DrawString(w->widget[NGWW_PLAYER].left - 100, 23, STR_NETWORK_PLAYER_NAME, TC_GOLD); truelight@0: Darkvater@2888: /* Sort based on widgets: name, clients, compatibility */ Darkvater@2888: switch (ld->sort_type) { peter1138@8845: case NGWW_NAME - NGWW_NAME: DrawSortButtonState(w, NGWW_NAME, arrow); break; peter1138@8845: case NGWW_CLIENTS - NGWW_NAME: DrawSortButtonState(w, NGWW_CLIENTS, arrow); break; peter1138@8845: case NGWW_INFO - NGWW_NAME: DrawSortButtonState(w, NGWW_INFO, arrow); break; Darkvater@2888: } Darkvater@2888: darkvater@211: { // draw list of games darkvater@211: uint16 y = NET_PRC__OFFSET_TOP_WIDGET + 3; darkvater@211: int32 n = 0; truelight@809: int32 pos = w->vscroll.pos; smatz@8456: uint max_name_width = w->widget[NGWW_NAME].right - w->widget[NGWW_NAME].left - 5; truelight@543: const NetworkGameList *cur_item = _network_game_list; truelight@809: truelight@809: while (pos > 0 && cur_item != NULL) { truelight@809: pos--; bjarni@808: cur_item = cur_item->next; bjarni@808: } truelight@809: darkvater@211: while (cur_item != NULL) { smatz@8456: /* show highlighted item with a different colour */ smatz@8456: if (cur_item == sel) GfxFillRect(w->widget[NGWW_NAME].left + 1, y - 2, w->widget[NGWW_INFO].right - 1, y + 9, 10); darkvater@211: Darkvater@2100: SetDParamStr(0, cur_item->info.server_name); smatz@8456: DrawStringTruncated(w->widget[NGWW_NAME].left + 5, y, STR_02BD, TC_BLACK, max_name_width); darkvater@211: truelight@543: SetDParam(0, cur_item->info.clients_on); truelight@543: SetDParam(1, cur_item->info.clients_max); Darkvater@2879: SetDParam(2, cur_item->info.companies_on); Darkvater@2879: SetDParam(3, cur_item->info.companies_max); smatz@8456: DrawStringCentered(w->widget[NGWW_CLIENTS].left + 39, y, STR_NETWORK_GENERAL_ONLINE, TC_GOLD); truelight@543: smatz@8456: /* only draw icons if the server is online */ truelight@591: if (cur_item->online) { smatz@8456: /* draw a lock if the server is password protected */ smatz@8456: if (cur_item->info.use_password) DrawSprite(SPR_LOCK, PAL_NONE, w->widget[NGWW_INFO].left + 5, y - 1); truelight@591: smatz@8456: /* draw red or green icon, depending on compatibility with server */ smatz@8456: 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); truelight@591: smatz@8456: /* draw flag according to server language */ smatz@8456: DrawSprite(SPR_FLAGS_BASE + cur_item->info.server_lang, PAL_NONE, w->widget[NGWW_INFO].left + 25, y); dominik@579: } truelight@591: truelight@591: cur_item = cur_item->next; truelight@591: y += NET_PRC__SIZE_OF_ROW; tron@2549: if (++n == w->vscroll.cap) break; // max number of games in the window darkvater@211: } darkvater@211: } truelight@543: Darkvater@2881: /* Draw the right menu */ smatz@8456: GfxFillRect(w->widget[NGWW_DETAILS].left + 1, 43, w->widget[NGWW_DETAILS].right - 1, 92, 157); ludde@2071: if (sel == NULL) { smatz@8456: DrawStringCentered(w->widget[NGWW_DETAILS].left + 115, 58, STR_NETWORK_GAME_INFO, TC_FROMSTRING); ludde@2071: } else if (!sel->online) { ludde@2071: SetDParamStr(0, sel->info.server_name); smatz@8456: DrawStringCentered(w->widget[NGWW_DETAILS].left + 115, 68, STR_ORANGE, TC_FROMSTRING); // game name truelight@543: smatz@8456: DrawStringCentered(w->widget[NGWW_DETAILS].left + 115, 132, STR_NETWORK_SERVER_OFFLINE, TC_FROMSTRING); // server offline truelight@543: } else { // show game info Darkvater@2881: uint16 y = 100; smatz@8456: const uint16 x = w->widget[NGWW_DETAILS].left + 5; truelight@543: smatz@8456: DrawStringCentered(w->widget[NGWW_DETAILS].left + 115, 48, STR_NETWORK_GAME_INFO, TC_FROMSTRING); truelight@543: ludde@2055: ludde@2071: SetDParamStr(0, sel->info.server_name); smatz@8456: DrawStringCenteredTruncated(w->widget[NGWW_DETAILS].left, w->widget[NGWW_DETAILS].right, 62, STR_ORANGE, TC_BLACK); // game name truelight@543: ludde@2071: SetDParamStr(0, sel->info.map_name); smatz@8456: DrawStringCenteredTruncated(w->widget[NGWW_DETAILS].left, w->widget[NGWW_DETAILS].right, 74, STR_02BD, TC_BLACK); // map name truelight@543: ludde@2071: SetDParam(0, sel->info.clients_on); ludde@2071: SetDParam(1, sel->info.clients_max); Darkvater@2879: SetDParam(2, sel->info.companies_on); Darkvater@2879: SetDParam(3, sel->info.companies_max); belugas@8320: DrawString(x, y, STR_NETWORK_CLIENTS, TC_GOLD); tron@2639: y += 10; truelight@543: glx@7292: SetDParam(0, STR_NETWORK_LANG_ANY + sel->info.server_lang); belugas@8320: DrawString(x, y, STR_NETWORK_LANGUAGE, TC_GOLD); // server language tron@2639: y += 10; truelight@543: Darkvater@2775: SetDParam(0, STR_TEMPERATE_LANDSCAPE + sel->info.map_set); belugas@8320: DrawString(x, y, STR_NETWORK_TILESET, TC_GOLD); // tileset tron@2639: y += 10; truelight@543: ludde@2071: SetDParam(0, sel->info.map_width); ludde@2071: SetDParam(1, sel->info.map_height); belugas@8320: DrawString(x, y, STR_NETWORK_MAP_SIZE, TC_GOLD); // map size tron@2639: y += 10; truelight@543: ludde@2071: SetDParamStr(0, sel->info.server_revision); belugas@8320: DrawString(x, y, STR_NETWORK_SERVER_VERSION, TC_GOLD); // server version tron@2639: y += 10; truelight@543: ludde@2071: SetDParamStr(0, sel->info.hostname); ludde@2071: SetDParam(1, sel->port); belugas@8320: DrawString(x, y, STR_NETWORK_SERVER_ADDRESS, TC_GOLD); // server address tron@2639: y += 10; truelight@543: ludde@2071: SetDParam(0, sel->info.start_date); belugas@8320: DrawString(x, y, STR_NETWORK_START_DATE, TC_GOLD); // start date tron@2639: y += 10; truelight@543: ludde@2071: SetDParam(0, sel->info.game_date); belugas@8320: DrawString(x, y, STR_NETWORK_CURRENT_DATE, TC_GOLD); // current date tron@2639: y += 10; truelight@543: tron@2639: y += 2; truelight@622: Darkvater@2881: if (!sel->info.compatible) { smatz@8456: DrawStringCentered(w->widget[NGWW_DETAILS].left + 115, y, sel->info.version_compatible ? STR_NETWORK_GRF_MISMATCH : STR_NETWORK_VERSION_MISMATCH, TC_FROMSTRING); // server mismatch ludde@2071: } else if (sel->info.clients_on == sel->info.clients_max) { smatz@8456: /* Show: server full, when clients_on == clients_max */ smatz@8456: DrawStringCentered(w->widget[NGWW_DETAILS].left + 115, y, STR_NETWORK_SERVER_FULL, TC_FROMSTRING); // server full tron@2639: } else if (sel->info.use_password) { smatz@8456: DrawStringCentered(w->widget[NGWW_DETAILS].left + 115, y, STR_NETWORK_PASSWORD, TC_FROMSTRING); // password warning tron@2639: } darkvater@659: tron@2639: y += 10; truelight@543: } rubidium@6988: } break; truelight@0: truelight@0: case WE_CLICK: belugas@4634: nd->field = e->we.click.widget; belugas@4634: switch (e->we.click.widget) { smatz@8456: case NGWW_CANCEL: // Cancel button truelight@0: DeleteWindowById(WC_NETWORK_WINDOW, 0); truelight@0: break; peter1138@8839: case NGWW_CONN_BTN: // 'Connection' droplist smatz@8456: ShowDropDownMenu(w, _lan_internet_types_dropdown, _network_lan_internet, NGWW_CONN_BTN, 0, 0); // do it for widget NSSW_CONN_BTN truelight@764: break; smatz@8456: case NGWW_NAME: // Sort by name smatz@8456: case NGWW_CLIENTS: // Sort by connected clients smatz@8456: case NGWW_INFO: // Connectivity (green dot) smatz@8456: if (ld->sort_type == e->we.click.widget - NGWW_NAME) ld->flags ^= VL_DESC; Darkvater@2888: ld->flags |= VL_RESORT; smatz@8456: ld->sort_type = e->we.click.widget - NGWW_NAME; Darkvater@2888: Darkvater@2888: _ng_sorting.order = !!(ld->flags & VL_DESC); Darkvater@2888: _ng_sorting.criteria = ld->sort_type; Darkvater@2888: SetWindowDirty(w); Darkvater@2888: break; smatz@8456: case NGWW_MATRIX: { // Matrix to show networkgames Darkvater@2887: NetworkGameList *cur_item; belugas@4634: uint32 id_v = (e->we.click.pt.y - NET_PRC__OFFSET_TOP_WIDGET) / NET_PRC__SIZE_OF_ROW; darkvater@211: tron@2549: if (id_v >= w->vscroll.cap) return; // click out of bounds darkvater@211: id_v += w->vscroll.pos; darkvater@211: Darkvater@2887: cur_item = _network_game_list; Darkvater@2887: for (; id_v > 0 && cur_item != NULL; id_v--) cur_item = cur_item->next; darkvater@211: Darkvater@2887: nd->server = cur_item; truelight@543: SetWindowDirty(w); darkvater@211: } break; smatz@8456: case NGWW_FIND: // Find server automatically truelight@764: switch (_network_lan_internet) { truelight@764: case 0: NetworkUDPSearchGame(); break; truelight@764: case 1: NetworkUDPQueryMasterServer(); break; truelight@764: } darkvater@211: break; smatz@8456: case NGWW_ADD: { // Add a server smatz@8456: ShowQueryString( ludde@2055: BindCString(_network_default_ip), truelight@543: STR_NETWORK_ENTER_IP, truelight@543: 31 | 0x1000, // maximum number of characters OR truelight@543: 250, // characters up to this width pixels, whichever is satisfied first Darkvater@5682: w, CS_ALPHANUMERAL); truelight@543: } break; smatz@8456: case NGWW_START: // Start server truelight@543: ShowNetworkStartServerWindow(); truelight@543: break; smatz@8456: case NGWW_JOIN: // Join Game Darkvater@2887: if (nd->server != NULL) { Darkvater@2887: snprintf(_network_last_host, sizeof(_network_last_host), "%s", inet_ntoa(*(struct in_addr *)&nd->server->ip)); Darkvater@2887: _network_last_port = nd->server->port; Darkvater@2887: ShowNetworkLobbyWindow(nd->server); truelight@543: } truelight@543: break; smatz@8456: case NGWW_REFRESH: // Refresh Darkvater@2888: if (nd->server != NULL) rubidium@6167: NetworkUDPQueryServer(nd->server->info.hostname, nd->server->port); truelight@543: break; smatz@8456: case NGWW_NEWGRF: // NewGRF Settings Darkvater@5352: if (nd->server != NULL) ShowNewGRFSettings(false, false, false, &nd->server->info.grfconfig); rubidium@5339: break; truelight@543: rubidium@6988: } break; darkvater@172: smatz@8456: case WE_DROPDOWN_SELECT: // we have selected a dropdown item in the list belugas@4634: switch (e->we.dropdown.button) { smatz@8456: case NGWW_CONN_BTN: belugas@4634: _network_lan_internet = e->we.dropdown.index; truelight@764: break; smatz@8456: default: smatz@8456: NOT_REACHED(); truelight@764: } truelight@764: truelight@764: SetWindowDirty(w); truelight@764: break; truelight@764: truelight@0: case WE_MOUSELOOP: smatz@8456: if (nd->field == NGWW_PLAYER) HandleEditBox(w, &WP(w, network_ql_d).q, NGWW_PLAYER); truelight@0: break; truelight@0: Darkvater@2888: case WE_MESSAGE: Darkvater@5035: if (e->we.message.msg != 0) nd->server = NULL; Darkvater@2888: ld->flags |= VL_REBUILD; Darkvater@2881: SetWindowDirty(w); Darkvater@2888: break; Darkvater@2881: truelight@0: case WE_KEYPRESS: smatz@8456: if (nd->field != NGWW_PLAYER) { Darkvater@2887: if (nd->server != NULL) { smatz@8456: if (e->we.keypress.keycode == WKC_DELETE) { // Press 'delete' to remove servers Darkvater@2887: NetworkGameListRemoveItem(nd->server); dominik@738: NetworkRebuildHostList(); Darkvater@2887: nd->server = NULL; dominik@738: } dominik@738: } truelight@741: break; truelight@741: } truelight@741: smatz@8456: if (HandleEditBoxKey(w, &WP(w, network_ql_d).q, NGWW_PLAYER, e) == 1) break; // enter pressed truelight@543: smatz@8456: /* The name is only allowed when it starts with a letter! */ tron@2639: if (_edit_str_buf[0] != '\0' && _edit_str_buf[0] != ' ') { truelight@543: ttd_strlcpy(_network_player_name, _edit_str_buf, lengthof(_network_player_name)); tron@2639: } else { truelight@543: ttd_strlcpy(_network_player_name, "Player", lengthof(_network_player_name)); tron@2639: } truelight@543: truelight@0: break; truelight@0: Darkvater@2881: case WE_ON_EDIT_TEXT: belugas@4634: NetworkAddServer(e->we.edittext.str); dominik@738: NetworkRebuildHostList(); Darkvater@2881: break; Darkvater@2888: rubidium@7839: case WE_RESIZE: { rubidium@7839: w->vscroll.cap += e->we.sizing.diff.y / (int)w->resize.step_height; rubidium@7839: smatz@8456: w->widget[NGWW_MATRIX].data = (w->vscroll.cap << 8) + 1; rubidium@7839: skidd13@8462: SetVScrollCount(w, ld->list_length); skidd13@8462: smatz@8456: int widget_width = w->widget[NGWW_FIND].right - w->widget[NGWW_FIND].left; rubidium@7839: int space = (w->width - 4 * widget_width - 25) / 3; rubidium@7839: rubidium@7839: int offset = 10; rubidium@7839: for (uint i = 0; i < 4; i++) { smatz@8456: w->widget[NGWW_FIND + i].left = offset; rubidium@7839: offset += widget_width; smatz@8456: w->widget[NGWW_FIND + i].right = offset; rubidium@7839: offset += space; rubidium@7839: } rubidium@7839: } break; rubidium@7839: smatz@8456: case WE_DESTROY: // Nicely clean up the sort-list Darkvater@2888: free(WP(w, network_ql_d).sort_list); Darkvater@2888: break; truelight@0: } truelight@0: } truelight@0: truelight@0: static const Widget _network_game_window_widgets[] = { smatz@8456: /* TOP */ smatz@8456: { WWT_CLOSEBOX, RESIZE_NONE, BGC, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, // NGWW_CLOSE rubidium@7935: { WWT_CAPTION, RESIZE_RIGHT, BGC, 11, 449, 0, 13, STR_NETWORK_MULTIPLAYER, STR_NULL}, rubidium@7935: { WWT_PANEL, RESIZE_RB, BGC, 0, 449, 14, 263, 0x0, STR_NULL}, truelight@0: smatz@8456: { WWT_TEXT, RESIZE_NONE, BGC, 9, 85, 23, 35, STR_NETWORK_CONNECTION, STR_NULL}, peter1138@8839: { WWT_DROPDOWNIN, RESIZE_NONE, BGC, 90, 181, 22, 33, STR_NETWORK_LAN_INTERNET_COMBO, STR_NETWORK_CONNECTION_TIP}, // NGWW_CONN_BTN Darkvater@2881: smatz@8456: { WWT_PANEL, RESIZE_LR, BGC, 290, 440, 22, 33, 0x0, STR_NETWORK_ENTER_NAME_TIP}, // NGWW_PLAYER rubidium@4344: smatz@8456: /* LEFT SIDE */ smatz@8456: { WWT_PUSHTXTBTN, RESIZE_RIGHT, BTC, 10, 70, 42, 53, STR_NETWORK_GAME_NAME, STR_NETWORK_GAME_NAME_TIP}, // NGWW_NAME smatz@8456: { WWT_PUSHTXTBTN, RESIZE_LR, BTC, 71, 150, 42, 53, STR_NETWORK_CLIENTS_CAPTION, STR_NETWORK_CLIENTS_CAPTION_TIP}, // NGWW_CLIENTS smatz@8456: { WWT_PUSHTXTBTN, RESIZE_LR, BTC, 151, 190, 42, 53, STR_EMPTY, STR_NETWORK_INFO_ICONS_TIP}, // NGWW_INFO rubidium@7935: smatz@8456: { WWT_MATRIX, RESIZE_RB, BGC, 10, 190, 54, 236, (13 << 8) + 1, STR_NETWORK_CLICK_GAME_TO_SELECT}, // NGWW_MATRIX smatz@8456: { WWT_SCROLLBAR, RESIZE_LRB, BGC, 191, 202, 42, 236, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST}, truelight@543: truelight@543: /* RIGHT SIDE */ smatz@8456: { WWT_PANEL, RESIZE_LRB, BGC, 210, 440, 42, 236, 0x0, STR_NULL}, // NGWW_DETAILS Darkvater@2881: smatz@8456: { WWT_PUSHTXTBTN, RESIZE_LRTB, BTC, 215, 315, 215, 226, STR_NETWORK_JOIN_GAME, STR_NULL}, // NGWW_JOIN smatz@8456: { WWT_PUSHTXTBTN, RESIZE_LRTB, BTC, 330, 435, 215, 226, STR_NETWORK_REFRESH, STR_NETWORK_REFRESH_TIP}, // NGWW_REFRESH rubidium@5339: smatz@8456: { WWT_PUSHTXTBTN, RESIZE_LRTB, BTC, 330, 435, 197, 208, STR_NEWGRF_SETTINGS_BUTTON, STR_NULL}, // NGWW_NEWGRF rubidium@7839: smatz@8456: /* BOTTOM */ smatz@8456: { WWT_PUSHTXTBTN, RESIZE_TB, BTC, 10, 110, 246, 257, STR_NETWORK_FIND_SERVER, STR_NETWORK_FIND_SERVER_TIP}, // NGWW_FIND smatz@8456: { WWT_PUSHTXTBTN, RESIZE_TB, BTC, 118, 218, 246, 257, STR_NETWORK_ADD_SERVER, STR_NETWORK_ADD_SERVER_TIP}, // NGWW_ADD smatz@8456: { WWT_PUSHTXTBTN, RESIZE_TB, BTC, 226, 326, 246, 257, STR_NETWORK_START_SERVER, STR_NETWORK_START_SERVER_TIP}, // NGWW_START smatz@8456: { WWT_PUSHTXTBTN, RESIZE_TB, BTC, 334, 434, 246, 257, STR_012E_CANCEL, STR_NULL}, // NGWW_CANCEL rubidium@7935: rubidium@7935: { WWT_RESIZEBOX, RESIZE_LRTB, BGC, 438, 449, 252, 263, 0x0, STR_RESIZE_BUTTON }, truelight@543: darkvater@176: { WIDGETS_END}, truelight@0: }; truelight@0: truelight@0: static const WindowDesc _network_game_window_desc = { rubidium@7839: WDP_CENTER, WDP_CENTER, 450, 264, 550, 264, rubidium@6144: WC_NETWORK_WINDOW, WC_NONE, rubidium@7839: WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_STD_BTN | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE, truelight@0: _network_game_window_widgets, truelight@0: NetworkGameWindowWndProc, truelight@0: }; truelight@0: rubidium@6573: void ShowNetworkGameWindow() truelight@0: { Darkvater@2922: static bool first = true; truelight@0: Window *w; truelight@0: DeleteWindowById(WC_NETWORK_WINDOW, 0); dominik@105: truelight@764: /* Only show once */ Darkvater@2922: if (first) { Darkvater@2885: char* const *srv; Darkvater@2881: Darkvater@2922: first = false; truelight@764: // add all servers from the config file to our list Darkvater@2885: for (srv = &_network_host_list[0]; srv != endof(_network_host_list) && *srv != NULL; srv++) { Darkvater@2881: NetworkAddServer(*srv); truelight@764: } Darkvater@2922: Darkvater@2922: _ng_sorting.criteria = 2; // sort default by collectivity (green-dots on top) Darkvater@2922: _ng_sorting.order = 0; // sort ascending by default truelight@764: } truelight@764: truelight@0: w = AllocateWindowDesc(&_network_game_window_desc); Darkvater@2887: if (w != NULL) { belugas@4171: querystr_d *querystr = &WP(w, network_ql_d).q; tron@3470: tron@3470: ttd_strlcpy(_edit_str_buf, _network_player_name, lengthof(_edit_str_buf)); Darkvater@4909: querystr->afilter = CS_ALPHANUMERAL; Darkvater@4948: InitializeTextBuffer(&querystr->text, _edit_str_buf, lengthof(_edit_str_buf), 120); truelight@0: Darkvater@2887: UpdateNetworkGameWindow(true); Darkvater@2887: } truelight@543: } truelight@543: truelight@543: enum { truelight@543: NSSWND_START = 64, truelight@543: NSSWND_ROWSIZE = 12 truelight@543: }; truelight@543: smatz@8455: /** Enum for NetworkStartServerWindow, referring to _network_start_server_window_widgets */ smatz@8455: enum NetworkStartServerWidgets { smatz@8455: NSSW_CLOSE = 0, ///< Close 'X' button smatz@8455: NSSW_GAMENAME = 4, ///< Background for editbox to set game name smatz@8455: NSSW_SETPWD = 5, ///< 'Set password' button smatz@8455: NSSW_SELMAP = 7, ///< 'Select map' list peter1138@8839: NSSW_CONNTYPE_BTN = 10, ///< 'Connection type' droplist button peter1138@8839: NSSW_CLIENTS_BTND = 12, ///< 'Max clients' downarrow peter1138@8839: NSSW_CLIENTS_TXT = 13, ///< 'Max clients' text peter1138@8839: NSSW_CLIENTS_BTNU = 14, ///< 'Max clients' uparrow peter1138@8839: NSSW_COMPANIES_BTND = 16, ///< 'Max companies' downarrow peter1138@8839: NSSW_COMPANIES_TXT = 17, ///< 'Max companies' text peter1138@8839: NSSW_COMPANIES_BTNU = 18, ///< 'Max companies' uparrow peter1138@8839: NSSW_SPECTATORS_BTND = 20, ///< 'Max spectators' downarrow peter1138@8839: NSSW_SPECTATORS_TXT = 21, ///< 'Max spectators' text peter1138@8839: NSSW_SPECTATORS_BTNU = 22, ///< 'Max spectators' uparrow peter1138@8839: NSSW_LANGUAGE_BTN = 24, ///< 'Language spoken' droplist button peter1138@8839: NSSW_START = 25, ///< 'Start' button peter1138@8839: NSSW_LOAD = 26, ///< 'Load' button peter1138@8839: NSSW_CANCEL = 27, ///< 'Cancel' button smatz@8455: }; smatz@8455: smatz@8457: /** smatz@8457: * Handler of actions done in the NetworkStartServer window smatz@8457: * smatz@8457: * @param w pointer to the Window structure smatz@8457: * @param e pointer to window event smatz@8457: * @note Uses network_ql_d (network_d, querystr_d and list_d) WP macro smatz@8457: * @see struct _network_start_server_window_widgets smatz@8457: * @see enum NetworkStartServerWidgets smatz@8457: */ truelight@0: static void NetworkStartServerWindowWndProc(Window *w, WindowEvent *e) truelight@0: { Darkvater@2888: network_d *nd = &WP(w, network_ql_d).n; Darkvater@2887: Darkvater@1653: switch (e->event) { smatz@8455: case WE_CREATE: // focus input box smatz@8455: nd->field = NSSW_GAMENAME; tron@1899: _network_game_info.use_password = (_network_server_password[0] != '\0'); Darkvater@1653: break; Darkvater@1653: truelight@0: case WE_PAINT: { truelight@543: int y = NSSWND_START, pos; truelight@543: const FiosItem *item; truelight@193: smatz@8455: /* draw basic widgets */ rubidium@7935: SetDParam(1, _connection_types_dropdown[_network_advertise]); rubidium@7935: SetDParam(2, _network_game_info.clients_max); rubidium@7935: SetDParam(3, _network_game_info.companies_max); rubidium@7935: SetDParam(4, _network_game_info.spectators_max); rubidium@7935: SetDParam(5, STR_NETWORK_LANG_ANY + _network_game_info.server_lang); truelight@0: DrawWindowWidgets(w); truelight@0: smatz@8455: /* editbox to set game name */ smatz@8455: DrawEditBox(w, &WP(w, network_ql_d).q, NSSW_GAMENAME); Darkvater@2879: smatz@8455: /* if password is set, draw red '*' next to 'Set password' button */ belugas@8320: if (_network_game_info.use_password) DoDrawString("*", 408, 23, TC_RED); Darkvater@1653: smatz@8455: /* draw list of maps */ smatz@8455: GfxFillRect(11, 63, 258, 215, 0xD7); // black background of maps list smatz@8455: truelight@543: pos = w->vscroll.pos; truelight@543: while (pos < _fios_num + 1) { truelight@543: item = _fios_list + pos - 1; Darkvater@2887: if (item == nd->map || (pos == 0 && nd->map == NULL)) Darkvater@2879: GfxFillRect(11, y - 1, 258, y + 10, 155); // show highlighted item with a different colour truelight@543: tron@4077: if (pos == 0) { belugas@8320: DrawString(14, y, STR_4010_GENERATE_RANDOM_NEW_GAME, TC_DARK_GREEN); tron@4077: } else { tron@4077: DoDrawString(item->title, 14, y, _fios_colors[item->type] ); tron@4077: } truelight@543: pos++; truelight@543: y += NSSWND_ROWSIZE; truelight@543: truelight@543: if (y >= w->vscroll.cap * NSSWND_ROWSIZE + NSSWND_START) break; truelight@543: } rubidium@6988: } break; truelight@0: truelight@0: case WE_CLICK: belugas@4634: nd->field = e->we.click.widget; belugas@4634: switch (e->we.click.widget) { smatz@8455: case NSSW_CLOSE: // Close 'X' smatz@8455: case NSSW_CANCEL: // Cancel button truelight@0: ShowNetworkGameWindow(); truelight@0: break; tron@2639: smatz@8455: case NSSW_SETPWD: // Set password button smatz@8455: nd->widget_id = NSSW_SETPWD; Darkvater@5682: ShowQueryString(BindCString(_network_server_password), STR_NETWORK_SET_PASSWORD, 20, 250, w, CS_ALPHANUMERAL); tron@2639: break; tron@2639: smatz@8455: case NSSW_SELMAP: { // Select map belugas@4634: int y = (e->we.click.pt.y - NSSWND_START) / NSSWND_ROWSIZE; tron@2639: tron@2639: y += w->vscroll.pos; tron@2639: if (y >= w->vscroll.count) return; Darkvater@2100: Darkvater@2887: nd->map = (y == 0) ? NULL : _fios_list + y - 1; truelight@543: SetWindowDirty(w); truelight@543: } break; peter1138@8839: case NSSW_CONNTYPE_BTN: // Connection type smatz@8455: ShowDropDownMenu(w, _connection_types_dropdown, _network_advertise, NSSW_CONNTYPE_BTN, 0, 0); // do it for widget NSSW_CONNTYPE_BTN truelight@675: break; smatz@8455: case NSSW_CLIENTS_BTND: case NSSW_CLIENTS_BTNU: // Click on up/down button for number of clients smatz@8455: case NSSW_COMPANIES_BTND: case NSSW_COMPANIES_BTNU: // Click on up/down button for number of companies smatz@8455: case NSSW_SPECTATORS_BTND: case NSSW_SPECTATORS_BTNU: // Click on up/down button for number of spectators rubidium@7935: /* Don't allow too fast scrolling */ rubidium@7935: if ((w->flags4 & WF_TIMEOUT_MASK) <= 2 << WF_TIMEOUT_SHL) { belugas@8532: w->HandleButtonClick(e->we.click.widget); rubidium@7935: SetWindowDirty(w); rubidium@7935: switch (e->we.click.widget) { rubidium@7935: default: NOT_REACHED(); smatz@8455: case NSSW_CLIENTS_BTND: case NSSW_CLIENTS_BTNU: smatz@8455: _network_game_info.clients_max = Clamp(_network_game_info.clients_max + e->we.click.widget - NSSW_CLIENTS_TXT, 2, MAX_CLIENTS); smatz@8455: break; smatz@8455: case NSSW_COMPANIES_BTND: case NSSW_COMPANIES_BTNU: smatz@8455: _network_game_info.companies_max = Clamp(_network_game_info.companies_max + e->we.click.widget - NSSW_COMPANIES_TXT, 1, MAX_PLAYERS); smatz@8455: break; smatz@8455: case NSSW_SPECTATORS_BTND: case NSSW_SPECTATORS_BTNU: smatz@8455: _network_game_info.spectators_max = Clamp(_network_game_info.spectators_max + e->we.click.widget - NSSW_SPECTATORS_TXT, 0, MAX_CLIENTS); smatz@8455: break; rubidium@7935: } rubidium@7935: } rubidium@7935: _left_button_clicked = false; Darkvater@2879: break; smatz@8455: case NSSW_CLIENTS_TXT: // Click on number of players smatz@8455: nd->widget_id = NSSW_CLIENTS_TXT; rubidium@7935: SetDParam(0, _network_game_info.clients_max); smatz@8455: ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_NETWORK_NUMBER_OF_CLIENTS, 3, 50, w, CS_NUMERAL); Darkvater@2879: break; smatz@8455: case NSSW_COMPANIES_TXT: // Click on number of companies smatz@8455: nd->widget_id = NSSW_COMPANIES_TXT; rubidium@7935: SetDParam(0, _network_game_info.companies_max); smatz@8455: ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_NETWORK_NUMBER_OF_COMPANIES, 3, 50, w, CS_NUMERAL); Darkvater@2879: break; smatz@8455: case NSSW_SPECTATORS_TXT: // Click on number of spectators smatz@8455: nd->widget_id = NSSW_SPECTATORS_TXT; rubidium@7935: SetDParam(0, _network_game_info.spectators_max); rubidium@7935: ShowQueryString(STR_CONFIG_PATCHES_INT32, STR_NETWORK_NUMBER_OF_SPECTATORS, 3, 50, w, CS_NUMERAL); rubidium@7935: break; peter1138@8839: case NSSW_LANGUAGE_BTN: { // Language glx@7294: uint sel = 0; glx@7292: for (uint i = 0; i < lengthof(_language_dropdown) - 1; i++) { glx@7292: if (_language_dropdown[i] == STR_NETWORK_LANG_ANY + _network_game_info.server_lang) { glx@7292: sel = i; glx@7292: break; glx@7292: } glx@7292: } smatz@8455: ShowDropDownMenu(w, _language_dropdown, sel, NSSW_LANGUAGE_BTN, 0, 0); Darkvater@2879: break; glx@7292: } smatz@8455: case NSSW_START: // Start game truelight@543: _is_network_server = true; Darkvater@2887: Darkvater@2887: if (nd->map == NULL) { // start random new game truelight@4300: ShowGenerateLandscape(); truelight@543: } else { // load a scenario Darkvater@2887: char *name = FiosBrowseTo(nd->map); Darkvater@2100: if (name != NULL) { Darkvater@2887: SetFiosType(nd->map->type); belugas@8648: _file_to_saveload.filetype = FT_SCENARIO; Darkvater@2100: ttd_strlcpy(_file_to_saveload.name, name, sizeof(_file_to_saveload.name)); Darkvater@2887: ttd_strlcpy(_file_to_saveload.title, nd->map->title, sizeof(_file_to_saveload.title)); Darkvater@2100: truelight@543: DeleteWindow(w); truelight@4300: SwitchMode(SM_START_SCENARIO); truelight@543: } truelight@543: } truelight@0: break; smatz@8455: case NSSW_LOAD: // Load game truelight@543: _is_network_server = true; smatz@8455: /* XXX - WC_NETWORK_WINDOW (this window) should stay, but if it stays, it gets truelight@543: * copied all the elements of 'load game' and upon closing that, it segfaults */ smatz@8455: DeleteWindow(w); darkvater@172: ShowSaveLoadDialog(SLD_LOAD_GAME); darkvater@172: break; truelight@0: } darkvater@172: break; darkvater@172: smatz@8455: case WE_DROPDOWN_SELECT: // we have selected a dropdown item in the list belugas@4634: switch (e->we.dropdown.button) { smatz@8455: case NSSW_CONNTYPE_BTN: smatz@8455: _network_advertise = (e->we.dropdown.index != 0); smatz@8455: break; smatz@8455: case NSSW_LANGUAGE_BTN: glx@7292: _network_game_info.server_lang = _language_dropdown[e->we.dropdown.index] - STR_NETWORK_LANG_ANY; glx@7276: break; smatz@8455: default: smatz@8455: NOT_REACHED(); truelight@543: } darkvater@172: darkvater@172: SetWindowDirty(w); darkvater@172: break; truelight@0: truelight@0: case WE_MOUSELOOP: smatz@8455: if (nd->field == NSSW_GAMENAME) HandleEditBox(w, &WP(w, network_ql_d).q, NSSW_GAMENAME); truelight@0: break; truelight@0: truelight@0: case WE_KEYPRESS: smatz@8455: if (nd->field == NSSW_GAMENAME) { smatz@8455: if (HandleEditBoxKey(w, &WP(w, network_ql_d).q, NSSW_GAMENAME, e) == 1) break; // enter pressed Darkvater@2887: Darkvater@2888: ttd_strlcpy(_network_server_name, WP(w, network_ql_d).q.text.buf, sizeof(_network_server_name)); Darkvater@2887: } truelight@0: break; truelight@193: rubidium@7935: case WE_ON_EDIT_TEXT: rubidium@7935: if (e->we.edittext.str == NULL) break; rubidium@7935: smatz@8455: if (nd->widget_id == NSSW_SETPWD) { rubidium@7935: ttd_strlcpy(_network_server_password, e->we.edittext.str, lengthof(_network_server_password)); rubidium@7935: _network_game_info.use_password = (_network_server_password[0] != '\0'); rubidium@7935: } else { rubidium@7935: int32 value = atoi(e->we.edittext.str); glx@8524: w->InvalidateWidget(nd->widget_id); rubidium@7935: switch (nd->widget_id) { rubidium@7935: default: NOT_REACHED(); smatz@8455: case NSSW_CLIENTS_TXT: _network_game_info.clients_max = Clamp(value, 2, MAX_CLIENTS); break; smatz@8455: case NSSW_COMPANIES_TXT: _network_game_info.companies_max = Clamp(value, 1, MAX_PLAYERS); break; smatz@8455: case NSSW_SPECTATORS_TXT: _network_game_info.spectators_max = Clamp(value, 0, MAX_CLIENTS); break; rubidium@7935: } rubidium@7935: } rubidium@7935: Darkvater@1653: SetWindowDirty(w); rubidium@7935: break; truelight@0: } truelight@0: } truelight@0: truelight@0: static const Widget _network_start_server_window_widgets[] = { smatz@8455: /* Window decoration and background panel */ smatz@8455: { WWT_CLOSEBOX, RESIZE_NONE, BGC, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW }, // NSSW_CLOSE smatz@8455: { WWT_CAPTION, RESIZE_NONE, BGC, 11, 419, 0, 13, STR_NETWORK_START_GAME_WINDOW, STR_NULL}, smatz@8457: { WWT_PANEL, RESIZE_NONE, BGC, 0, 419, 14, 243, 0x0, STR_NULL}, truelight@867: smatz@8455: /* Set game name and password widgets */ smatz@8455: { WWT_TEXT, RESIZE_NONE, BGC, 10, 90, 22, 34, STR_NETWORK_NEW_GAME_NAME, STR_NULL}, smatz@8457: { WWT_PANEL, RESIZE_NONE, BGC, 100, 272, 22, 33, 0x0, STR_NETWORK_NEW_GAME_NAME_TIP}, // NSSW_GAMENAME smatz@8455: { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 285, 405, 22, 33, STR_NETWORK_SET_PASSWORD, STR_NETWORK_PASSWORD_TIP}, // NSSW_SETPWD truelight@867: smatz@8455: /* List of playable scenarios */ smatz@8455: { WWT_TEXT, RESIZE_NONE, BGC, 10, 110, 43, 55, STR_NETWORK_SELECT_MAP, STR_NULL}, smatz@8455: { WWT_INSET, RESIZE_NONE, BGC, 10, 271, 62, 216, STR_NULL, STR_NETWORK_SELECT_MAP_TIP}, // NSSW_SELMAP smatz@8457: { WWT_SCROLLBAR, RESIZE_NONE, BGC, 259, 270, 63, 215, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST}, smatz@8455: rubidium@7935: /* Combo/selection boxes to control Connection Type / Max Clients / Max Companies / Max Observers / Language */ smatz@8455: { WWT_TEXT, RESIZE_NONE, BGC, 280, 419, 63, 75, STR_NETWORK_CONNECTION, STR_NULL}, peter1138@8839: { WWT_DROPDOWNIN, RESIZE_NONE, BGC, 280, 410, 77, 88, STR_NETWORK_LAN_INTERNET_COMBO, STR_NETWORK_CONNECTION_TIP}, // NSSW_CONNTYPE_BTN rubidium@7935: smatz@8455: { WWT_TEXT, RESIZE_NONE, BGC, 280, 419, 95, 107, STR_NETWORK_NUMBER_OF_CLIENTS, STR_NULL}, smatz@8455: { WWT_IMGBTN, RESIZE_NONE, BGC, 280, 291, 109, 120, SPR_ARROW_DOWN, STR_NETWORK_NUMBER_OF_CLIENTS_TIP}, // NSSW_CLIENTS_BTND smatz@8455: { WWT_PUSHTXTBTN, RESIZE_NONE, BGC, 292, 397, 109, 120, STR_NETWORK_CLIENTS_SELECT, STR_NETWORK_NUMBER_OF_CLIENTS_TIP}, // NSSW_CLIENTS_TXT smatz@8455: { WWT_IMGBTN, RESIZE_NONE, BGC, 398, 410, 109, 120, SPR_ARROW_UP, STR_NETWORK_NUMBER_OF_CLIENTS_TIP}, // NSSW_CLIENTS_BTNU rubidium@7935: smatz@8455: { WWT_TEXT, RESIZE_NONE, BGC, 280, 419, 127, 139, STR_NETWORK_NUMBER_OF_COMPANIES, STR_NULL}, smatz@8455: { WWT_IMGBTN, RESIZE_NONE, BGC, 280, 291, 141, 152, SPR_ARROW_DOWN, STR_NETWORK_NUMBER_OF_COMPANIES_TIP}, // NSSW_COMPANIES_BTND smatz@8455: { WWT_PUSHTXTBTN, RESIZE_NONE, BGC, 292, 397, 141, 152, STR_NETWORK_COMPANIES_SELECT, STR_NETWORK_NUMBER_OF_COMPANIES_TIP}, // NSSW_COMPANIES_TXT smatz@8455: { WWT_IMGBTN, RESIZE_NONE, BGC, 398, 410, 141, 152, SPR_ARROW_UP, STR_NETWORK_NUMBER_OF_COMPANIES_TIP}, // NSSW_COMPANIES_BTNU smatz@8455: smatz@8455: { WWT_TEXT, RESIZE_NONE, BGC, 280, 419, 159, 171, STR_NETWORK_NUMBER_OF_SPECTATORS, STR_NULL}, smatz@8455: { WWT_IMGBTN, RESIZE_NONE, BGC, 280, 291, 173, 184, SPR_ARROW_DOWN, STR_NETWORK_NUMBER_OF_SPECTATORS_TIP}, // NSSW_SPECTATORS_BTND smatz@8455: { WWT_PUSHTXTBTN, RESIZE_NONE, BGC, 292, 397, 173, 184, STR_NETWORK_SPECTATORS_SELECT, STR_NETWORK_NUMBER_OF_SPECTATORS_TIP}, // NSSW_SPECTATORS_TXT smatz@8455: { WWT_IMGBTN, RESIZE_NONE, BGC, 398, 410, 173, 184, SPR_ARROW_UP, STR_NETWORK_NUMBER_OF_SPECTATORS_TIP}, // NSSW_SPECTATORS_BTNU smatz@8455: smatz@8455: { WWT_TEXT, RESIZE_NONE, BGC, 280, 419, 191, 203, STR_NETWORK_LANGUAGE_SPOKEN, STR_NULL}, peter1138@8839: { WWT_DROPDOWNIN, RESIZE_NONE, BGC, 280, 410, 205, 216, STR_NETWORK_LANGUAGE_COMBO, STR_NETWORK_LANGUAGE_TIP}, // NSSW_LANGUAGE_BTN smatz@8455: smatz@8455: /* Buttons Start / Load / Cancel */ smatz@8455: { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 40, 140, 224, 235, STR_NETWORK_START_GAME, STR_NETWORK_START_GAME_TIP}, // NSSW_START smatz@8455: { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 150, 250, 224, 235, STR_NETWORK_LOAD_GAME, STR_NETWORK_LOAD_GAME_TIP}, // NSSW_LOAD smatz@8455: { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 260, 360, 224, 235, STR_012E_CANCEL, STR_NULL}, // NSSW_CANCEL smatz@8455: darkvater@176: { WIDGETS_END}, truelight@0: }; truelight@0: truelight@0: static const WindowDesc _network_start_server_window_desc = { rubidium@7837: WDP_CENTER, WDP_CENTER, 420, 244, 420, 244, rubidium@6144: WC_NETWORK_WINDOW, WC_NONE, ludde@2064: WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, truelight@0: _network_start_server_window_widgets, truelight@0: NetworkStartServerWindowWndProc, truelight@0: }; truelight@0: rubidium@6573: static void ShowNetworkStartServerWindow() truelight@0: { truelight@0: Window *w; truelight@0: DeleteWindowById(WC_NETWORK_WINDOW, 0); truelight@193: truelight@0: w = AllocateWindowDesc(&_network_start_server_window_desc); tron@3470: ttd_strlcpy(_edit_str_buf, _network_server_name, lengthof(_edit_str_buf)); truelight@543: truelight@543: _saveload_mode = SLD_NEW_GAME; truelight@543: BuildFileList(); Darkvater@2943: w->vscroll.cap = 12; rubidium@6987: w->vscroll.count = _fios_num + 1; truelight@193: Darkvater@4909: WP(w, network_ql_d).q.afilter = CS_ALPHANUMERAL; Darkvater@4948: InitializeTextBuffer(&WP(w, network_ql_d).q.text, _edit_str_buf, lengthof(_edit_str_buf), 160); truelight@0: } truelight@0: rubidium@5838: static PlayerID NetworkLobbyFindCompanyIndex(byte pos) truelight@734: { rubidium@5838: PlayerID i; tron@2639: truelight@734: /* Scroll through all _network_player_info and get the 'pos' item truelight@734: that is not empty */ rubidium@5838: for (i = PLAYER_FIRST; i < MAX_PLAYERS; i++) { truelight@734: if (_network_player_info[i].company_name[0] != '\0') { tron@2639: if (pos-- == 0) return i; truelight@734: } truelight@734: } truelight@734: rubidium@5838: return PLAYER_FIRST; truelight@734: } truelight@734: smatz@8459: /** Enum for NetworkLobbyWindow, referring to _network_lobby_window_widgets */ smatz@8459: enum NetworkLobbyWindowWidgets { smatz@8459: NLWW_CLOSE = 0, ///< Close 'X' button smatz@8459: NLWW_MATRIX = 5, ///< List of companies smatz@8459: NLWW_DETAILS = 7, ///< Company details smatz@8459: NLWW_JOIN = 8, ///< 'Join company' button smatz@8459: NLWW_NEW = 9, ///< 'New company' button smatz@8459: NLWW_SPECTATE = 10, ///< 'Spectate game' button smatz@8459: NLWW_REFRESH = 11, ///< 'Refresh server' button smatz@8459: NLWW_CANCEL = 12, ///< 'Cancel' button smatz@8459: }; smatz@8459: smatz@8459: /** smatz@8459: * Handler of actions done in the NetworkLobby window smatz@8459: * smatz@8459: * @param w pointer to the Window structure smatz@8459: * @param e pointer to window event smatz@8459: * @note uses network_d WP macro smatz@8459: * @see struct _network_lobby_window_widgets smatz@8459: * @see enum NetworkLobbyWindowWidgets smatz@8459: */ truelight@0: static void NetworkLobbyWindowWndProc(Window *w, WindowEvent *e) truelight@0: { Darkvater@2887: network_d *nd = &WP(w, network_d); Darkvater@2887: tron@2639: switch (e->event) { Darkvater@2879: case WE_CREATE: rubidium@5838: nd->company = INVALID_PLAYER; Darkvater@2879: break; Darkvater@2879: truelight@0: case WE_PAINT: { Darkvater@2887: const NetworkGameInfo *gi = &nd->server->info; truelight@543: int y = NET_PRC__OFFSET_TOP_WIDGET_COMPANY, pos; truelight@193: smatz@8459: /* Join button is disabled when no company is selected */ rubidium@8493: w->SetWidgetDisabledState(NLWW_JOIN, nd->company == INVALID_PLAYER); smatz@8459: /* Cannot start new company if there are too many */ rubidium@8493: w->SetWidgetDisabledState(NLWW_NEW, gi->companies_on >= gi->companies_max); smatz@8459: /* Cannot spectate if there are too many spectators */ rubidium@8493: w->SetWidgetDisabledState(NLWW_SPECTATE, gi->spectators_on >= gi->spectators_max); truelight@621: smatz@8459: /* Draw window widgets */ smatz@8459: SetDParamStr(0, gi->server_name); truelight@0: DrawWindowWidgets(w); truelight@0: Darkvater@2887: /* Draw company list */ truelight@543: pos = w->vscroll.pos; Darkvater@2879: while (pos < gi->companies_on) { Darkvater@2887: byte company = NetworkLobbyFindCompanyIndex(pos); truelight@1011: bool income = false; Darkvater@2887: if (nd->company == company) Darkvater@2887: GfxFillRect(11, y - 1, 154, y + 10, 10); // show highlighted item with a different colour truelight@193: belugas@8320: DoDrawStringTruncated(_network_player_info[company].company_name, 13, y, TC_BLACK, 135 - 13); peter1138@5920: if (_network_player_info[company].use_password != 0) DrawSprite(SPR_LOCK, PAL_NONE, 135, y); truelight@1011: truelight@1011: /* If the company's income was positive puts a green dot else a red dot */ Darkvater@2887: if (_network_player_info[company].income >= 0) income = true; peter1138@5920: DrawSprite(SPR_BLOT, income ? PALETTE_TO_GREEN : PALETTE_TO_RED, 145, y); truelight@0: truelight@543: pos++; Darkvater@2887: y += NET_PRC__SIZE_OF_ROW; tron@2639: if (pos >= w->vscroll.cap) break; truelight@543: } truelight@543: Darkvater@2887: /* Draw info about selected company when it is selected in the left window */ Darkvater@2887: GfxFillRect(174, 39, 403, 75, 157); belugas@8320: DrawStringCentered(290, 50, STR_NETWORK_COMPANY_INFO, TC_FROMSTRING); smatz@8459: if (nd->company != INVALID_PLAYER) { darkvater@1025: const uint x = 183; smatz@8459: const uint trunc_width = w->widget[NLWW_DETAILS].right - x; Darkvater@2887: y = 80; truelight@543: Darkvater@2887: SetDParam(0, nd->server->info.clients_on); Darkvater@2887: SetDParam(1, nd->server->info.clients_max); Darkvater@2887: SetDParam(2, nd->server->info.companies_on); Darkvater@2887: SetDParam(3, nd->server->info.companies_max); belugas@8320: DrawString(x, y, STR_NETWORK_CLIENTS, TC_GOLD); Darkvater@2887: y += 10; Darkvater@2887: Darkvater@2887: SetDParamStr(0, _network_player_info[nd->company].company_name); belugas@8320: DrawStringTruncated(x, y, STR_NETWORK_COMPANY_NAME, TC_GOLD, trunc_width); truelight@543: y += 10; truelight@543: rubidium@4329: SetDParam(0, _network_player_info[nd->company].inaugurated_year); belugas@8320: DrawString(x, y, STR_NETWORK_INAUGURATION_YEAR, TC_GOLD); // inauguration year truelight@543: y += 10; truelight@543: rubidium@7498: SetDParam(0, _network_player_info[nd->company].company_value); belugas@8320: DrawString(x, y, STR_NETWORK_VALUE, TC_GOLD); // company value truelight@543: y += 10; truelight@543: rubidium@7498: SetDParam(0, _network_player_info[nd->company].money); belugas@8320: DrawString(x, y, STR_NETWORK_CURRENT_BALANCE, TC_GOLD); // current balance truelight@543: y += 10; truelight@543: rubidium@7498: SetDParam(0, _network_player_info[nd->company].income); belugas@8320: DrawString(x, y, STR_NETWORK_LAST_YEARS_INCOME, TC_GOLD); // last year's income truelight@543: y += 10; truelight@543: Darkvater@2887: SetDParam(0, _network_player_info[nd->company].performance); belugas@8320: DrawString(x, y, STR_NETWORK_PERFORMANCE, TC_GOLD); // performance truelight@543: y += 10; truelight@543: Darkvater@2887: SetDParam(0, _network_player_info[nd->company].num_vehicle[0]); Darkvater@2887: SetDParam(1, _network_player_info[nd->company].num_vehicle[1]); Darkvater@2887: SetDParam(2, _network_player_info[nd->company].num_vehicle[2]); Darkvater@2887: SetDParam(3, _network_player_info[nd->company].num_vehicle[3]); Darkvater@2887: SetDParam(4, _network_player_info[nd->company].num_vehicle[4]); belugas@8320: DrawString(x, y, STR_NETWORK_VEHICLES, TC_GOLD); // vehicles truelight@543: y += 10; truelight@543: Darkvater@2887: SetDParam(0, _network_player_info[nd->company].num_station[0]); Darkvater@2887: SetDParam(1, _network_player_info[nd->company].num_station[1]); Darkvater@2887: SetDParam(2, _network_player_info[nd->company].num_station[2]); Darkvater@2887: SetDParam(3, _network_player_info[nd->company].num_station[3]); Darkvater@2887: SetDParam(4, _network_player_info[nd->company].num_station[4]); belugas@8320: DrawString(x, y, STR_NETWORK_STATIONS, TC_GOLD); // stations truelight@543: y += 10; truelight@543: Darkvater@2887: SetDParamStr(0, _network_player_info[nd->company].players); belugas@8320: DrawStringTruncated(x, y, STR_NETWORK_PLAYERS, TC_GOLD, trunc_width); // players truelight@543: } rubidium@6988: } break; truelight@0: truelight@0: case WE_CLICK: belugas@4634: switch (e->we.click.widget) { smatz@8459: case NLWW_CLOSE: // Close 'X' smatz@8459: case NLWW_CANCEL: // Cancel button truelight@0: ShowNetworkGameWindow(); truelight@0: break; smatz@8459: case NLWW_MATRIX: { // Company list belugas@4634: uint32 id_v = (e->we.click.pt.y - NET_PRC__OFFSET_TOP_WIDGET_COMPANY) / NET_PRC__SIZE_OF_ROW; truelight@0: smatz@8459: if (id_v >= w->vscroll.cap) break; truelight@543: Darkvater@2887: id_v += w->vscroll.pos; rubidium@5838: nd->company = (id_v >= nd->server->info.companies_on) ? INVALID_PLAYER : NetworkLobbyFindCompanyIndex(id_v); truelight@543: SetWindowDirty(w); rubidium@6988: } break; smatz@8459: case NLWW_JOIN: // Join company smatz@8459: /* Button can be clicked only when it is enabled */ smatz@8459: _network_playas = nd->company; smatz@8459: NetworkClientConnectGame(_network_last_host, _network_last_port); truelight@543: break; smatz@8459: case NLWW_NEW: // New company Darkvater@4861: _network_playas = PLAYER_NEW_COMPANY; truelight@543: NetworkClientConnectGame(_network_last_host, _network_last_port); truelight@543: break; smatz@8459: case NLWW_SPECTATE: // Spectate game Darkvater@4848: _network_playas = PLAYER_SPECTATOR; truelight@543: NetworkClientConnectGame(_network_last_host, _network_last_port); truelight@543: break; smatz@8459: case NLWW_REFRESH: // Refresh rubidium@6167: NetworkTCPQueryServer(_network_last_host, _network_last_port); // company info rubidium@6167: NetworkUDPQueryServer(_network_last_host, _network_last_port); // general data truelight@543: break; rubidium@6988: } break; Darkvater@2884: Darkvater@2884: case WE_MESSAGE: Darkvater@2884: SetWindowDirty(w); Darkvater@2884: break; truelight@543: } truelight@543: } truelight@543: truelight@543: static const Widget _network_lobby_window_widgets[] = { smatz@8459: { WWT_CLOSEBOX, RESIZE_NONE, BGC, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW }, // NLWW_CLOSE smatz@8459: { WWT_CAPTION, RESIZE_NONE, BGC, 11, 419, 0, 13, STR_NETWORK_GAME_LOBBY, STR_NULL}, smatz@8459: { WWT_PANEL, RESIZE_NONE, BGC, 0, 419, 14, 234, 0x0, STR_NULL}, smatz@8459: { WWT_TEXT, RESIZE_NONE, BGC, 10, 419, 22, 34, STR_NETWORK_PREPARE_TO_JOIN, STR_NULL}, truelight@543: smatz@8459: /* company list */ smatz@8459: { WWT_PANEL, RESIZE_NONE, BTC, 10, 155, 38, 49, 0x0, STR_NULL}, smatz@8459: { WWT_MATRIX, RESIZE_NONE, BGC, 10, 155, 50, 190, (10 << 8) + 1, STR_NETWORK_COMPANY_LIST_TIP}, // NLWW_MATRIX smatz@8459: { WWT_SCROLLBAR, RESIZE_NONE, BGC, 156, 167, 38, 190, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST}, truelight@543: smatz@8459: /* company/player info */ smatz@8459: { WWT_PANEL, RESIZE_NONE, BGC, 173, 404, 38, 190, 0x0, STR_NULL}, // NLWW_DETAILS smatz@8459: smatz@8459: /* buttons */ smatz@8459: { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 10, 151, 200, 211, STR_NETWORK_JOIN_COMPANY, STR_NETWORK_JOIN_COMPANY_TIP}, // NLWW_JOIN smatz@8459: { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 10, 151, 215, 226, STR_NETWORK_NEW_COMPANY, STR_NETWORK_NEW_COMPANY_TIP}, // NLWW_NEW smatz@8459: { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 158, 268, 200, 211, STR_NETWORK_SPECTATE_GAME, STR_NETWORK_SPECTATE_GAME_TIP}, // NLWW_SPECTATE smatz@8459: { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 158, 268, 215, 226, STR_NETWORK_REFRESH, STR_NETWORK_REFRESH_TIP}, // NLWW_REFRESH smatz@8459: { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 278, 388, 200, 211, STR_012E_CANCEL, STR_NULL}, // NLWW_CANCEL truelight@543: truelight@543: { WIDGETS_END}, truelight@543: }; truelight@543: truelight@543: static const WindowDesc _network_lobby_window_desc = { rubidium@7837: WDP_CENTER, WDP_CENTER, 420, 235, 420, 235, rubidium@6144: WC_NETWORK_WINDOW, WC_NONE, truelight@543: WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, truelight@543: _network_lobby_window_widgets, truelight@543: NetworkLobbyWindowWndProc, truelight@543: }; truelight@543: Darkvater@2888: /* Show the networklobbywindow with the selected server Darkvater@2888: * @param ngl Selected game pointer which is passed to the new window */ Darkvater@2887: static void ShowNetworkLobbyWindow(NetworkGameList *ngl) truelight@543: { truelight@543: Window *w; truelight@543: DeleteWindowById(WC_NETWORK_WINDOW, 0); truelight@543: rubidium@6167: NetworkTCPQueryServer(_network_last_host, _network_last_port); // company info rubidium@6167: NetworkUDPQueryServer(_network_last_host, _network_last_port); // general data truelight@543: truelight@543: w = AllocateWindowDesc(&_network_lobby_window_desc); Darkvater@2887: if (w != NULL) { Darkvater@2888: WP(w, network_ql_d).n.server = ngl; Darkvater@2887: strcpy(_edit_str_buf, ""); Darkvater@2887: w->vscroll.cap = 10; Darkvater@2887: } truelight@543: } truelight@543: truelight@543: // The window below gives information about the connected clients truelight@543: // and also makes able to give money to them, kick them (if server) truelight@543: // and stuff like that. truelight@543: Darkvater@3692: extern void DrawPlayerIcon(PlayerID pid, int x, int y); truelight@543: truelight@543: // Every action must be of this form truelight@543: typedef void ClientList_Action_Proc(byte client_no); truelight@543: truelight@543: // Max 10 actions per client truelight@543: #define MAX_CLIENTLIST_ACTION 10 truelight@543: truelight@543: // Some standard bullshit.. defines variables ;) truelight@543: static void ClientListWndProc(Window *w, WindowEvent *e); truelight@543: static void ClientListPopupWndProc(Window *w, WindowEvent *e); truelight@543: static byte _selected_clientlist_item = 255; truelight@543: static byte _selected_clientlist_y = 0; truelight@543: static char _clientlist_action[MAX_CLIENTLIST_ACTION][50]; truelight@543: static ClientList_Action_Proc *_clientlist_proc[MAX_CLIENTLIST_ACTION]; truelight@543: truelight@543: enum { truelight@543: CLNWND_OFFSET = 16, truelight@543: CLNWND_ROWSIZE = 10 truelight@543: }; truelight@543: truelight@867: static const Widget _client_list_widgets[] = { peter1138@2725: { WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, truelight@7525: { WWT_CAPTION, RESIZE_NONE, 14, 11, 237, 0, 13, STR_NETWORK_CLIENT_LIST, STR_018C_WINDOW_TITLE_DRAG_THIS}, truelight@7525: { WWT_STICKYBOX, RESIZE_NONE, 14, 238, 249, 0, 13, STR_NULL, STR_STICKY_BUTTON}, truelight@543: Darkvater@4938: { WWT_PANEL, RESIZE_NONE, 14, 0, 249, 14, 14 + CLNWND_ROWSIZE + 1, 0x0, STR_NULL}, truelight@543: { WIDGETS_END}, truelight@543: }; truelight@543: truelight@867: static const Widget _client_list_popup_widgets[] = { rubidium@4344: { WWT_PANEL, RESIZE_NONE, 14, 0, 99, 0, 0, 0, STR_NULL}, truelight@543: { WIDGETS_END}, truelight@543: }; truelight@543: truelight@543: static WindowDesc _client_list_desc = { rubidium@7837: WDP_AUTO, WDP_AUTO, 250, 1, 250, 1, rubidium@6144: WC_CLIENT_LIST, WC_NONE, truelight@7525: WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON, truelight@543: _client_list_widgets, truelight@543: ClientListWndProc truelight@543: }; truelight@543: truelight@543: // Finds the Xth client-info that is active belugas@4171: static const NetworkClientInfo *NetworkFindClientInfo(byte client_no) truelight@543: { belugas@4171: const NetworkClientInfo *ci; tron@2630: Darkvater@4883: FOR_ALL_ACTIVE_CLIENT_INFOS(ci) { truelight@543: if (client_no == 0) return ci; truelight@543: client_no--; truelight@543: } truelight@543: truelight@543: return NULL; truelight@543: } truelight@543: truelight@543: // Here we start to define the options out of the menu truelight@543: static void ClientList_Kick(byte client_no) truelight@543: { truelight@543: if (client_no < MAX_PLAYERS) Darkvater@4880: SEND_COMMAND(PACKET_SERVER_ERROR)(DEREF_CLIENT(client_no), NETWORK_ERROR_KICKED); truelight@543: } truelight@543: truelight@841: static void ClientList_Ban(byte client_no) truelight@543: { truelight@841: uint i; truelight@841: uint32 ip = NetworkFindClientInfo(client_no)->client_ip; truelight@841: truelight@841: for (i = 0; i < lengthof(_network_ban_list); i++) { Darkvater@2914: if (_network_ban_list[i] == NULL) { truelight@841: _network_ban_list[i] = strdup(inet_ntoa(*(struct in_addr *)&ip)); truelight@841: break; truelight@841: } truelight@841: } truelight@841: truelight@841: if (client_no < MAX_PLAYERS) Darkvater@4880: SEND_COMMAND(PACKET_SERVER_ERROR)(DEREF_CLIENT(client_no), NETWORK_ERROR_KICKED); truelight@841: } truelight@543: truelight@543: static void ClientList_GiveMoney(byte client_no) truelight@543: { truelight@543: if (NetworkFindClientInfo(client_no) != NULL) Darkvater@4878: ShowNetworkGiveMoneyWindow(NetworkFindClientInfo(client_no)->client_playas); truelight@543: } truelight@543: truelight@543: static void ClientList_SpeakToClient(byte client_no) truelight@543: { truelight@543: if (NetworkFindClientInfo(client_no) != NULL) truelight@543: ShowNetworkChatQueryWindow(DESTTYPE_CLIENT, NetworkFindClientInfo(client_no)->client_index); truelight@543: } truelight@543: Darkvater@4906: static void ClientList_SpeakToCompany(byte client_no) truelight@543: { truelight@543: if (NetworkFindClientInfo(client_no) != NULL) Darkvater@4906: ShowNetworkChatQueryWindow(DESTTYPE_TEAM, NetworkFindClientInfo(client_no)->client_playas); truelight@543: } truelight@543: truelight@543: static void ClientList_SpeakToAll(byte client_no) truelight@543: { truelight@543: ShowNetworkChatQueryWindow(DESTTYPE_BROADCAST, 0); truelight@543: } truelight@543: truelight@543: static void ClientList_None(byte client_no) truelight@543: { truelight@543: // No action ;) truelight@543: } truelight@543: truelight@543: truelight@543: truelight@543: // Help, a action is clicked! What do we do? rubidium@7817: static void HandleClientListPopupClick(byte index, byte clientno) rubidium@7817: { truelight@543: // A click on the Popup of the ClientList.. handle the command truelight@543: if (index < MAX_CLIENTLIST_ACTION && _clientlist_proc[index] != NULL) { truelight@543: _clientlist_proc[index](clientno); truelight@543: } truelight@543: } truelight@543: truelight@543: // Finds the amount of clients and set the height correct truelight@543: static bool CheckClientListHeight(Window *w) truelight@543: { truelight@543: int num = 0; Darkvater@4883: const NetworkClientInfo *ci; truelight@543: truelight@543: // Should be replaced with a loop through all clients Darkvater@4883: FOR_ALL_ACTIVE_CLIENT_INFOS(ci) { truelight@543: num++; truelight@543: } truelight@543: truelight@543: num *= CLNWND_ROWSIZE; truelight@543: truelight@543: // If height is changed truelight@867: if (w->height != CLNWND_OFFSET + num + 1) { truelight@543: // XXX - magic unfortunately; (num + 2) has to be one bigger than heigh (num + 1) truelight@867: SetWindowDirty(w); truelight@7525: w->widget[3].bottom = w->widget[3].top + num + 2; truelight@867: w->height = CLNWND_OFFSET + num + 1; truelight@867: SetWindowDirty(w); truelight@543: return false; truelight@543: } truelight@543: return true; truelight@543: } truelight@543: truelight@543: // Finds the amount of actions in the popup and set the height correct rubidium@7817: static uint ClientListPopupHeight() rubidium@7817: { truelight@543: int i, num = 0; truelight@543: truelight@543: // Find the amount of actions truelight@543: for (i = 0; i < MAX_CLIENTLIST_ACTION; i++) { truelight@543: if (_clientlist_action[i][0] == '\0') continue; truelight@543: if (_clientlist_proc[i] == NULL) continue; truelight@543: num++; truelight@543: } truelight@543: truelight@543: num *= CLNWND_ROWSIZE; truelight@867: truelight@867: return num + 1; truelight@543: } truelight@543: truelight@543: // Show the popup (action list) truelight@543: static Window *PopupClientList(Window *w, int client_no, int x, int y) truelight@543: { truelight@867: int i, h; belugas@4171: const NetworkClientInfo *ci; truelight@543: DeleteWindowById(WC_TOOLBAR_MENU, 0); truelight@543: truelight@543: // Clean the current actions truelight@543: for (i = 0; i < MAX_CLIENTLIST_ACTION; i++) { truelight@543: _clientlist_action[i][0] = '\0'; truelight@543: _clientlist_proc[i] = NULL; truelight@543: } truelight@543: truelight@543: // Fill the actions this client has truelight@543: // Watch is, max 50 chars long! truelight@543: truelight@543: ci = NetworkFindClientInfo(client_no); truelight@543: if (ci == NULL) return NULL; truelight@543: truelight@543: i = 0; truelight@543: if (_network_own_client_index != ci->client_index) { Darkvater@4912: GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_SPEAK_TO_CLIENT, lastof(_clientlist_action[i])); truelight@543: _clientlist_proc[i++] = &ClientList_SpeakToClient; truelight@543: } truelight@543: Darkvater@4945: if (IsValidPlayer(ci->client_playas) || ci->client_playas == PLAYER_SPECTATOR) { Darkvater@4912: GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_SPEAK_TO_COMPANY, lastof(_clientlist_action[i])); Darkvater@4906: _clientlist_proc[i++] = &ClientList_SpeakToCompany; truelight@543: } Darkvater@4912: GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_SPEAK_TO_ALL, lastof(_clientlist_action[i])); truelight@543: _clientlist_proc[i++] = &ClientList_SpeakToAll; truelight@543: truelight@543: if (_network_own_client_index != ci->client_index) { rubidium@8153: /* We are no spectator and the player we want to give money to is no spectator and money gifts are allowed */ rubidium@8153: if (IsValidPlayer(_network_playas) && IsValidPlayer(ci->client_playas) && _patches.give_money) { Darkvater@4912: GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_GIVE_MONEY, lastof(_clientlist_action[i])); Darkvater@4878: _clientlist_proc[i++] = &ClientList_GiveMoney; truelight@0: } truelight@543: } truelight@543: Darkvater@4880: // A server can kick clients (but not himself) truelight@543: if (_network_server && _network_own_client_index != ci->client_index) { Darkvater@4912: GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_KICK, lastof(_clientlist_action[i])); truelight@543: _clientlist_proc[i++] = &ClientList_Kick; truelight@543: Darkvater@4912: sprintf(_clientlist_action[i],"Ban"); // XXX GetString? truelight@841: _clientlist_proc[i++] = &ClientList_Ban; truelight@543: } truelight@543: truelight@543: if (i == 0) { Darkvater@4912: GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_NONE, lastof(_clientlist_action[i])); truelight@543: _clientlist_proc[i++] = &ClientList_None; truelight@543: } truelight@543: truelight@867: /* Calculate the height */ rubidium@7817: h = ClientListPopupHeight(); truelight@543: truelight@543: // Allocate the popup Darkvater@3250: w = AllocateWindow(x, y, 150, h + 1, ClientListPopupWndProc, WC_TOOLBAR_MENU, _client_list_popup_widgets); truelight@867: w->widget[0].bottom = w->widget[0].top + h; Darkvater@3250: w->widget[0].right = w->widget[0].left + 150; truelight@867: truelight@543: w->flags4 &= ~WF_WHITE_BORDER_MASK; rubidium@8578: WP(w, menu_d).item_count = 0; truelight@543: // Save our client rubidium@8578: WP(w, menu_d).main_button = client_no; rubidium@8578: WP(w, menu_d).sel_index = 0; truelight@543: // We are a popup truelight@543: _popup_menu_active = true; truelight@543: truelight@543: return w; truelight@543: } truelight@543: Darkvater@2887: /** Main handle for the client popup list Darkvater@2887: * uses menu_d WP macro */ truelight@543: static void ClientListPopupWndProc(Window *w, WindowEvent *e) truelight@543: { tron@2639: switch (e->event) { truelight@543: case WE_PAINT: { truelight@543: int i, y, sel; belugas@8320: TextColour colour; truelight@543: DrawWindowWidgets(w); truelight@543: truelight@543: // Draw the actions rubidium@8578: sel = WP(w, menu_d).sel_index; truelight@543: y = 1; truelight@543: for (i = 0; i < MAX_CLIENTLIST_ACTION; i++, y += CLNWND_ROWSIZE) { truelight@543: if (_clientlist_action[i][0] == '\0') continue; truelight@543: if (_clientlist_proc[i] == NULL) continue; truelight@543: truelight@543: if (sel-- == 0) { // Selected item, highlight it Darkvater@3250: GfxFillRect(1, y, 150 - 2, y + CLNWND_ROWSIZE - 1, 0); belugas@8320: colour = TC_WHITE; tron@4077: } else { belugas@8320: colour = TC_BLACK; tron@4077: } truelight@543: truelight@543: DoDrawString(_clientlist_action[i], 4, y, colour); truelight@543: } rubidium@6988: } break; truelight@543: truelight@543: case WE_POPUPMENU_SELECT: { truelight@543: // We selected an action belugas@4634: int index = (e->we.popupmenu.pt.y - w->top) / CLNWND_ROWSIZE; truelight@543: belugas@4634: if (index >= 0 && e->we.popupmenu.pt.y >= w->top) rubidium@8578: HandleClientListPopupClick(index, WP(w, menu_d).main_button); truelight@543: truelight@543: DeleteWindowById(WC_TOOLBAR_MENU, 0); rubidium@6988: } break; truelight@543: truelight@543: case WE_POPUPMENU_OVER: { truelight@543: // Our mouse hoovers over an action? Select it! belugas@4634: int index = (e->we.popupmenu.pt.y - w->top) / CLNWND_ROWSIZE; truelight@543: rubidium@8578: if (index == -1 || index == WP(w, menu_d).sel_index) return; truelight@543: rubidium@8578: WP(w, menu_d).sel_index = index; truelight@543: SetWindowDirty(w); truelight@543: } break; truelight@543: truelight@543: } truelight@543: } truelight@543: truelight@543: // Main handle for clientlist truelight@543: static void ClientListWndProc(Window *w, WindowEvent *e) truelight@543: { tron@2639: switch (e->event) { truelight@543: case WE_PAINT: { truelight@543: NetworkClientInfo *ci; truelight@543: int y, i = 0; belugas@8320: TextColour colour; truelight@543: truelight@543: // Check if we need to reset the height truelight@543: if (!CheckClientListHeight(w)) break; truelight@543: truelight@543: DrawWindowWidgets(w); truelight@543: truelight@543: y = CLNWND_OFFSET; truelight@543: Darkvater@4883: FOR_ALL_ACTIVE_CLIENT_INFOS(ci) { truelight@543: if (_selected_clientlist_item == i++) { // Selected item, highlight it truelight@543: GfxFillRect(1, y, 248, y + CLNWND_ROWSIZE - 1, 0); belugas@8320: colour = TC_WHITE; tron@4077: } else { belugas@8320: colour = TC_BLACK; tron@4077: } truelight@543: truelight@543: if (ci->client_index == NETWORK_SERVER_INDEX) { truelight@722: DrawString(4, y, STR_NETWORK_SERVER, colour); tron@2639: } else { truelight@722: DrawString(4, y, STR_NETWORK_CLIENT, colour); tron@2639: } truelight@543: truelight@543: // Filter out spectators Darkvater@4878: if (IsValidPlayer(ci->client_playas)) DrawPlayerIcon(ci->client_playas, 64, y + 1); truelight@543: Darkvater@3250: DoDrawString(ci->client_name, 81, y, colour); truelight@543: truelight@543: y += CLNWND_ROWSIZE; truelight@543: } rubidium@6988: } break; truelight@543: truelight@543: case WE_CLICK: truelight@543: // Show the popup with option truelight@543: if (_selected_clientlist_item != 255) { belugas@4634: PopupClientList(w, _selected_clientlist_item, e->we.click.pt.x + w->left, e->we.click.pt.y + w->top); truelight@0: } truelight@193: truelight@0: break; truelight@0: truelight@543: case WE_MOUSEOVER: truelight@543: // -1 means we left the current window belugas@4634: if (e->we.mouseover.pt.y == -1) { truelight@543: _selected_clientlist_y = 0; truelight@543: _selected_clientlist_item = 255; truelight@543: SetWindowDirty(w); truelight@0: break; truelight@543: } truelight@543: // It did not change.. no update! belugas@4634: if (e->we.mouseover.pt.y == _selected_clientlist_y) break; truelight@543: truelight@543: // Find the new selected item (if any) belugas@4634: _selected_clientlist_y = e->we.mouseover.pt.y; belugas@4634: if (e->we.mouseover.pt.y > CLNWND_OFFSET) { belugas@4634: _selected_clientlist_item = (e->we.mouseover.pt.y - CLNWND_OFFSET) / CLNWND_ROWSIZE; tron@4077: } else { truelight@543: _selected_clientlist_item = 255; tron@4077: } truelight@543: truelight@543: // Repaint truelight@543: SetWindowDirty(w); truelight@543: break; truelight@543: truelight@543: case WE_DESTROY: case WE_CREATE: truelight@543: // When created or destroyed, data is reset truelight@543: _selected_clientlist_item = 255; truelight@543: _selected_clientlist_y = 0; truelight@543: break; truelight@543: } truelight@543: } truelight@543: rubidium@6573: void ShowClientList() truelight@543: { tron@4491: AllocateWindowDescFront(&_client_list_desc, 0); truelight@543: } truelight@543: tron@4512: tron@4512: static NetworkPasswordType pw_type; tron@4512: tron@4512: tron@4512: void ShowNetworkNeedPassword(NetworkPasswordType npt) tron@4512: { tron@4512: StringID caption; tron@4512: tron@4512: pw_type = npt; tron@4512: switch (npt) { tron@4512: default: NOT_REACHED(); Darkvater@4774: case NETWORK_GAME_PASSWORD: caption = STR_NETWORK_NEED_GAME_PASSWORD_CAPTION; break; Darkvater@4774: case NETWORK_COMPANY_PASSWORD: caption = STR_NETWORK_NEED_COMPANY_PASSWORD_CAPTION; break; tron@4512: } Darkvater@5682: ShowQueryString(STR_EMPTY, caption, 20, 180, FindWindowById(WC_NETWORK_STATUS_WINDOW, 0), CS_ALPHANUMERAL); tron@4512: } tron@4512: tron@4512: truelight@543: static void NetworkJoinStatusWindowWndProc(Window *w, WindowEvent *e) truelight@543: { tron@2639: switch (e->event) { truelight@543: case WE_PAINT: { truelight@543: uint8 progress; // used for progress bar truelight@543: DrawWindowWidgets(w); truelight@543: belugas@8320: DrawStringCentered(125, 35, STR_NETWORK_CONNECTING_1 + _network_join_status, TC_GREY); truelight@543: switch (_network_join_status) { truelight@543: case NETWORK_JOIN_STATUS_CONNECTING: case NETWORK_JOIN_STATUS_AUTHORIZING: truelight@543: case NETWORK_JOIN_STATUS_GETTING_COMPANY_INFO: truelight@543: progress = 10; // first two stages 10% truelight@543: break; truelight@543: case NETWORK_JOIN_STATUS_WAITING: truelight@543: SetDParam(0, _network_join_waiting); belugas@8320: DrawStringCentered(125, 46, STR_NETWORK_CONNECTING_WAITING, TC_GREY); truelight@543: progress = 15; // third stage is 15% truelight@543: break; truelight@543: case NETWORK_JOIN_STATUS_DOWNLOADING: truelight@543: SetDParam(0, _network_join_kbytes); truelight@543: SetDParam(1, _network_join_kbytes_total); belugas@8320: DrawStringCentered(125, 46, STR_NETWORK_CONNECTING_DOWNLOADING, TC_GREY); truelight@543: /* Fallthrough */ truelight@543: default: /* Waiting is 15%, so the resting receivement of map is maximum 70% */ truelight@543: progress = 15 + _network_join_kbytes * (100 - 15) / _network_join_kbytes_total; truelight@543: } truelight@543: truelight@543: /* Draw nice progress bar :) */ rubidium@5838: DrawFrameRect(20, 18, (int)((w->width - 20) * progress / 100), 28, 10, FR_NONE); rubidium@6988: } break; truelight@543: truelight@543: case WE_CLICK: belugas@4634: switch (e->we.click.widget) { Darkvater@5683: case 2: /* Disconnect button */ tron@2639: NetworkDisconnect(); Darkvater@5682: DeleteWindow(w); tron@2639: SwitchMode(SM_MENU); tron@2639: ShowNetworkGameWindow(); tron@2639: break; truelight@0: } truelight@0: break; truelight@193: Darkvater@5682: /* If the server asks for a password, we need to fill it in */ tron@4512: case WE_ON_EDIT_TEXT_CANCEL: tron@4512: NetworkDisconnect(); tron@4512: ShowNetworkGameWindow(); tron@4512: break; tron@4512: tron@4512: case WE_ON_EDIT_TEXT: belugas@4634: SEND_COMMAND(PACKET_CLIENT_PASSWORD)(pw_type, e->we.edittext.str); tron@4512: break; truelight@0: } truelight@0: } truelight@0: truelight@543: static const Widget _network_join_status_window_widget[] = { Darkvater@5683: { WWT_CAPTION, RESIZE_NONE, 14, 0, 249, 0, 13, STR_NETWORK_CONNECTING, STR_018C_WINDOW_TITLE_DRAG_THIS}, Darkvater@4938: { WWT_PANEL, RESIZE_NONE, 14, 0, 249, 14, 84, 0x0, STR_NULL}, truelight@867: { WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 75, 175, 69, 80, STR_NETWORK_DISCONNECT, STR_NULL}, darkvater@176: { WIDGETS_END}, truelight@0: }; truelight@0: truelight@543: static const WindowDesc _network_join_status_window_desc = { rubidium@7837: WDP_CENTER, WDP_CENTER, 250, 85, 250, 85, rubidium@6144: WC_NETWORK_STATUS_WINDOW, WC_NONE, Darkvater@5683: WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_MODAL, truelight@543: _network_join_status_window_widget, truelight@543: NetworkJoinStatusWindowWndProc, truelight@0: }; truelight@0: rubidium@6573: void ShowJoinStatusWindow() truelight@543: { Darkvater@5683: Window *w; truelight@543: DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0); Darkvater@5683: w = AllocateWindowDesc(&_network_join_status_window_desc); Darkvater@5683: /* Parent the status window to the lobby */ Darkvater@5683: if (w != NULL) w->parent = FindWindowById(WC_NETWORK_WINDOW, 0); truelight@670: } truelight@0: rubidium@7027: static void SendChat(const char *buf, DestType type, int dest) tron@4013: { rubidium@7817: if (StrEmpty(buf)) return; tron@4013: if (!_network_server) { rubidium@5838: SEND_COMMAND(PACKET_CLIENT_CHAT)((NetworkAction)(NETWORK_ACTION_CHAT + type), type, dest, buf); tron@4013: } else { rubidium@5838: NetworkServer_HandleChat((NetworkAction)(NETWORK_ACTION_CHAT + type), type, dest, buf, NETWORK_SERVER_INDEX); tron@4013: } tron@4013: } tron@4013: truelight@4315: /** truelight@4315: * Find the next item of the list of things that can be auto-completed. truelight@4319: * @param item The current indexed item to return. This function can, and most truelight@4319: * likely will, alter item, to skip empty items in the arrays. truelight@4319: * @return Returns the char that matched to the index. truelight@4315: */ truelight@4315: static const char *ChatTabCompletionNextItem(uint *item) truelight@4315: { truelight@4315: static char chat_tab_temp_buffer[64]; truelight@4315: truelight@4315: /* First, try clients */ truelight@4315: if (*item < MAX_CLIENT_INFO) { truelight@4315: /* Skip inactive clients */ truelight@4315: while (_network_client_info[*item].client_index == NETWORK_EMPTY_INDEX && *item < MAX_CLIENT_INFO) (*item)++; truelight@4315: if (*item < MAX_CLIENT_INFO) return _network_client_info[*item].client_name; truelight@4315: } truelight@4315: truelight@4315: /* Then, try townnames */ matthijs@5247: /* Not that the following assumes all town indices are adjacent, ie no matthijs@5247: * towns have been deleted. */ matthijs@5247: if (*item <= (uint)MAX_CLIENT_INFO + GetMaxTownIndex()) { truelight@4319: const Town *t; truelight@4315: truelight@4315: FOR_ALL_TOWNS_FROM(t, *item - MAX_CLIENT_INFO) { truelight@4315: /* Get the town-name via the string-system */ glx@7452: SetDParam(0, t->index); glx@7452: GetString(chat_tab_temp_buffer, STR_TOWN, lastof(chat_tab_temp_buffer)); truelight@4315: return &chat_tab_temp_buffer[0]; truelight@4315: } truelight@4315: } truelight@4315: truelight@4315: return NULL; truelight@4315: } truelight@4315: truelight@4315: /** truelight@4315: * Find what text to complete. It scans for a space from the left and marks truelight@4315: * the word right from that as to complete. It also writes a \0 at the truelight@4315: * position of the space (if any). If nothing found, buf is returned. truelight@4315: */ truelight@4315: static char *ChatTabCompletionFindText(char *buf) truelight@4315: { Darkvater@4880: char *p = strrchr(buf, ' '); truelight@4319: if (p == NULL) return buf; truelight@4315: truelight@4319: *p = '\0'; truelight@4319: return p + 1; truelight@4315: } truelight@4315: truelight@4315: /** truelight@4315: * See if we can auto-complete the current text of the user. truelight@4315: */ truelight@4315: static void ChatTabCompletion(Window *w) truelight@4315: { truelight@4315: static char _chat_tab_completion_buf[lengthof(_edit_str_buf)]; rubidium@7027: Textbuf *tb = &WP(w, chatquerystr_d).text; truelight@4315: uint len, tb_len; truelight@4315: uint item; truelight@4315: char *tb_buf, *pre_buf; truelight@4315: const char *cur_name; truelight@4315: bool second_scan = false; truelight@4315: truelight@4315: item = 0; truelight@4315: truelight@4315: /* Copy the buffer so we can modify it without damaging the real data */ truelight@4315: pre_buf = (_chat_tab_completion_active) ? strdup(_chat_tab_completion_buf) : strdup(tb->buf); truelight@4315: truelight@4315: tb_buf = ChatTabCompletionFindText(pre_buf); truelight@4315: tb_len = strlen(tb_buf); truelight@4315: truelight@4315: while ((cur_name = ChatTabCompletionNextItem(&item)) != NULL) { truelight@4315: item++; truelight@4315: truelight@4315: if (_chat_tab_completion_active) { truelight@4315: /* We are pressing TAB again on the same name, is there an other name truelight@4315: * that starts with this? */ truelight@4315: if (!second_scan) { truelight@4315: uint offset; truelight@4315: uint length; truelight@4315: truelight@4315: /* If we are completing at the begin of the line, skip the ': ' we added */ truelight@4315: if (tb_buf == pre_buf) { truelight@4315: offset = 0; truelight@4315: length = tb->length - 2; truelight@4315: } else { truelight@4315: /* Else, find the place we are completing at */ truelight@4315: offset = strlen(pre_buf) + 1; truelight@4315: length = tb->length - offset; truelight@4315: } truelight@4315: truelight@4315: /* Compare if we have a match */ truelight@4315: if (strlen(cur_name) == length && strncmp(cur_name, tb->buf + offset, length) == 0) second_scan = true; truelight@4315: truelight@4315: continue; truelight@4315: } truelight@4315: truelight@4315: /* Now any match we make on _chat_tab_completion_buf after this, is perfect */ truelight@4315: } truelight@4315: truelight@4315: len = strlen(cur_name); truelight@4315: if (tb_len < len && strncasecmp(cur_name, tb_buf, tb_len) == 0) { truelight@4315: /* Save the data it was before completion */ truelight@4315: if (!second_scan) snprintf(_chat_tab_completion_buf, lengthof(_chat_tab_completion_buf), "%s", tb->buf); truelight@4315: _chat_tab_completion_active = true; truelight@4315: truelight@4315: /* Change to the found name. Add ': ' if we are at the start of the line (pretty) */ truelight@4315: if (pre_buf == tb_buf) { truelight@4315: snprintf(tb->buf, lengthof(_edit_str_buf), "%s: ", cur_name); truelight@4315: } else { truelight@4315: snprintf(tb->buf, lengthof(_edit_str_buf), "%s %s", pre_buf, cur_name); truelight@4315: } truelight@4315: truelight@4315: /* Update the textbuffer */ rubidium@7027: UpdateTextBufferSize(&WP(w, chatquerystr_d).text); truelight@4315: truelight@4315: SetWindowDirty(w); truelight@4315: free(pre_buf); truelight@4315: return; truelight@4315: } truelight@4315: } truelight@4315: truelight@4315: if (second_scan) { truelight@4315: /* We walked all posibilities, and the user presses tab again.. revert to original text */ truelight@4315: strcpy(tb->buf, _chat_tab_completion_buf); truelight@4315: _chat_tab_completion_active = false; truelight@4315: truelight@4315: /* Update the textbuffer */ rubidium@7027: UpdateTextBufferSize(&WP(w, chatquerystr_d).text); truelight@4315: truelight@4315: SetWindowDirty(w); truelight@4315: } truelight@4315: free(pre_buf); truelight@4315: } tron@4013: rubidium@7027: /* rubidium@7027: * uses chatquerystr_d WP macro rubidium@7027: * uses chatquerystr_d->caption to store type of chat message (Private/Team/All) rubidium@7027: */ dominik@649: static void ChatWindowWndProc(Window *w, WindowEvent *e) dominik@649: { darkvater@1648: switch (e->event) { darkvater@1648: case WE_CREATE: rubidium@8565: SendWindowMessage(WC_NEWS_WINDOW, 0, WE_CREATE, w->height, 0); rubidium@8565: SetBit(_no_scroll, SCROLL_CHAT); // do not scroll the game with the arrow-keys darkvater@1648: break; dominik@649: Darkvater@4887: case WE_PAINT: { rubidium@8565: static const StringID chat_captions[] = { rubidium@8565: STR_NETWORK_CHAT_ALL_CAPTION, rubidium@8565: STR_NETWORK_CHAT_COMPANY_CAPTION, rubidium@8565: STR_NETWORK_CHAT_CLIENT_CAPTION rubidium@8565: }; rubidium@8565: StringID msg; rubidium@8565: dominik@649: DrawWindowWidgets(w); Darkvater@4887: rubidium@8565: assert(WP(w, chatquerystr_d).caption < lengthof(chat_captions)); rubidium@8565: msg = chat_captions[WP(w, chatquerystr_d).caption]; rubidium@8565: DrawStringRightAligned(w->widget[2].left - 2, w->widget[2].top + 1, msg, TC_BLACK); rubidium@7027: DrawEditBox(w, &WP(w, chatquerystr_d), 2); Darkvater@4887: } break; dominik@649: dominik@649: case WE_CLICK: belugas@4634: switch (e->we.click.widget) { Darkvater@4888: case 3: { /* Send */ rubidium@7027: DestType type = (DestType)WP(w, chatquerystr_d).caption; rubidium@7027: int dest = WP(w, chatquerystr_d).dest; rubidium@7027: SendChat(WP(w, chatquerystr_d).text.buf, type, dest); Darkvater@4888: } /* FALLTHROUGH */ Darkvater@4887: case 0: /* Cancel */ DeleteWindow(w); break; dominik@649: } dominik@649: break; dominik@649: tron@4013: case WE_MOUSELOOP: rubidium@7027: HandleEditBox(w, &WP(w, chatquerystr_d), 2); tron@4013: break; dominik@649: tron@4013: case WE_KEYPRESS: belugas@4634: if (e->we.keypress.keycode == WKC_TAB) { truelight@4315: ChatTabCompletion(w); truelight@4315: } else { truelight@4315: _chat_tab_completion_active = false; rubidium@7027: switch (HandleEditBoxKey(w, &WP(w, chatquerystr_d), 2, e)) { Darkvater@4888: case 1: { /* Return */ rubidium@7027: DestType type = (DestType)WP(w, chatquerystr_d).caption; rubidium@7027: int dest = WP(w, chatquerystr_d).dest; rubidium@7027: SendChat(WP(w, chatquerystr_d).text.buf, type, dest); Darkvater@4888: } /* FALLTHROUGH */ truelight@4315: case 2: /* Escape */ DeleteWindow(w); break; truelight@4315: } dominik@649: } tron@4013: break; dominik@649: dominik@649: case WE_DESTROY: darkvater@1648: SendWindowMessage(WC_NEWS_WINDOW, 0, WE_DESTROY, 0, 0); skidd13@8425: ClrBit(_no_scroll, SCROLL_CHAT); dominik@649: break; dominik@649: } dominik@649: } dominik@649: dominik@649: static const Widget _chat_window_widgets[] = { rubidium@7859: { WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, rubidium@7859: { WWT_PANEL, RESIZE_RIGHT, 14, 11, 319, 0, 13, 0x0, STR_NULL}, // background rubidium@7859: { WWT_PANEL, RESIZE_RIGHT, 14, 75, 257, 1, 12, 0x0, STR_NULL}, // text box rubidium@7859: { WWT_PUSHTXTBTN, RESIZE_LR, 14, 258, 319, 1, 12, STR_NETWORK_SEND, STR_NULL}, // send button dominik@649: { WIDGETS_END}, dominik@649: }; dominik@649: dominik@649: static const WindowDesc _chat_window_desc = { rubidium@7859: WDP_CENTER, -26, 320, 14, 640, 14, // x, y, width, height rubidium@6144: WC_SEND_NETWORK_MSG, WC_NONE, belugas@8515: WDF_STD_TOOLTIPS | WDF_DEF_WIDGET, dominik@649: _chat_window_widgets, dominik@649: ChatWindowWndProc dominik@649: }; dominik@649: rubidium@7027: void ShowNetworkChatQueryWindow(DestType type, int dest) dominik@649: { dominik@649: Window *w; dominik@649: dominik@649: DeleteWindowById(WC_SEND_NETWORK_MSG, 0); dominik@649: tron@3464: _edit_str_buf[0] = '\0'; truelight@4315: _chat_tab_completion_active = false; dominik@649: dominik@649: w = AllocateWindowDesc(&_chat_window_desc); dominik@649: rubidium@8493: w->LowerWidget(2); rubidium@7027: WP(w, chatquerystr_d).caption = type; // Misuse of caption rubidium@7027: WP(w, chatquerystr_d).dest = dest; rubidium@7027: WP(w, chatquerystr_d).afilter = CS_ALPHANUMERAL; rubidium@7027: InitializeTextBuffer(&WP(w, chatquerystr_d).text, _edit_str_buf, lengthof(_edit_str_buf), 0); dominik@649: } dominik@649: rubidium@8494: /** Enum for NetworkGameWindow, referring to _network_game_window_widgets */ rubidium@8494: enum NetworkCompanyPasswordWindowWidgets { rubidium@8494: NCPWW_CLOSE, ///< Close 'X' button rubidium@8494: NCPWW_CAPTION, ///< Caption of the whole window rubidium@8494: NCPWW_BACKGROUND, ///< The background of the interface rubidium@8494: NCPWW_LABEL, ///< Label in front of the password field rubidium@8494: NCPWW_PASSWORD, ///< Input field for the password rubidium@8494: NCPWW_SAVE_AS_DEFAULT_PASSWORD, ///< Toggle 'button' for saving the current password as default password rubidium@8494: NCPWW_CANCEL, ///< Close the window without changing anything rubidium@8494: NCPWW_OK, ///< Safe the password etc. rubidium@8494: }; rubidium@8494: rubidium@8494: static void NetworkCompanyPasswordWindowWndProc(Window *w, WindowEvent *e) rubidium@8494: { rubidium@8494: switch (e->event) { rubidium@8494: case WE_PAINT: rubidium@8494: DrawWindowWidgets(w); rubidium@8494: DrawEditBox(w, &WP(w, chatquerystr_d), 4); rubidium@8494: break; rubidium@8494: rubidium@8494: case WE_CLICK: rubidium@8494: switch (e->we.click.widget) { rubidium@8494: case NCPWW_OK: { rubidium@8494: if (w->IsWidgetLowered(NCPWW_SAVE_AS_DEFAULT_PASSWORD)) { rubidium@8494: snprintf(_network_default_company_pass, lengthof(_network_default_company_pass), "%s", _edit_str_buf); rubidium@8494: } rubidium@8494: rubidium@8494: /* empty password is a '*' because of console argument */ rubidium@8494: if (StrEmpty(_edit_str_buf)) snprintf(_edit_str_buf, lengthof(_edit_str_buf), "*"); rubidium@8494: char *password = _edit_str_buf; rubidium@8494: NetworkChangeCompanyPassword(1, &password); rubidium@8494: } rubidium@8494: rubidium@8494: /* FALL THROUGH */ rubidium@8494: case NCPWW_CANCEL: rubidium@8494: DeleteWindow(w); rubidium@8494: break; rubidium@8494: rubidium@8494: case NCPWW_SAVE_AS_DEFAULT_PASSWORD: rubidium@8494: w->ToggleWidgetLoweredState(NCPWW_SAVE_AS_DEFAULT_PASSWORD); rubidium@8494: SetWindowDirty(w); rubidium@8494: break; rubidium@8494: } rubidium@8494: break; rubidium@8494: rubidium@8494: case WE_MOUSELOOP: rubidium@8494: HandleEditBox(w, &WP(w, chatquerystr_d), 4); rubidium@8494: break; rubidium@8494: rubidium@8494: case WE_KEYPRESS: rubidium@8494: switch (HandleEditBoxKey(w, &WP(w, chatquerystr_d), 4, e)) { rubidium@8494: case 1: // Return rubidium@8583: e->event = WE_CLICK; rubidium@8583: e->we.click.widget = NCPWW_OK; rubidium@8583: NetworkCompanyPasswordWindowWndProc(w, e); rubidium@8583: break; rubidium@8583: rubidium@8494: case 2: // Escape rubidium@8494: DeleteWindow(w); rubidium@8494: break; rubidium@8494: } rubidium@8494: break; rubidium@8494: } rubidium@8494: } rubidium@8494: rubidium@8494: static const Widget _ncp_window_widgets[] = { rubidium@8494: { WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, rubidium@8494: { WWT_CAPTION, RESIZE_NONE, 14, 11, 299, 0, 13, STR_COMPANY_PASSWORD_CAPTION, STR_018C_WINDOW_TITLE_DRAG_THIS}, rubidium@8494: { WWT_PANEL, RESIZE_NONE, 14, 0, 299, 14, 50, 0x0, STR_NULL}, rubidium@8494: { WWT_TEXT, RESIZE_NONE, 14, 5, 100, 19, 30, STR_COMPANY_PASSWORD, STR_NULL}, rubidium@8494: { WWT_PANEL, RESIZE_NONE, 14, 101, 294, 19, 30, 0x0, STR_NULL}, rubidium@8494: { WWT_TEXTBTN, RESIZE_NONE, 14, 101, 294, 35, 46, STR_MAKE_DEFAULT_COMPANY_PASSWORD, STR_MAKE_DEFAULT_COMPANY_PASSWORD_TIP}, rubidium@8494: { WWT_PUSHTXTBTN, RESIZE_NONE, 14, 0, 149, 51, 62, STR_012E_CANCEL, STR_COMPANY_PASSWORD_CANCEL}, rubidium@8494: { WWT_PUSHTXTBTN, RESIZE_NONE, 14, 150, 299, 51, 62, STR_012F_OK, STR_COMPANY_PASSWORD_OK}, rubidium@8494: { WIDGETS_END}, rubidium@8494: }; rubidium@8494: rubidium@8494: static const WindowDesc _ncp_window_desc = { rubidium@8494: WDP_AUTO, WDP_AUTO, 300, 63, 300, 63, rubidium@8494: WC_COMPANY_PASSWORD_WINDOW, WC_NONE, belugas@8515: WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON, rubidium@8494: _ncp_window_widgets, rubidium@8494: NetworkCompanyPasswordWindowWndProc rubidium@8494: }; rubidium@8494: rubidium@8494: void ShowNetworkCompanyPasswordWindow() rubidium@8494: { rubidium@8494: DeleteWindowById(WC_COMPANY_PASSWORD_WINDOW, 0); rubidium@8494: rubidium@8494: _edit_str_buf[0] = '\0'; rubidium@8494: Window *w = AllocateWindowDesc(&_ncp_window_desc); rubidium@8494: WP(w, chatquerystr_d).afilter = CS_ALPHANUMERAL; rubidium@8494: InitializeTextBuffer(&WP(w, chatquerystr_d).text, _edit_str_buf, min(lengthof(_network_default_company_pass), lengthof(_edit_str_buf)), 0); rubidium@8494: } rubidium@8494: truelight@543: #endif /* ENABLE_NETWORK */