diff --git a/src/company_base.h b/src/company_base.h index 5813988ea8..90572fb433 100644 --- a/src/company_base.h +++ b/src/company_base.h @@ -81,6 +81,7 @@ struct CompanyProperties { std::string president_name{}; ///< Name of the president if the user changed it. NetworkAuthorizedKeys allow_list{}; ///< Public keys of clients that are allowed to join this company. + bool allow_any = false; ///< Set if anyone is allowed to join this company. CompanyManagerFace face{}; ///< Face description of the president. diff --git a/src/company_cmd.cpp b/src/company_cmd.cpp index 68a66cd811..7d9ad424df 100644 --- a/src/company_cmd.cpp +++ b/src/company_cmd.cpp @@ -1014,6 +1014,16 @@ static bool ExecuteAllowListCtrlAction(CompanyAllowListCtrlAction action, Compan case CALCA_REMOVE: return c->allow_list.Remove(public_key); + case CALCA_ALLOW_ANY: + if (c->allow_any) return false; + c->allow_any = true; + return true; + + case CALCA_ALLOW_LISTED: + if (!c->allow_any) return false; + c->allow_any = false; + return true; + default: NOT_REACHED(); } @@ -1031,12 +1041,16 @@ CommandCost CmdCompanyAllowListCtrl(DoCommandFlags flags, CompanyAllowListCtrlAc Company *c = Company::GetIfValid(_current_company); if (c == nullptr) return CMD_ERROR; - /* The public key length includes the '\0'. */ - if (public_key.size() != NETWORK_PUBLIC_KEY_LENGTH - 1) return CMD_ERROR; - switch (action) { case CALCA_ADD: case CALCA_REMOVE: + /* The public key length includes the '\0'. */ + if (public_key.size() != NETWORK_PUBLIC_KEY_LENGTH - 1) return CMD_ERROR; + break; + + case CALCA_ALLOW_ANY: + case CALCA_ALLOW_LISTED: + if (public_key.size() != 0) return CMD_ERROR; break; default: diff --git a/src/company_type.h b/src/company_type.h index 614f5e9d56..9e7a552912 100644 --- a/src/company_type.h +++ b/src/company_type.h @@ -80,6 +80,8 @@ enum CompanyCtrlAction : uint8_t { enum CompanyAllowListCtrlAction : uint8_t { CALCA_ADD, ///< Create a public key. CALCA_REMOVE, ///< Remove a public key. + CALCA_ALLOW_ANY, ///< Allow joining the company without a key. + CALCA_ALLOW_LISTED, ///< Allow only listed keys to join the company. CALCA_END, ///< Sentinel for end. }; diff --git a/src/lang/english.txt b/src/lang/english.txt index ac28eedd84..3807eaf07d 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2547,6 +2547,8 @@ STR_NETWORK_CLIENT_LIST_SERVER_CONNECTION_TYPE_TURN :{BLACK}Via rela STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_KICK :Kick STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_BAN :Ban STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET :Delete +STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_ALLOW_ANY :Allow any client +STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_ALLOW_LISTED :Allow only listed clients STR_NETWORK_CLIENT_LIST_ASK_CAPTION :{WHITE}Admin action STR_NETWORK_CLIENT_LIST_ASK_CLIENT_KICK :{YELLOW}Are you sure you want to kick player '{RAW_STRING}'? diff --git a/src/network/network.cpp b/src/network/network.cpp index 7e82f6ce73..259a40b269 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -131,7 +131,7 @@ NetworkClientInfo::~NetworkClientInfo() bool NetworkClientInfo::CanJoinCompany(CompanyID company_id) const { Company *c = Company::GetIfValid(company_id); - return c != nullptr && c->allow_list.Contains(this->public_key); + return c != nullptr && (c->allow_any || c->allow_list.Contains(this->public_key)); } /** diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp index 947d1900e9..91cd73b81e 100644 --- a/src/network/network_gui.cpp +++ b/src/network/network_gui.cpp @@ -1539,6 +1539,8 @@ private: AdminKickClient, ///< Admin kick client. AdminBanClient, ///< Admin ban client. AdminResetCompany, ///< Admin reset company. + CompanyAllowAny, ///< Allow any client. + CompanyAllowListed, ///< Allow only listed clients. }; ClientListWidgets query_widget{}; ///< During a query this tracks what widget caused the query. @@ -1620,7 +1622,11 @@ private: static void OnClickCompanyAdmin([[maybe_unused]] NetworkClientListWindow *w, [[maybe_unused]] Point pt, CompanyID company_id) { DropDownList list; - list.push_back(MakeDropDownListStringItem(STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET, to_underlying(DropDownAction::AdminResetCompany), NetworkCompanyHasClients(company_id))); + if (_network_server) list.push_back(MakeDropDownListStringItem(STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_RESET, to_underlying(DropDownAction::AdminResetCompany), NetworkCompanyHasClients(company_id))); + if (const Company *c = Company::GetIfValid(company_id); c != nullptr) { + list.push_back(MakeDropDownListStringItem(STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_ALLOW_ANY, to_underlying(DropDownAction::CompanyAllowAny), c->allow_any)); + list.push_back(MakeDropDownListStringItem(STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_ALLOW_LISTED, to_underlying(DropDownAction::CompanyAllowListed), !c->allow_any)); + } Rect wi_rect; wi_rect.left = pt.x; @@ -1631,6 +1637,7 @@ private: w->dd_company_id = company_id; ShowDropDownListAt(w, std::move(list), -1, WID_CL_MATRIX, wi_rect, COLOUR_GREY, DropDownOption::InstantClose); } + /** * Chat button on a Client is clicked. * @param w The instance of this window. @@ -1657,7 +1664,9 @@ private: void RebuildListCompany(CompanyID company_id, CompanyID client_playas, bool can_join_company) { ButtonLine &company_line = *this->buttons.emplace_back(std::make_unique(company_id)); - if (_network_server) company_line.AddButton(SPR_ADMIN, STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_TOOLTIP, COLOUR_RED, company_id, &NetworkClientListWindow::OnClickCompanyAdmin, company_id == COMPANY_SPECTATOR); + if (_network_server || company_id == _local_company) { + company_line.AddButton(SPR_ADMIN, STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_TOOLTIP, COLOUR_RED, company_id, &NetworkClientListWindow::OnClickCompanyAdmin, company_id == COMPANY_SPECTATOR); + } ButtonCommon &chat_button = company_line.AddButton(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 (can_join_company) company_line.AddButton(SPR_JOIN, STR_NETWORK_CLIENT_LIST_JOIN_TOOLTIP, COLOUR_ORANGE, company_id, &NetworkClientListWindow::OnClickCompanyJoin, company_id != COMPANY_SPECTATOR && Company::Get(company_id)->is_ai); @@ -1700,7 +1709,7 @@ private: for (const Company *c : Company::Iterate()) { if (c->index == client_playas) continue; - this->RebuildListCompany(c->index, client_playas, (own_ci != nullptr && c->allow_list.Contains(own_ci->public_key)) || _network_server); + this->RebuildListCompany(c->index, client_playas, _network_server || c->allow_any || (own_ci != nullptr && c->allow_list.Contains(own_ci->public_key))); } /* Spectators */ @@ -1897,6 +1906,18 @@ public: text = GetEncodedString(STR_NETWORK_CLIENT_LIST_ASK_COMPANY_RESET, _admin_company_id); break; + case DropDownAction::CompanyAllowAny: { + AutoRestoreBackup cur_company(_current_company, this->dd_company_id); + Command::Post(CALCA_ALLOW_ANY, {}); + return; + } + + case DropDownAction::CompanyAllowListed: { + AutoRestoreBackup cur_company(_current_company, this->dd_company_id); + Command::Post(CALCA_ALLOW_LISTED, {}); + return; + } + default: NOT_REACHED(); } diff --git a/src/network/network_server.cpp b/src/network/network_server.cpp index 133bf7e5b6..7b07b09169 100644 --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -942,7 +942,8 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_IDENTIFY(Packet return this->SendError(NETWORK_ERROR_COMPANY_MISMATCH); } - if (!Company::Get(playas)->allow_list.Contains(this->peer_public_key)) { + const Company *c = Company::Get(playas); + if (!c->allow_any && !c->allow_list.Contains(this->peer_public_key)) { /* When we're not authorized, just bump us to a spectator. */ playas = COMPANY_SPECTATOR; } @@ -1161,16 +1162,18 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_COMMAND(Packet /* Only allow clients to add/remove currently joined clients. The server owner does not go via this method, so is allowed to do more. */ std::string public_key = std::get<1>(EndianBufferReader::ToValue::Args>(cp.data)); - bool found = false; - for (const NetworkClientInfo *info : NetworkClientInfo::Iterate()) { - if (info->public_key == public_key) { - found = true; - break; + if (!public_key.empty()) { + bool found = false; + for (const NetworkClientInfo *info : NetworkClientInfo::Iterate()) { + if (info->public_key == public_key) { + found = true; + break; + } } - } - /* Maybe the client just left? */ - if (!found) return NETWORK_RECV_STATUS_OKAY; + /* Maybe the client just left? */ + if (!found) return NETWORK_RECV_STATUS_OKAY; + } } if (GetCommandFlags(cp.cmd).Test(CommandFlag::ClientID)) NetworkReplaceCommandClientId(cp, this->client_id); @@ -1517,11 +1520,14 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_MOVE(Packet &p) Debug(net, 9, "client[{}] Receive_CLIENT_MOVE(): company_id={}", this->client_id, company_id); /* Check if the company is valid, we don't allow moving to AI companies */ - if (company_id != COMPANY_SPECTATOR && !Company::IsValidHumanID(company_id)) return NETWORK_RECV_STATUS_OKAY; + if (company_id != COMPANY_SPECTATOR) { + if (!Company::IsValidHumanID(company_id)) return NETWORK_RECV_STATUS_OKAY; - if (company_id != COMPANY_SPECTATOR && !Company::Get(company_id)->allow_list.Contains(this->peer_public_key)) { - Debug(net, 2, "Wrong public key from client-id #{} for company #{}", this->client_id, company_id + 1); - return NETWORK_RECV_STATUS_OKAY; + const Company *c = Company::Get(company_id); + if (!c->allow_any && !c->allow_list.Contains(this->peer_public_key)) { + Debug(net, 2, "Wrong public key from client-id #{} for company #{}", this->client_id, company_id + 1); + return NETWORK_RECV_STATUS_OKAY; + } } /* if we get here we can move the client */ diff --git a/src/saveload/company_sl.cpp b/src/saveload/company_sl.cpp index 2baaff3ceb..cd6fc42c7a 100644 --- a/src/saveload/company_sl.cpp +++ b/src/saveload/company_sl.cpp @@ -499,6 +499,7 @@ static const SaveLoad _company_desc[] = { SLE_CONDVECTOR(CompanyProperties, allow_list, SLE_STR, SLV_COMPANY_ALLOW_LIST, SLV_COMPANY_ALLOW_LIST_V2), SLEG_CONDSTRUCTLIST("allow_list", SlAllowListData, SLV_COMPANY_ALLOW_LIST_V2, SL_MAX_VERSION), + SLE_VAR(CompanyProperties, allow_any, SLE_BOOL), SLE_VARNAME(CompanyProperties, face.bits, "face", SLE_UINT32), SLE_CONDSSTRNAME(CompanyProperties, face.style_label, "face_style", SLE_STR, SLV_FACE_STYLES, SL_MAX_VERSION),