From b799a441512cb8a648277228ee12451a11939c9c Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Tue, 10 Aug 2021 19:09:32 +0200 Subject: [PATCH 1/5] Feature: make "join game" button join the game, instead of first showing a lobby window Nobody really paid attention to the lobby window, and it completely missed its purpose. Most people don't even wait for companies to show up, but just hit "New Company". This in turn means people create a lot of unneeded companies, while they "just want to watch the game" or join another company. Instead, "Join Game" now just joins the game as spectators. --- src/lang/english.txt | 25 --- src/network/core/tcp_game.h | 2 +- src/network/network.cpp | 36 ---- src/network/network_client.cpp | 42 +--- src/network/network_gui.cpp | 322 +----------------------------- src/network/network_gui.h | 2 - src/network/network_internal.h | 1 - src/script/api/game_changelog.hpp | 8 + src/widgets/network_widget.h | 15 -- src/window_type.h | 2 - 10 files changed, 12 insertions(+), 443 deletions(-) diff --git a/src/lang/english.txt b/src/lang/english.txt index eb7b7365bdbe..f740d6c8eeab 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2084,31 +2084,6 @@ STR_NETWORK_START_SERVER_LANGUAGE_TOOLTIP :{BLACK}Other pl STR_NETWORK_START_SERVER_NEW_GAME_NAME_OSKTITLE :{BLACK}Enter a name for the network game -# Network game lobby -STR_NETWORK_GAME_LOBBY_CAPTION :{WHITE}Multiplayer game lobby - -STR_NETWORK_GAME_LOBBY_PREPARE_TO_JOIN :{BLACK}Preparing to join: {ORANGE}{RAW_STRING} -STR_NETWORK_GAME_LOBBY_COMPANY_LIST_TOOLTIP :{BLACK}A list of all companies currently in this game. You can either join one or start a new one if there is a free company slot - -STR_NETWORK_GAME_LOBBY_COMPANY_INFO :{SILVER}COMPANY INFO -STR_NETWORK_GAME_LOBBY_COMPANY_NAME :{SILVER}Company name: {WHITE}{RAW_STRING} -STR_NETWORK_GAME_LOBBY_INAUGURATION_YEAR :{SILVER}Inauguration: {WHITE}{NUM} -STR_NETWORK_GAME_LOBBY_VALUE :{SILVER}Company value: {WHITE}{CURRENCY_LONG} -STR_NETWORK_GAME_LOBBY_CURRENT_BALANCE :{SILVER}Current balance: {WHITE}{CURRENCY_LONG} -STR_NETWORK_GAME_LOBBY_LAST_YEARS_INCOME :{SILVER}Last year's income: {WHITE}{CURRENCY_LONG} -STR_NETWORK_GAME_LOBBY_PERFORMANCE :{SILVER}Performance: {WHITE}{NUM} - -STR_NETWORK_GAME_LOBBY_VEHICLES :{SILVER}Vehicles: {WHITE}{NUM} {TRAIN}, {NUM} {LORRY}, {NUM} {BUS}, {NUM} {SHIP}, {NUM} {PLANE} -STR_NETWORK_GAME_LOBBY_STATIONS :{SILVER}Stations: {WHITE}{NUM} {TRAIN}, {NUM} {LORRY}, {NUM} {BUS}, {NUM} {SHIP}, {NUM} {PLANE} -STR_NETWORK_GAME_LOBBY_PLAYERS :{SILVER}Players: {WHITE}{RAW_STRING} - -STR_NETWORK_GAME_LOBBY_NEW_COMPANY :{BLACK}New company -STR_NETWORK_GAME_LOBBY_NEW_COMPANY_TOOLTIP :{BLACK}Create a new company -STR_NETWORK_GAME_LOBBY_SPECTATE_GAME :{BLACK}Spectate game -STR_NETWORK_GAME_LOBBY_SPECTATE_GAME_TOOLTIP :{BLACK}Watch the game as a spectator -STR_NETWORK_GAME_LOBBY_JOIN_COMPANY :{BLACK}Join company -STR_NETWORK_GAME_LOBBY_JOIN_COMPANY_TOOLTIP :{BLACK}Help manage this company - # Network connecting window STR_NETWORK_CONNECTING_CAPTION :{WHITE}Connecting... diff --git a/src/network/core/tcp_game.h b/src/network/core/tcp_game.h index 90b7184f0ad3..cbef799cf9ef 100644 --- a/src/network/core/tcp_game.h +++ b/src/network/core/tcp_game.h @@ -38,7 +38,7 @@ enum PacketGameType { PACKET_CLIENT_JOIN, ///< The client telling the server it wants to join. PACKET_SERVER_ERROR, ///< Server sending an error message to the client. - /* Packets used for the pre-game lobby. */ + /* Packets used for the pre-game lobby (unused, but remain for backward/forward compatibility). */ PACKET_CLIENT_COMPANY_INFO, ///< Request information about all companies. PACKET_SERVER_COMPANY_INFO, ///< Information about a single company. diff --git a/src/network/network.cpp b/src/network/network.cpp index 3bce6d4d5dae..929e50450f0d 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -657,42 +657,6 @@ void NetworkQueryServer(const std::string &connection_string) new TCPQueryConnecter(connection_string); } -/** Non blocking connection to query servers for their game and company info. */ -class TCPLobbyQueryConnecter : TCPServerConnecter { -private: - std::string connection_string; - -public: - TCPLobbyQueryConnecter(const std::string &connection_string) : TCPServerConnecter(connection_string, NETWORK_DEFAULT_PORT), connection_string(connection_string) {} - - void OnFailure() override - { - CloseWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_LOBBY); - - ShowErrorMessage(STR_NETWORK_ERROR_NOCONNECTION, INVALID_STRING_ID, WL_ERROR); - } - - void OnConnect(SOCKET s) override - { - _networking = true; - new ClientNetworkGameSocketHandler(s, this->connection_string); - MyClient::SendInformationQuery(true); - } -}; - -/** - * Query a server to fetch the game-info for the lobby. - * @param connection_string the address to query. - */ -void NetworkQueryLobbyServer(const std::string &connection_string) -{ - if (!_network_available) return; - - NetworkInitialize(); - - new TCPLobbyQueryConnecter(connection_string); -} - /** * Validates an address entered as a string and adds the server to * the list. If you use this function, the games will be marked diff --git a/src/network/network_client.cpp b/src/network/network_client.cpp index 16fd31cfe749..5c4d2c4ee9fd 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -567,11 +567,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_GAME_INFO(Packe { if (this->status != STATUS_COMPANY_INFO && this->status != STATUS_GAME_INFO) return NETWORK_RECV_STATUS_MALFORMED_PACKET; - NetworkGameList *item = GetLobbyGameInfo(); - if (item == nullptr) { - /* This is not the lobby, so add it to the game list. */ - item = NetworkGameListAddItem(this->connection_string); - } + NetworkGameList *item = NetworkGameListAddItem(this->connection_string); /* Clear any existing GRFConfig chain. */ ClearGRFConfigList(&item->info.grfconfig); @@ -582,9 +578,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_GAME_INFO(Packe /* Ensure we consider the server online. */ item->online = true; - /* It could be either window, but only one is open, so redraw both. */ UpdateNetworkGameWindow(); - SetWindowDirty(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_LOBBY); /* We will receive company info next, so keep connection open. */ if (this->status == STATUS_COMPANY_INFO) return NETWORK_RECV_STATUS_OKAY; @@ -595,39 +589,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_COMPANY_INFO(Pa { if (this->status != STATUS_COMPANY_INFO) return NETWORK_RECV_STATUS_MALFORMED_PACKET; - byte company_info_version = p->Recv_uint8(); - - if (!this->HasClientQuit() && company_info_version == NETWORK_COMPANY_INFO_VERSION) { - /* We have received all data... (there are no more packets coming) */ - if (!p->Recv_bool()) return NETWORK_RECV_STATUS_CLOSE_QUERY; - - CompanyID current = (Owner)p->Recv_uint8(); - if (current >= MAX_COMPANIES) return NETWORK_RECV_STATUS_CLOSE_QUERY; - - NetworkCompanyInfo *company_info = GetLobbyCompanyInfo(current); - if (company_info == nullptr) return NETWORK_RECV_STATUS_CLOSE_QUERY; - - company_info->company_name = p->Recv_string(NETWORK_COMPANY_NAME_LENGTH); - company_info->inaugurated_year = p->Recv_uint32(); - company_info->company_value = p->Recv_uint64(); - company_info->money = p->Recv_uint64(); - company_info->income = p->Recv_uint64(); - company_info->performance = p->Recv_uint16(); - company_info->use_password = p->Recv_bool(); - for (uint i = 0; i < NETWORK_VEH_END; i++) { - company_info->num_vehicle[i] = p->Recv_uint16(); - } - for (uint i = 0; i < NETWORK_VEH_END; i++) { - company_info->num_station[i] = p->Recv_uint16(); - } - company_info->ai = p->Recv_bool(); - - company_info->clients = p->Recv_string(NETWORK_CLIENTS_LENGTH); - - SetWindowDirty(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_LOBBY); - - return NETWORK_RECV_STATUS_OKAY; - } + /* Unused, but this packet is part of the "this will never change" packet group. */ return NETWORK_RECV_STATUS_CLOSE_QUERY; } diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp index 076b3d46ec3c..8b41a863dde4 100644 --- a/src/network/network_gui.cpp +++ b/src/network/network_gui.cpp @@ -53,7 +53,6 @@ #include "../safeguards.h" static void ShowNetworkStartServerWindow(); -static void ShowNetworkLobbyWindow(NetworkGameList *ngl); static const int NETWORK_LIST_REFRESH_DELAY = 30; ///< Time, in seconds, between updates of the network list. @@ -770,7 +769,7 @@ class NetworkGameWindow : public Window { case WID_NG_JOIN: // Join Game if (this->server != nullptr) { - ShowNetworkLobbyWindow(this->server); + NetworkClientConnectGame(this->server->connection_string, COMPANY_SPECTATOR); } break; @@ -990,7 +989,6 @@ static WindowDesc _network_game_window_desc( void ShowNetworkGameWindow() { static bool first = true; - CloseWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_LOBBY); CloseWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_START); /* Only show once */ @@ -1266,328 +1264,10 @@ static void ShowNetworkStartServerWindow() if (!NetworkValidateOurClientName()) return; CloseWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_GAME); - CloseWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_LOBBY); new NetworkStartServerWindow(&_network_start_server_window_desc); } -struct NetworkLobbyWindow : public Window { - CompanyID company; ///< Selected company - NetworkGameList *server; ///< Selected server - NetworkCompanyInfo company_info[MAX_COMPANIES]; - Scrollbar *vscroll; - - NetworkLobbyWindow(WindowDesc *desc, NetworkGameList *ngl) : - Window(desc), company(INVALID_COMPANY), server(ngl) - { - this->CreateNestedTree(); - this->vscroll = this->GetScrollbar(WID_NL_SCROLLBAR); - this->FinishInitNested(WN_NETWORK_WINDOW_LOBBY); - } - - CompanyID NetworkLobbyFindCompanyIndex(byte pos) const - { - /* Scroll through all this->company_info and get the 'pos' item that is not empty. */ - for (CompanyID i = COMPANY_FIRST; i < MAX_COMPANIES; i++) { - if (!this->company_info[i].company_name.empty()) { - if (pos-- == 0) return i; - } - } - - return COMPANY_FIRST; - } - - void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override - { - switch (widget) { - case WID_NL_HEADER: - size->height = WD_MATRIX_TOP + FONT_HEIGHT_NORMAL + WD_MATRIX_BOTTOM; - break; - - case WID_NL_MATRIX: - resize->height = WD_MATRIX_TOP + std::max(std::max(GetSpriteSize(SPR_LOCK).height, GetSpriteSize(SPR_PROFIT_LOT).height), FONT_HEIGHT_NORMAL) + WD_MATRIX_BOTTOM; - size->height = 10 * resize->height; - break; - - case WID_NL_DETAILS: - size->height = 30 + 11 * FONT_HEIGHT_NORMAL; - break; - } - } - - void SetStringParameters(int widget) const override - { - switch (widget) { - case WID_NL_TEXT: - SetDParamStr(0, this->server->info.server_name); - break; - } - } - - void DrawWidget(const Rect &r, int widget) const override - { - switch (widget) { - case WID_NL_DETAILS: - this->DrawDetails(r); - break; - - case WID_NL_MATRIX: - this->DrawMatrix(r); - break; - } - } - - void OnPaint() override - { - const NetworkGameInfo *gi = &this->server->info; - - /* Join button is disabled when no company is selected and for AI companies. */ - this->SetWidgetDisabledState(WID_NL_JOIN, this->company == INVALID_COMPANY || GetLobbyCompanyInfo(this->company)->ai); - /* Cannot start new company if there are too many. */ - this->SetWidgetDisabledState(WID_NL_NEW, gi->companies_on >= gi->companies_max); - - this->vscroll->SetCount(gi->companies_on); - - /* Draw window widgets */ - this->DrawWidgets(); - } - - void DrawMatrix(const Rect &r) const - { - bool rtl = _current_text_dir == TD_RTL; - uint left = r.left + WD_FRAMERECT_LEFT; - uint right = r.right - WD_FRAMERECT_RIGHT; - uint text_offset = (this->resize.step_height - WD_MATRIX_TOP - WD_MATRIX_BOTTOM - FONT_HEIGHT_NORMAL) / 2 + WD_MATRIX_TOP; - - Dimension lock_size = GetSpriteSize(SPR_LOCK); - int lock_width = lock_size.width; - int lock_y_offset = (this->resize.step_height - WD_MATRIX_TOP - WD_MATRIX_BOTTOM - lock_size.height) / 2 + WD_MATRIX_TOP; - - Dimension profit_size = GetSpriteSize(SPR_PROFIT_LOT); - int profit_width = lock_size.width; - int profit_y_offset = (this->resize.step_height - WD_MATRIX_TOP - WD_MATRIX_BOTTOM - profit_size.height) / 2 + WD_MATRIX_TOP; - - uint text_left = left + (rtl ? lock_width + profit_width + 4 : 0); - uint text_right = right - (rtl ? 0 : lock_width + profit_width + 4); - uint profit_left = rtl ? left : right - profit_width; - uint lock_left = rtl ? left + profit_width + 2 : right - profit_width - lock_width - 2; - - int y = r.top; - /* Draw company list */ - int pos = this->vscroll->GetPosition(); - while (pos < this->server->info.companies_on) { - byte company = NetworkLobbyFindCompanyIndex(pos); - bool income = false; - if (this->company == company) { - GfxFillRect(r.left + WD_BEVEL_LEFT, y + 1, r.right - WD_BEVEL_RIGHT, y + this->resize.step_height - 2, PC_GREY); // show highlighted item with a different colour - } - - DrawString(text_left, text_right, y + text_offset, this->company_info[company].company_name, TC_BLACK); - if (this->company_info[company].use_password != 0) DrawSprite(SPR_LOCK, PAL_NONE, lock_left, y + lock_y_offset); - - /* If the company's income was positive puts a green dot else a red dot */ - if (this->company_info[company].income >= 0) income = true; - DrawSprite(income ? SPR_PROFIT_LOT : SPR_PROFIT_NEGATIVE, PAL_NONE, profit_left, y + profit_y_offset); - - pos++; - y += this->resize.step_height; - if (pos >= this->vscroll->GetPosition() + this->vscroll->GetCapacity()) break; - } - } - - void DrawDetails(const Rect &r) const - { - const int detail_height = 12 + FONT_HEIGHT_NORMAL + 12; - /* Draw info about selected company when it is selected in the left window. */ - GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.top + detail_height - 1, PC_DARK_BLUE); - DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + 12, STR_NETWORK_GAME_LOBBY_COMPANY_INFO, TC_FROMSTRING, SA_HOR_CENTER); - - if (this->company == INVALID_COMPANY || this->company_info[this->company].company_name.empty()) return; - - int y = r.top + detail_height + 4; - const NetworkGameInfo *gi = &this->server->info; - - SetDParam(0, gi->clients_on); - SetDParam(1, gi->clients_max); - SetDParam(2, gi->companies_on); - SetDParam(3, gi->companies_max); - DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_CLIENTS); - y += FONT_HEIGHT_NORMAL; - - SetDParamStr(0, this->company_info[this->company].company_name); - DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_COMPANY_NAME); - y += FONT_HEIGHT_NORMAL; - - SetDParam(0, this->company_info[this->company].inaugurated_year); - DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_INAUGURATION_YEAR); // inauguration year - y += FONT_HEIGHT_NORMAL; - - SetDParam(0, this->company_info[this->company].company_value); - DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_VALUE); // company value - y += FONT_HEIGHT_NORMAL; - - SetDParam(0, this->company_info[this->company].money); - DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_CURRENT_BALANCE); // current balance - y += FONT_HEIGHT_NORMAL; - - SetDParam(0, this->company_info[this->company].income); - DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_LAST_YEARS_INCOME); // last year's income - y += FONT_HEIGHT_NORMAL; - - SetDParam(0, this->company_info[this->company].performance); - DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_PERFORMANCE); // performance - y += FONT_HEIGHT_NORMAL; - - SetDParam(0, this->company_info[this->company].num_vehicle[NETWORK_VEH_TRAIN]); - SetDParam(1, this->company_info[this->company].num_vehicle[NETWORK_VEH_LORRY]); - SetDParam(2, this->company_info[this->company].num_vehicle[NETWORK_VEH_BUS]); - SetDParam(3, this->company_info[this->company].num_vehicle[NETWORK_VEH_SHIP]); - SetDParam(4, this->company_info[this->company].num_vehicle[NETWORK_VEH_PLANE]); - DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_VEHICLES); // vehicles - y += FONT_HEIGHT_NORMAL; - - SetDParam(0, this->company_info[this->company].num_station[NETWORK_VEH_TRAIN]); - SetDParam(1, this->company_info[this->company].num_station[NETWORK_VEH_LORRY]); - SetDParam(2, this->company_info[this->company].num_station[NETWORK_VEH_BUS]); - SetDParam(3, this->company_info[this->company].num_station[NETWORK_VEH_SHIP]); - SetDParam(4, this->company_info[this->company].num_station[NETWORK_VEH_PLANE]); - DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_STATIONS); // stations - y += FONT_HEIGHT_NORMAL; - - SetDParamStr(0, this->company_info[this->company].clients); - DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_GAME_LOBBY_PLAYERS); // players - } - - void OnClick(Point pt, int widget, int click_count) override - { - switch (widget) { - case WID_NL_CANCEL: // Cancel button - ShowNetworkGameWindow(); - break; - - case WID_NL_MATRIX: { // Company list - uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NL_MATRIX); - this->company = (id_v >= this->server->info.companies_on) ? INVALID_COMPANY : NetworkLobbyFindCompanyIndex(id_v); - this->SetDirty(); - - /* FIXME the disabling should go into some InvalidateData, which is called instead of the SetDirty */ - if (click_count > 1 && !this->IsWidgetDisabled(WID_NL_JOIN)) this->OnClick(pt, WID_NL_JOIN, 1); - break; - } - - case WID_NL_JOIN: // Join company - /* Button can be clicked only when it is enabled. */ - NetworkClientConnectGame(this->server->connection_string, this->company); - break; - - case WID_NL_NEW: // New company - NetworkClientConnectGame(this->server->connection_string, COMPANY_NEW_COMPANY); - break; - - case WID_NL_SPECTATE: // Spectate game - NetworkClientConnectGame(this->server->connection_string, COMPANY_SPECTATOR); - break; - - case WID_NL_REFRESH: // Refresh - /* Clear the information so removed companies don't remain */ - for (auto &company : this->company_info) company = {}; - - NetworkQueryLobbyServer(this->server->connection_string); - break; - } - } - - void OnResize() override - { - this->vscroll->SetCapacityFromWidget(this, WID_NL_MATRIX); - } -}; - -static const NWidgetPart _nested_network_lobby_window_widgets[] = { - NWidget(NWID_HORIZONTAL), - NWidget(WWT_CLOSEBOX, COLOUR_LIGHT_BLUE), - NWidget(WWT_CAPTION, COLOUR_LIGHT_BLUE), SetDataTip(STR_NETWORK_GAME_LOBBY_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), - EndContainer(), - NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, WID_NL_BACKGROUND), - NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, WID_NL_TEXT), SetDataTip(STR_NETWORK_GAME_LOBBY_PREPARE_TO_JOIN, STR_NULL), SetResize(1, 0), SetPadding(10, 10, 0, 10), - NWidget(NWID_SPACER), SetMinimalSize(0, 3), - NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 10), - /* Company list. */ - NWidget(NWID_VERTICAL), - NWidget(WWT_PANEL, COLOUR_WHITE, WID_NL_HEADER), SetMinimalSize(146, 0), SetResize(1, 0), SetFill(1, 0), EndContainer(), - NWidget(WWT_MATRIX, COLOUR_LIGHT_BLUE, WID_NL_MATRIX), SetMinimalSize(146, 0), SetResize(1, 1), SetFill(1, 1), SetMatrixDataTip(1, 0, STR_NETWORK_GAME_LOBBY_COMPANY_LIST_TOOLTIP), SetScrollbar(WID_NL_SCROLLBAR), - EndContainer(), - NWidget(NWID_VSCROLLBAR, COLOUR_LIGHT_BLUE, WID_NL_SCROLLBAR), - NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetResize(0, 1), - /* Company info. */ - NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, WID_NL_DETAILS), SetMinimalSize(232, 0), SetResize(1, 1), SetFill(1, 1), EndContainer(), - EndContainer(), - NWidget(NWID_SPACER), SetMinimalSize(0, 9), - /* Buttons. */ - NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 3, 10), - NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, 3, 0), - NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NL_JOIN), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_NETWORK_GAME_LOBBY_JOIN_COMPANY, STR_NETWORK_GAME_LOBBY_JOIN_COMPANY_TOOLTIP), - NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NL_NEW), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_NETWORK_GAME_LOBBY_NEW_COMPANY, STR_NETWORK_GAME_LOBBY_NEW_COMPANY_TOOLTIP), - EndContainer(), - NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, 3, 0), - NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NL_SPECTATE), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_NETWORK_GAME_LOBBY_SPECTATE_GAME, STR_NETWORK_GAME_LOBBY_SPECTATE_GAME_TOOLTIP), - NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NL_REFRESH), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_NETWORK_SERVER_LIST_REFRESH, STR_NETWORK_SERVER_LIST_REFRESH_TOOLTIP), - EndContainer(), - NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, 3, 0), - NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NL_CANCEL), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_BUTTON_CANCEL, STR_NULL), - NWidget(NWID_SPACER), SetFill(1, 1), - EndContainer(), - EndContainer(), - NWidget(NWID_SPACER), SetMinimalSize(0, 8), - EndContainer(), -}; - -static WindowDesc _network_lobby_window_desc( - WDP_CENTER, nullptr, 0, 0, - WC_NETWORK_WINDOW, WC_NONE, - 0, - _nested_network_lobby_window_widgets, lengthof(_nested_network_lobby_window_widgets) -); - -/** - * Show the networklobbywindow with the selected server. - * @param ngl Selected game pointer which is passed to the new window. - */ -static void ShowNetworkLobbyWindow(NetworkGameList *ngl) -{ - if (!NetworkValidateOurClientName()) return; - - CloseWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_START); - CloseWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_GAME); - - _settings_client.network.last_joined = ngl->connection_string; - - NetworkQueryLobbyServer(ngl->connection_string); - - new NetworkLobbyWindow(&_network_lobby_window_desc, ngl); -} - -/** - * Get the company information of a given company to fill for the lobby. - * @param company the company to get the company info struct from. - * @return the company info struct to write the (downloaded) data to. - */ -NetworkCompanyInfo *GetLobbyCompanyInfo(CompanyID company) -{ - NetworkLobbyWindow *lobby = dynamic_cast(FindWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_LOBBY)); - return (lobby != nullptr && company < MAX_COMPANIES) ? &lobby->company_info[company] : nullptr; -} - -/** - * Get the game information for the lobby. - * @return the game info struct to write the (downloaded) data to. - */ -NetworkGameList *GetLobbyGameInfo() -{ - NetworkLobbyWindow *lobby = dynamic_cast(FindWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_LOBBY)); - return lobby != nullptr ? lobby->server : nullptr; -} - /* The window below gives information about the connected clients * and also makes able to kick them (if server) and stuff like that. */ diff --git a/src/network/network_gui.h b/src/network/network_gui.h index 06b501cb5dc8..c3194edaf995 100644 --- a/src/network/network_gui.h +++ b/src/network/network_gui.h @@ -37,8 +37,6 @@ struct NetworkCompanyInfo : NetworkCompanyStats { std::string clients; ///< The clients that control this company (Name1, name2, ..) }; -NetworkCompanyInfo *GetLobbyCompanyInfo(CompanyID company); -NetworkGameList *GetLobbyGameInfo(); void ShowNetworkAskRelay(const std::string &connection_string, const std::string &token); #endif /* NETWORK_GUI_H */ diff --git a/src/network/network_internal.h b/src/network/network_internal.h index 2c6639961dd0..1f1964d4c8a9 100644 --- a/src/network/network_internal.h +++ b/src/network/network_internal.h @@ -91,7 +91,6 @@ extern uint8 _network_reconnect; extern CompanyMask _network_company_passworded; void NetworkQueryServer(const std::string &connection_string); -void NetworkQueryLobbyServer(const std::string &connection_string); void GetBindAddresses(NetworkAddressList *addresses, uint16 port); struct NetworkGameList *NetworkAddServer(const std::string &connection_string, bool manually = true, bool never_expire = false); diff --git a/src/script/api/game_changelog.hpp b/src/script/api/game_changelog.hpp index 5d214efb125b..050022cc40aa 100644 --- a/src/script/api/game_changelog.hpp +++ b/src/script/api/game_changelog.hpp @@ -21,6 +21,14 @@ * \li GSNewGRF * \li GSNewGRFList * + * API removals: + * \li GSWindow::NetworkStartServerWidgets::WID_NSS_SPECTATORS_LABEL + * \li GSWindow::NetworkStartServerWidgets::WID_NSS_SPECTATORS_BTND + * \li GSWindow::NetworkStartServerWidgets::WID_NSS_SPECTATORS_TXT + * \li GSWindow::NetworkStartServerWidgets::WID_NSS_SPECTATORS_BTNU + * \li GSWindow::WindowNumberEnum::WN_NETWORK_WINDOW_LOBBY + * \li GSWindow::NetworkLobbyWidgets + * * \b 1.11.0 * * API additions: diff --git a/src/widgets/network_widget.h b/src/widgets/network_widget.h index ea98912763ff..f50bf04e9338 100644 --- a/src/widgets/network_widget.h +++ b/src/widgets/network_widget.h @@ -75,21 +75,6 @@ enum NetworkStartServerWidgets { WID_NSS_CANCEL, ///< 'Cancel' button. }; -/** Widgets of the #NetworkLobbyWindow class. */ -enum NetworkLobbyWidgets { - WID_NL_BACKGROUND, ///< Background of the window. - WID_NL_TEXT, ///< Heading text. - WID_NL_HEADER, ///< Header above list of companies. - WID_NL_MATRIX, ///< List of companies. - WID_NL_SCROLLBAR, ///< Scroll bar. - WID_NL_DETAILS, ///< Company details. - WID_NL_JOIN, ///< 'Join company' button. - WID_NL_NEW, ///< 'New company' button. - WID_NL_SPECTATE, ///< 'Spectate game' button. - WID_NL_REFRESH, ///< 'Refresh server' button. - WID_NL_CANCEL, ///< 'Cancel' button. -}; - /** Widgets of the #NetworkClientListWindow class. */ enum ClientListWidgets { WID_CL_PANEL, ///< Panel of the window. diff --git a/src/window_type.h b/src/window_type.h index 00aaaf1fde26..6eb9573b609a 100644 --- a/src/window_type.h +++ b/src/window_type.h @@ -25,7 +25,6 @@ enum WindowNumberEnum { WN_CONFIRM_POPUP_QUERY_BOOTSTRAP, ///< Query popup confirm for bootstrap. WN_NETWORK_WINDOW_GAME = 0, ///< Network game window. - WN_NETWORK_WINDOW_LOBBY, ///< Network lobby window. WN_NETWORK_WINDOW_CONTENT_LIST, ///< Network content list. WN_NETWORK_WINDOW_START, ///< Network start server. @@ -459,7 +458,6 @@ enum WindowClass { /** * Network window; %Window numbers: * - #WN_NETWORK_WINDOW_GAME = #NetworkGameWidgets - * - #WN_NETWORK_WINDOW_LOBBY = #NetworkLobbyWidgets * - #WN_NETWORK_WINDOW_CONTENT_LIST = #NetworkContentListWidgets * - #WN_NETWORK_WINDOW_START = #NetworkStartServerWidgets */ From a39cd7dd615ef87014434601397a5dd9f355d43c Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Tue, 10 Aug 2021 19:34:02 +0200 Subject: [PATCH 2/5] Feature: open Online Players GUI on joining a server Hopefully this hints players a bit more where to go to next, to create their own company. --- src/network/network_client.cpp | 2 ++ src/network/network_gui.cpp | 29 ++++++++++++++++------------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/network/network_client.cpp b/src/network/network_client.cpp index 5c4d2c4ee9fd..06af6a859dc1 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -902,6 +902,8 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_MAP_DONE(Packet SetLocalCompany(_network_join.company); } + ShowClientList(); + return NETWORK_RECV_STATUS_OKAY; } diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp index 8b41a863dde4..86c4c0c4ae37 100644 --- a/src/network/network_gui.cpp +++ b/src/network/network_gui.cpp @@ -1577,15 +1577,15 @@ struct NetworkClientListWindow : Window { /** * Part of RebuildList() to create the information for a single company. * @param company_id The company to build the list for. - * @param own_ci The NetworkClientInfo of the client itself. + * @param client_playas The company the client is joined as. */ - void RebuildListCompany(CompanyID company_id, const NetworkClientInfo *own_ci) + void RebuildListCompany(CompanyID company_id, CompanyID client_playas) { ButtonCommon *chat_button = new CompanyButton(SPR_CHAT, company_id == COMPANY_SPECTATOR ? STR_NETWORK_CLIENT_LIST_CHAT_SPECTATOR_TOOLTIP : STR_NETWORK_CLIENT_LIST_CHAT_COMPANY_TOOLTIP, COLOUR_ORANGE, company_id, &NetworkClientListWindow::OnClickCompanyChat); if (_network_server) this->buttons[line_count].emplace_back(new CompanyButton(SPR_ADMIN, STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_TOOLTIP, COLOUR_RED, company_id, &NetworkClientListWindow::OnClickCompanyAdmin, company_id == COMPANY_SPECTATOR)); this->buttons[line_count].emplace_back(chat_button); - if (own_ci->client_playas != company_id) this->buttons[line_count].emplace_back(new CompanyButton(SPR_JOIN, STR_NETWORK_CLIENT_LIST_JOIN_TOOLTIP, COLOUR_ORANGE, company_id, &NetworkClientListWindow::OnClickCompanyJoin, company_id != COMPANY_SPECTATOR && Company::Get(company_id)->is_ai)); + if (client_playas != company_id) this->buttons[line_count].emplace_back(new CompanyButton(SPR_JOIN, STR_NETWORK_CLIENT_LIST_JOIN_TOOLTIP, COLOUR_ORANGE, company_id, &NetworkClientListWindow::OnClickCompanyJoin, company_id != COMPANY_SPECTATOR && Company::Get(company_id)->is_ai)); this->line_count += 1; @@ -1616,6 +1616,7 @@ struct NetworkClientListWindow : Window { void RebuildList() { const NetworkClientInfo *own_ci = NetworkClientInfo::GetByClientID(_network_own_client_id); + CompanyID client_playas = own_ci == nullptr ? COMPANY_SPECTATOR : own_ci->client_playas; this->buttons.clear(); this->line_count = 0; @@ -1623,24 +1624,24 @@ struct NetworkClientListWindow : Window { this->player_self_index = -1; /* As spectator, show a line to create a new company. */ - if (own_ci->client_playas == COMPANY_SPECTATOR && !NetworkMaxCompaniesReached()) { + if (client_playas == COMPANY_SPECTATOR && !NetworkMaxCompaniesReached()) { this->buttons[line_count].emplace_back(new CompanyButton(SPR_JOIN, STR_NETWORK_CLIENT_LIST_NEW_COMPANY_TOOLTIP, COLOUR_ORANGE, COMPANY_SPECTATOR, &NetworkClientListWindow::OnClickCompanyNew)); this->line_count += 1; } - if (own_ci->client_playas != COMPANY_SPECTATOR) { - this->RebuildListCompany(own_ci->client_playas, own_ci); + if (client_playas != COMPANY_SPECTATOR) { + this->RebuildListCompany(client_playas, client_playas); } /* Companies */ for (const Company *c : Company::Iterate()) { - if (c->index == own_ci->client_playas) continue; + if (c->index == client_playas) continue; - this->RebuildListCompany(c->index, own_ci); + this->RebuildListCompany(c->index, client_playas); } /* Spectators */ - this->RebuildListCompany(COMPANY_SPECTATOR, own_ci); + this->RebuildListCompany(COMPANY_SPECTATOR, client_playas); this->vscroll->SetCount(this->line_count); } @@ -2060,16 +2061,18 @@ struct NetworkClientListWindow : Window { } NetworkClientInfo *own_ci = NetworkClientInfo::GetByClientID(_network_own_client_id); - if (own_ci->client_playas == COMPANY_SPECTATOR && !NetworkMaxCompaniesReached()) { + CompanyID client_playas = own_ci == nullptr ? COMPANY_SPECTATOR : own_ci->client_playas; + + if (client_playas == COMPANY_SPECTATOR && !NetworkMaxCompaniesReached()) { this->DrawCompany(COMPANY_NEW_COMPANY, r.left, r.right, r.top, line); } - if (own_ci->client_playas != COMPANY_SPECTATOR) { - this->DrawCompany(own_ci->client_playas, r.left, r.right, r.top, line); + if (client_playas != COMPANY_SPECTATOR) { + this->DrawCompany(client_playas, r.left, r.right, r.top, line); } for (const Company *c : Company::Iterate()) { - if (own_ci->client_playas == c->index) continue; + if (client_playas == c->index) continue; this->DrawCompany(c->index, r.left, r.right, r.top, line); } From 8fc5af6ae9f188a61e815ad4604c9a696656e2ec Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Tue, 10 Aug 2021 19:48:42 +0200 Subject: [PATCH 3/5] Add: synchronize server name to clients and display in Online Players GUI --- src/network/network_client.cpp | 3 +++ src/network/network_gui.cpp | 19 ++++++++++--------- src/network/network_internal.h | 3 +++ src/network/network_server.cpp | 1 + 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/network/network_client.cpp b/src/network/network_client.cpp index 06af6a859dc1..854832ddd8d8 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -314,6 +314,8 @@ static std::string _password_server_id; /** Maximum number of companies of the currently joined server. */ static uint8 _network_server_max_companies; +/** The current name of the server you are on. */ +std::string _network_server_name; /** Information about the game to join to. */ NetworkJoinInfo _network_join; @@ -1151,6 +1153,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CONFIG_UPDATE(P if (this->status < STATUS_ACTIVE) return NETWORK_RECV_STATUS_MALFORMED_PACKET; _network_server_max_companies = p->Recv_uint8(); + _network_server_name = p->Recv_string(NETWORK_NAME_LENGTH); return NETWORK_RECV_STATUS_OKAY; } diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp index 86c4c0c4ae37..faa5a68501c7 100644 --- a/src/network/network_gui.cpp +++ b/src/network/network_gui.cpp @@ -1281,14 +1281,14 @@ static const NWidgetPart _nested_client_list_widgets[] = { NWidget(WWT_STICKYBOX, COLOUR_GREY), EndContainer(), NWidget(WWT_PANEL, COLOUR_GREY), - NWidget(NWID_SELECTION, INVALID_COLOUR, WID_CL_SERVER_SELECTOR), - NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER, STR_NULL), SetPadding(4, 4, 0, 4), SetPIP(0, 2, 0), - NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0), - NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER_NAME, STR_NULL), - NWidget(NWID_SPACER), SetMinimalSize(10, 0), - NWidget(WWT_TEXT, COLOUR_GREY, WID_CL_SERVER_NAME), SetFill(1, 0), SetMinimalTextLines(1, 0), SetResize(1, 0), SetDataTip(STR_BLACK_RAW_STRING, STR_NETWORK_CLIENT_LIST_SERVER_NAME_TOOLTIP), SetAlignment(SA_VERT_CENTER | SA_RIGHT), - NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_CL_SERVER_NAME_EDIT), SetMinimalSize(12, 14), SetDataTip(SPR_RENAME, STR_NETWORK_CLIENT_LIST_SERVER_NAME_EDIT_TOOLTIP), - EndContainer(), + NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER, STR_NULL), SetPadding(4, 4, 0, 4), SetPIP(0, 2, 0), + NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0), + NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER_NAME, STR_NULL), + NWidget(NWID_SPACER), SetMinimalSize(10, 0), + NWidget(WWT_TEXT, COLOUR_GREY, WID_CL_SERVER_NAME), SetFill(1, 0), SetMinimalTextLines(1, 0), SetResize(1, 0), SetDataTip(STR_BLACK_RAW_STRING, STR_NETWORK_CLIENT_LIST_SERVER_NAME_TOOLTIP), SetAlignment(SA_VERT_CENTER | SA_RIGHT), + NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_CL_SERVER_NAME_EDIT), SetMinimalSize(12, 14), SetDataTip(SPR_RENAME, STR_NETWORK_CLIENT_LIST_SERVER_NAME_EDIT_TOOLTIP), + EndContainer(), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_CL_SERVER_SELECTOR), NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0), NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY, STR_NULL), NWidget(NWID_SPACER), SetMinimalSize(10, 0), SetFill(1, 0), SetResize(1, 0), @@ -1698,6 +1698,7 @@ struct NetworkClientListWindow : Window { /* Currently server information is not sync'd to clients, so we cannot show it on clients. */ this->GetWidget(WID_CL_SERVER_SELECTOR)->SetDisplayedPlane(_network_server ? 0 : SZSP_HORIZONTAL); + this->SetWidgetDisabledState(WID_CL_SERVER_NAME_EDIT, !_network_server); } void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override @@ -1732,7 +1733,7 @@ struct NetworkClientListWindow : Window { { switch (widget) { case WID_CL_SERVER_NAME: - SetDParamStr(0, _settings_client.network.server_name); + SetDParamStr(0, _network_server ? _settings_client.network.server_name : _network_server_name); break; case WID_CL_SERVER_VISIBILITY: diff --git a/src/network/network_internal.h b/src/network/network_internal.h index 1f1964d4c8a9..f26f60a18699 100644 --- a/src/network/network_internal.h +++ b/src/network/network_internal.h @@ -86,6 +86,9 @@ extern uint32 _network_join_bytes_total; extern ConnectionType _network_server_connection_type; extern std::string _network_server_invite_code; +/* Variable available for clients. */ +extern std::string _network_server_name; + extern uint8 _network_reconnect; extern CompanyMask _network_company_passworded; diff --git a/src/network/network_server.cpp b/src/network/network_server.cpp index e4e1de906b8e..c817acc90063 100644 --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -820,6 +820,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendConfigUpdate() Packet *p = new Packet(PACKET_SERVER_CONFIG_UPDATE); p->Send_uint8(_settings_client.network.max_companies); + p->Send_string(_settings_client.network.server_name); this->SendPacket(p); return NETWORK_RECV_STATUS_OKAY; } From 9173630791e7c8b98fd2f02beeb6fad477d6995c Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Tue, 10 Aug 2021 21:33:34 +0200 Subject: [PATCH 4/5] Fix: name the Online Players caption the same as in the menu Referring to one window in two ways is a bit annoying. --- src/lang/english.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/english.txt b/src/lang/english.txt index f740d6c8eeab..f62c5623f59d 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2111,7 +2111,7 @@ STR_NETWORK_NEED_COMPANY_PASSWORD_CAPTION :{WHITE}Company STR_NETWORK_COMPANY_LIST_CLIENT_LIST :Online players # Network client list -STR_NETWORK_CLIENT_LIST_CAPTION :{WHITE}Multiplayer +STR_NETWORK_CLIENT_LIST_CAPTION :{WHITE}Online Players STR_NETWORK_CLIENT_LIST_SERVER :{BLACK}Server STR_NETWORK_CLIENT_LIST_SERVER_NAME :{BLACK}Name STR_NETWORK_CLIENT_LIST_SERVER_NAME_TOOLTIP :{BLACK}Name of the server you are playing on From 275e3bafb71f8c4df025b5544e9dc6115dfe44f6 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Tue, 10 Aug 2021 20:02:46 +0200 Subject: [PATCH 5/5] Feature: servers can now set a Message Of The Day This is shown to clients upon joining. --- src/lang/english.txt | 8 ++ src/network/core/config.h | 1 + src/network/network.cpp | 21 +++- src/network/network_client.cpp | 12 +++ src/network/network_func.h | 1 + src/network/network_gui.cpp | 99 ++++++++++++++++++- src/network/network_gui.h | 5 +- src/network/network_internal.h | 1 + src/network/network_server.cpp | 1 + src/settings_type.h | 1 + .../settings/network_private_settings.ini | 10 ++ src/widgets/network_widget.h | 9 ++ src/window_type.h | 6 ++ 13 files changed, 170 insertions(+), 5 deletions(-) diff --git a/src/lang/english.txt b/src/lang/english.txt index f62c5623f59d..04379936b6bb 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2068,6 +2068,8 @@ STR_NETWORK_START_SERVER_CAPTION :{WHITE}Start ne STR_NETWORK_START_SERVER_NEW_GAME_NAME :{BLACK}Game name: STR_NETWORK_START_SERVER_NEW_GAME_NAME_TOOLTIP :{BLACK}The game name will be displayed to other players in the multiplayer game selection menu +STR_NETWORK_START_SERVER_MOTD :{BLACK}Message Of The Day: +STR_NETWORK_START_SERVER_MOTD_TOOLTIP :{BLACK}When clients join they are shown this Message Of The Day STR_NETWORK_START_SERVER_SET_PASSWORD :{BLACK}Set password STR_NETWORK_START_SERVER_PASSWORD_TOOLTIP :{BLACK}Protect your game with a password if you don't want it to be publicly accessible @@ -2107,6 +2109,11 @@ STR_NETWORK_CONNECTION_DISCONNECT :{BLACK}Disconne STR_NETWORK_NEED_GAME_PASSWORD_CAPTION :{WHITE}Server is protected. Enter password STR_NETWORK_NEED_COMPANY_PASSWORD_CAPTION :{WHITE}Company is protected. Enter password +STR_NETWORK_MOTD_CAPTION :{WHITE}Welcome to {RAW_STRING}! +STR_NETWORK_MOTD_TEXT :{BLACK}{RAW_STRING} +STR_NETWORK_MOTD_IF_SPECTATOR :{BLACK}To start playing, join a (new) company via the "Online Players" window. +STR_NETWORK_MOTD_CLOSE :{BLACK}Close + # Network company list added strings STR_NETWORK_COMPANY_LIST_CLIENT_LIST :Online players @@ -2211,6 +2218,7 @@ STR_NETWORK_ERROR_TIMEOUT :{WHITE}Connecti STR_NETWORK_ERROR_SERVER_ERROR :{WHITE}A protocol error was detected and the connection was closed STR_NETWORK_ERROR_BAD_PLAYER_NAME :{WHITE}Your player name has not been set. The name can be set at the top of the Multiplayer window STR_NETWORK_ERROR_BAD_SERVER_NAME :{WHITE}Your server name has not been set. The name can be set at the top of the Multiplayer window +STR_NETWORK_ERROR_BAD_SERVER_MOTD :{WHITE}The Message Of The Day has not been set. The message can be set in the Start Server window STR_NETWORK_ERROR_WRONG_REVISION :{WHITE}The revision of this client does not match the server's revision STR_NETWORK_ERROR_WRONG_PASSWORD :{WHITE}Wrong password STR_NETWORK_ERROR_SERVER_FULL :{WHITE}The server is full diff --git a/src/network/core/config.h b/src/network/core/config.h index 0a6eaa4f7568..0149dedb9f23 100644 --- a/src/network/core/config.h +++ b/src/network/core/config.h @@ -53,6 +53,7 @@ static const byte NETWORK_COMPANY_INFO_VERSION = 6; ///< What static const byte NETWORK_COORDINATOR_VERSION = 5; ///< What version of game-coordinator-protocol do we use? static const uint NETWORK_NAME_LENGTH = 80; ///< The maximum length of the server name and map name, in bytes including '\0' +static const uint NETWORK_MOTD_LENGTH = 1000; ///< The maximum length of the server Message Of The Day, in bytes including '\0' static const uint NETWORK_COMPANY_NAME_LENGTH = 128; ///< The maximum length of the company name, in bytes including '\0' static const uint NETWORK_HOSTNAME_LENGTH = 80; ///< The maximum length of the host name, in bytes including '\0' static const uint NETWORK_HOSTNAME_PORT_LENGTH = 80 + 6; ///< The maximum length of the host name + port, in bytes including '\0'. The extra six is ":" + port number (with a max of 65536) diff --git a/src/network/network.cpp b/src/network/network.cpp index 929e50450f0d..82bdd1e2b569 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -817,7 +817,7 @@ static void NetworkInitGameInfo() * Trim the given server name in place, i.e. remove leading and trailing spaces. * After the trim check whether the server name is not empty. * When the server name is empty a GUI error message is shown telling the - * user to set the servername and this function returns false. + * user to set the server name and this function returns false. * * @param server_name The server name to validate. It will be trimmed of leading * and trailing spaces. @@ -832,6 +832,25 @@ bool NetworkValidateServerName(std::string &server_name) return false; } +/** + * Trim the given server MOTD in place, i.e. remove leading and trailing spaces. + * After the trim check whether the server MOTD is not empty. + * When the server MOTD is empty a GUI error message is shown telling the + * user to set the server MOTD and this function returns false. + * + * @param server_name The server MOTD to validate. It will be trimmed of leading + * and trailing spaces. + * @return True iff the server MOTD is valid. + */ +bool NetworkValidateServerMessageOfTheDay(std::string &server_motd) +{ + StrTrimInPlace(server_motd); + if (!server_motd.empty()) return true; + + ShowErrorMessage(STR_NETWORK_ERROR_BAD_SERVER_MOTD, INVALID_STRING_ID, WL_ERROR); + return false; +} + /** * Check whether the client and server name are set, for a dedicated server and if not set them to some default * value and tell the user to change this as soon as possible. diff --git a/src/network/network_client.cpp b/src/network/network_client.cpp index 854832ddd8d8..25e3cae4fc05 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -28,6 +28,7 @@ #include "network_base.h" #include "network_client.h" #include "network_gamelist.h" +#include "network_gui.h" #include "../core/backup_type.hpp" #include "../thread.h" @@ -174,6 +175,9 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::CloseConnection(NetworkRecvSta CSleep(3 * MILLISECONDS_PER_TICK); } + /* Reset MOTD so we show it next time we join the server. */ + _network_server_motd = ""; + delete this; return status; @@ -316,6 +320,8 @@ static std::string _password_server_id; static uint8 _network_server_max_companies; /** The current name of the server you are on. */ std::string _network_server_name; +/** The current Message Of The Day of the server you are on. */ +std::string _network_server_motd; /** Information about the game to join to. */ NetworkJoinInfo _network_join; @@ -1154,6 +1160,12 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CONFIG_UPDATE(P _network_server_max_companies = p->Recv_uint8(); _network_server_name = p->Recv_string(NETWORK_NAME_LENGTH); + std::string new_motd = p->Recv_string(NETWORK_MOTD_LENGTH); + + if (new_motd != _network_server_motd) { + _network_server_motd = new_motd; + ShowNetworkMessageOfTheDay(); + } return NETWORK_RECV_STATUS_OKAY; } diff --git a/src/network/network_func.h b/src/network/network_func.h index 1708ed95bd1c..ffee20195d04 100644 --- a/src/network/network_func.h +++ b/src/network/network_func.h @@ -38,6 +38,7 @@ bool NetworkIsValidClientName(const std::string_view client_name); bool NetworkValidateOurClientName(); bool NetworkValidateClientName(std::string &client_name); bool NetworkValidateServerName(std::string &server_name); +bool NetworkValidateServerMessageOfTheDay(std::string &server_motd); void NetworkUpdateClientName(const std::string &client_name); void NetworkUpdateServerGameType(); bool NetworkCompanyHasClients(CompanyID company); diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp index faa5a68501c7..d5fc6f70491f 100644 --- a/src/network/network_gui.cpp +++ b/src/network/network_gui.cpp @@ -1006,13 +1006,16 @@ void ShowNetworkGameWindow() struct NetworkStartServerWindow : public Window { byte widget_id; ///< The widget that has the pop-up input menu QueryString name_editbox; ///< Server name editbox. + QueryString motd_editbox; ///< Server MOTD editbox. - NetworkStartServerWindow(WindowDesc *desc) : Window(desc), name_editbox(NETWORK_NAME_LENGTH) + NetworkStartServerWindow(WindowDesc *desc) : Window(desc), name_editbox(NETWORK_NAME_LENGTH), motd_editbox(NETWORK_MOTD_LENGTH) { this->InitNested(WN_NETWORK_WINDOW_START); this->querystrings[WID_NSS_GAMENAME] = &this->name_editbox; + this->querystrings[WID_NSS_MOTD] = &this->motd_editbox; this->name_editbox.text.Assign(_settings_client.network.server_name.c_str()); + this->motd_editbox.text.Assign(_settings_client.network.server_motd.c_str()); this->SetFocusedWidget(WID_NSS_GAMENAME); } @@ -1200,6 +1203,14 @@ static const NWidgetPart _nested_network_start_server_window_widgets[] = { EndContainer(), EndContainer(), + NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 6, 10), + NWidget(NWID_VERTICAL), SetPIP(0, 1, 0), + /* Message of the day widgets */ + NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_MOTD, STR_NULL), + NWidget(WWT_EDITBOX, COLOUR_LIGHT_BLUE, WID_NSS_MOTD), SetMinimalSize(10, 12), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_NEW_GAME_NAME_OSKTITLE, STR_NETWORK_START_SERVER_MOTD_TOOLTIP), + EndContainer(), + EndContainer(), + NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 6, 10), NWidget(NWID_VERTICAL), SetPIP(0, 1, 0), NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, WID_NSS_CONNTYPE_LABEL), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_VISIBILITY_LABEL, STR_NULL), @@ -2459,3 +2470,89 @@ void ShowNetworkAskRelay(const std::string &connection_string, const std::string Window *parent = FindWindowById(WC_MAIN_WINDOW, 0); new NetworkAskRelayWindow(&_network_ask_relay_desc, parent, connection_string, token); } + +struct NetworkMessageOfTheDay : public Window { + NetworkMessageOfTheDay(WindowDesc *desc) : Window(desc) + { + this->CreateNestedTree(); + this->OnInvalidateData(); + this->FinishInitNested(0); + } + + void OnInvalidateData(int data = 0, bool gui_scope = true) override + { + const NetworkClientInfo *own_ci = NetworkClientInfo::GetByClientID(_network_own_client_id); + CompanyID client_playas = own_ci == nullptr ? COMPANY_SPECTATOR : own_ci->client_playas; + + this->GetWidget(WID_MOTD_IF_SPECTATOR_SELECTOR)->SetDisplayedPlane(client_playas == COMPANY_SPECTATOR ? 0 : SZSP_HORIZONTAL); + } + + void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override + { + if (widget == WID_MOTD_TEXT) { + *size = GetStringBoundingBox(STR_NETWORK_MOTD_TEXT); + size->height = GetStringHeight(STR_NETWORK_MOTD_TEXT, size->width - WD_FRAMETEXT_LEFT - WD_FRAMETEXT_RIGHT) + WD_FRAMETEXT_BOTTOM + WD_FRAMETEXT_TOP; + } + } + + void SetStringParameters(int widget) const override + { + switch (widget) { + case WID_MOTD_CAPTION: + SetDParamStr(0, _network_server_name); + break; + + case WID_MOTD_TEXT: + SetDParamStr(0, _network_server_motd); + break; + } + } + + void DrawWidget(const Rect &r, int widget) const override + { + if (widget != WID_MOTD_TEXT) return; + + DrawStringMultiLine(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, r.top + WD_FRAMETEXT_TOP, r.bottom - WD_FRAMETEXT_BOTTOM, STR_NETWORK_MOTD_TEXT, TC_FROMSTRING, SA_CENTER); + } + + void OnClick(Point pt, int widget, int click_count) override + { + switch (widget) { + case WID_MOTD_CLOSE: + this->Close(); + break; + } + } +}; + +static const NWidgetPart _nested_network_message_of_the_day_window_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_GREY), + NWidget(WWT_CAPTION, COLOUR_GREY, WID_MOTD_CAPTION), SetDataTip(STR_NETWORK_MOTD_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + EndContainer(), + NWidget(WWT_PANEL, COLOUR_GREY), + NWidget(WWT_TEXT, COLOUR_GREY, WID_MOTD_TEXT), SetAlignment(SA_HOR_CENTER), SetFill(1, 1), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_MOTD_IF_SPECTATOR_SELECTOR), + NWidget(NWID_VERTICAL), SetPIP(0, 0, 10), + NWidget(NWID_HORIZONTAL), SetPIP(10, 15, 10), + NWidget(WWT_TEXT, COLOUR_GREY), SetAlignment(SA_HOR_CENTER), SetFill(1, 1), SetDataTip(STR_NETWORK_MOTD_IF_SPECTATOR, STR_NULL), + EndContainer(), + EndContainer(), + EndContainer(), + EndContainer(), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_MOTD_CLOSE), SetMinimalSize(71, 12), SetFill(1, 1), SetDataTip(STR_NETWORK_MOTD_CLOSE, STR_NULL), +}; + +static WindowDesc _network_message_of_the_day_window_desc( + WDP_CENTER, nullptr, 0, 0, + WC_NETWORK_MOTD, WC_NONE, + 0, + _nested_network_message_of_the_day_window_widgets, lengthof(_nested_network_message_of_the_day_window_widgets) +); + +void ShowNetworkMessageOfTheDay() +{ + CloseWindowByClass(WC_NETWORK_MOTD); + + new NetworkMessageOfTheDay(&_network_message_of_the_day_window_desc); +} diff --git a/src/network/network_gui.h b/src/network/network_gui.h index c3194edaf995..5a6117e1fd12 100644 --- a/src/network/network_gui.h +++ b/src/network/network_gui.h @@ -23,7 +23,8 @@ void ShowJoinStatusWindow(); void ShowNetworkGameWindow(); void ShowClientList(); void ShowNetworkCompanyPasswordWindow(Window *parent); - +void ShowNetworkAskRelay(const std::string &connection_string, const std::string &token); +void ShowNetworkMessageOfTheDay(); /** Company information stored at the client side */ struct NetworkCompanyInfo : NetworkCompanyStats { @@ -37,6 +38,4 @@ struct NetworkCompanyInfo : NetworkCompanyStats { std::string clients; ///< The clients that control this company (Name1, name2, ..) }; -void ShowNetworkAskRelay(const std::string &connection_string, const std::string &token); - #endif /* NETWORK_GUI_H */ diff --git a/src/network/network_internal.h b/src/network/network_internal.h index f26f60a18699..0eeab24bbac0 100644 --- a/src/network/network_internal.h +++ b/src/network/network_internal.h @@ -88,6 +88,7 @@ extern std::string _network_server_invite_code; /* Variable available for clients. */ extern std::string _network_server_name; +extern std::string _network_server_motd; extern uint8 _network_reconnect; diff --git a/src/network/network_server.cpp b/src/network/network_server.cpp index c817acc90063..47de0c1115a3 100644 --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -821,6 +821,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendConfigUpdate() p->Send_uint8(_settings_client.network.max_companies); p->Send_string(_settings_client.network.server_name); + p->Send_string(_settings_client.network.server_motd); this->SendPacket(p); return NETWORK_RECV_STATUS_OKAY; } diff --git a/src/settings_type.h b/src/settings_type.h index 05833cdb0da1..577937596900 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -277,6 +277,7 @@ struct NetworkSettings { std::string server_invite_code; ///< Invite code to use when registering as server. std::string server_invite_code_secret; ///< Secret to proof we got this invite code from the Game Coordinator. std::string server_name; ///< name of the server + std::string server_motd; ///< Message Of The Day of the server. std::string server_password; ///< password for joining this server std::string rcon_password; ///< password for rconsole (server side) std::string admin_password; ///< password for the admin network diff --git a/src/table/settings/network_private_settings.ini b/src/table/settings/network_private_settings.ini index cae43330fdd0..0e06c6302b38 100644 --- a/src/table/settings/network_private_settings.ini +++ b/src/table/settings/network_private_settings.ini @@ -52,6 +52,16 @@ pre_cb = NetworkValidateServerName post_cb = [](auto) { UpdateClientConfigValues(); } cat = SC_BASIC +[SDTC_SSTR] +var = network.server_motd +type = SLE_STR +length = NETWORK_NAME_LENGTH +flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC | SF_NETWORK_ONLY +def = ""Please enjoy your stay."" +pre_cb = NetworkValidateServerMessageOfTheDay +post_cb = [](auto) { UpdateClientConfigValues(); } +cat = SC_BASIC + [SDTC_SSTR] var = network.connect_to_ip type = SLE_STR diff --git a/src/widgets/network_widget.h b/src/widgets/network_widget.h index f50bf04e9338..7772e7163137 100644 --- a/src/widgets/network_widget.h +++ b/src/widgets/network_widget.h @@ -55,6 +55,7 @@ enum NetworkStartServerWidgets { WID_NSS_BACKGROUND, ///< Background of the window. WID_NSS_GAMENAME_LABEL, ///< Label for the game name. WID_NSS_GAMENAME, ///< Background for editbox to set game name. + WID_NSS_MOTD, ///< Message Of The Day text. WID_NSS_SETPWD, ///< 'Set password' button. WID_NSS_CONNTYPE_LABEL, ///< Label for 'connection type'. WID_NSS_CONNTYPE_BTN, ///< 'Connection type' droplist button. @@ -118,4 +119,12 @@ enum NetworkAskRelayWidgets { WID_NAR_YES_ALWAYS, ///< "Yes, always" button. }; +/** Widgets of the #NetworkMessageOfTheDayWindow class. */ +enum NetworkMessageOfTheDayWidgets { + WID_MOTD_CAPTION, ///< Caption of the window. + WID_MOTD_TEXT, ///< Text in the window. + WID_MOTD_IF_SPECTATOR_SELECTOR, ///< Selector to show text if you join as spectator. + WID_MOTD_CLOSE, ///< "Close" button. +}; + #endif /* WIDGETS_NETWORK_WIDGET_H */ diff --git a/src/window_type.h b/src/window_type.h index 6eb9573b609a..8b4dd4910cd4 100644 --- a/src/window_type.h +++ b/src/window_type.h @@ -482,6 +482,12 @@ enum WindowClass { */ WC_NETWORK_ASK_RELAY, + /** + * Network Message Of The Day window; %Window numbers: + * - 0 - #NetworkMessageOfTheDayWidgets + */ + WC_NETWORK_MOTD, + /** * Chatbox; %Window numbers: * - #DestType = #NetWorkChatWidgets