From 04709f279809125e24c3652f182eac0e56c1d90f Mon Sep 17 00:00:00 2001 From: rubidium Date: Fri, 23 Jan 2009 22:18:06 +0000 Subject: (svn r15242) -Feature: allow moving clients between companies/spectators by the server and the clients themselves (dihedral) --- src/company_cmd.cpp | 11 +++- src/company_gui.cpp | 27 +++++++++ src/console_cmds.cpp | 86 +++++++++++++++++++++++++++- src/lang/english.txt | 7 +++ src/network/core/tcp_game.h | 4 ++ src/network/network.cpp | 23 ++++++++ src/network/network_client.cpp | 97 ++++++++++++++++++++++++++++++++ src/network/network_client.h | 1 + src/network/network_func.h | 7 +++ src/network/network_internal.h | 2 + src/network/network_server.cpp | 125 +++++++++++++++++++++++++++++++++++++++++ src/network/network_server.h | 1 + src/network/network_type.h | 3 + src/settings.cpp | 11 +++- src/toolbar_gui.cpp | 50 +++++++++++++++-- 15 files changed, 444 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/company_cmd.cpp b/src/company_cmd.cpp index 221ecfe7a..614dbdb18 100644 --- a/src/company_cmd.cpp +++ b/src/company_cmd.cpp @@ -752,8 +752,8 @@ CommandCost CmdCompanyCtrl(TileIndex tile, uint32 flags, uint32 p1, uint32 p2, c break; } - /* This is the joining client who wants a new company */ - if (_local_company != _network_playas && _network_playas == c->index) { + /* This is the client (or non-dedicated server) who wants a new company */ + if (cid == _network_own_client_id) { assert(_local_company == COMPANY_SPECTATOR); SetLocalCompany(c->index); if (!StrEmpty(_settings_client.network.default_company_pass)) { @@ -780,6 +780,7 @@ CommandCost CmdCompanyCtrl(TileIndex tile, uint32 flags, uint32 p1, uint32 p2, c /* XXX - UGLY! p2 (pid) is mis-used to fetch the client-id, done at * server-side in network_server.c:838, function * DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_COMMAND) */ + CompanyID old_playas = ci->client_playas; ci->client_playas = c->index; NetworkUpdateClientInfo(ci->client_id); @@ -787,6 +788,7 @@ CommandCost CmdCompanyCtrl(TileIndex tile, uint32 flags, uint32 p1, uint32 p2, c CompanyID company_backup = _local_company; _network_company_states[c->index].months_empty = 0; _network_company_states[c->index].password[0] = '\0'; + NetworkServerUpdateCompanyPassworded(ci->client_playas, false); /* XXX - When a client joins, we automatically set its name to the * client's name (for some reason). As it stands now only the server @@ -803,6 +805,11 @@ CommandCost CmdCompanyCtrl(TileIndex tile, uint32 flags, uint32 p1, uint32 p2, c NetworkSend_Command(0, 0, 0, CMD_RENAME_PRESIDENT, NULL, ci->client_name); _local_company = company_backup; } + + /* Announce new company on network, if the client was a SPECTATOR before */ + if (old_playas == COMPANY_SPECTATOR) { + NetworkServerSendChat(NETWORK_ACTION_COMPANY_NEW, DESTTYPE_BROADCAST, 0, "", ci->client_id, ci->client_playas + 1); + } } #endif /* ENABLE_NETWORK */ } break; diff --git a/src/company_gui.cpp b/src/company_gui.cpp index 712f092f7..1e65834bd 100644 --- a/src/company_gui.cpp +++ b/src/company_gui.cpp @@ -13,6 +13,7 @@ #include "command_func.h" #include "network/network.h" #include "network/network_gui.h" +#include "network/network_func.h" #include "variables.h" #include "roadveh.h" #include "train.h" @@ -1054,6 +1055,7 @@ enum CompanyWindowWidgets { CW_WIDGET_BUY_SHARE, CW_WIDGET_SELL_SHARE, CW_WIDGET_COMPANY_PASSWORD, + CW_WIDGET_COMPANY_JOIN, }; static const Widget _company_widgets[] = { @@ -1069,6 +1071,7 @@ static const Widget _company_widgets[] = { { WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_GREY, 0, 179, 158, 169, STR_7077_BUY_25_SHARE_IN_COMPANY, STR_7079_BUY_25_SHARE_IN_THIS_COMPANY}, { WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_GREY, 180, 359, 158, 169, STR_7078_SELL_25_SHARE_IN_COMPANY, STR_707A_SELL_25_SHARE_IN_THIS_COMPANY}, { WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_GREY, 266, 355, 138, 149, STR_COMPANY_PASSWORD, STR_COMPANY_PASSWORD_TOOLTIP}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_GREY, 266, 355, 46, 57, STR_COMPANY_JOIN, STR_COMPANY_JOIN_TIP}, { WIDGETS_END}, }; @@ -1187,6 +1190,8 @@ struct CompanyWindow : Window this->SetWidgetHiddenState(CW_WIDGET_BUY_SHARE, local); this->SetWidgetHiddenState(CW_WIDGET_SELL_SHARE, local); this->SetWidgetHiddenState(CW_WIDGET_COMPANY_PASSWORD, !local || !_networking); + this->SetWidgetHiddenState(CW_WIDGET_COMPANY_JOIN, local || !_networking); + this->SetWidgetDisabledState(CW_WIDGET_COMPANY_JOIN, !IsHumanCompany(c->index)); if (!local) { if (_settings_game.economy.allow_shares) { // Shares are allowed @@ -1298,6 +1303,22 @@ struct CompanyWindow : Window case CW_WIDGET_COMPANY_PASSWORD: if (this->window_number == _local_company) ShowNetworkCompanyPasswordWindow(this); break; + + case CW_WIDGET_COMPANY_JOIN: { + this->query_widget = CW_WIDGET_COMPANY_JOIN; + CompanyID company = (CompanyID)this->window_number; + if (_network_server) { + NetworkServerDoMove(CLIENT_ID_SERVER, company); + MarkWholeScreenDirty(); + } else if (NetworkCompanyIsPassworded(company)) { + /* ask for the password */ + ShowQueryString(STR_EMPTY, STR_NETWORK_NEED_COMPANY_PASSWORD_CAPTION, 20, 180, this, CS_ALPHANUMERAL, QSF_NONE); + } else { + /* just send the join command */ + NetworkClientRequestMove(company); + } + break; + } #endif /* ENABLE_NETWORK */ } } @@ -1335,6 +1356,12 @@ struct CompanyWindow : Window case CW_WIDGET_COMPANY_NAME: DoCommandP(0, 0, 0, CMD_RENAME_COMPANY | CMD_MSG(STR_700C_CAN_T_CHANGE_COMPANY_NAME), NULL, str); break; + +#ifdef ENABLE_NETWORK + case CW_WIDGET_COMPANY_JOIN: + NetworkClientRequestMove((CompanyID)this->window_number, str); + break; +#endif /* ENABLE_NETWORK */ } } }; diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index e7b262450..09e5b592a 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -622,6 +622,86 @@ DEF_CONSOLE_CMD(ConKick) return true; } +DEF_CONSOLE_CMD(ConJoinCompany) +{ + if (argc < 2) { + IConsoleHelp("Request joining another company. Usage: join []"); + IConsoleHelp("For valid company-id see company list, use 255 for spectator"); + return true; + } + + CompanyID company_id = (CompanyID)(atoi(argv[1]) <= MAX_COMPANIES ? atoi(argv[1]) - 1 : atoi(argv[1])); + + /* Check we have a valid company id! */ + if (!IsValidCompanyID(company_id) && company_id != COMPANY_SPECTATOR) { + IConsolePrintF(CC_ERROR, "Company does not exist. Company-id must be between 1 and %d.", MAX_COMPANIES); + return true; + } + + if (NetworkFindClientInfoFromClientID(_network_own_client_id)->client_playas == company_id) { + IConsoleError("You are already there!"); + return true; + } + + if (company_id == COMPANY_SPECTATOR && NetworkMaxSpectatorsReached()) { + IConsoleError("Cannot join spectators, maximum number of spectators reached."); + return true; + } + + /* Check if the company requires a password */ + if (NetworkCompanyIsPassworded(company_id) && argc < 3) { + IConsolePrintF(CC_ERROR, "Company %d requires a password to join.", company_id + 1); + return true; + } + + /* non-dedicated server may just do the move! */ + if (_network_server) { + NetworkServerDoMove(CLIENT_ID_SERVER, company_id); + } else { + NetworkClientRequestMove(company_id, NetworkCompanyIsPassworded(company_id) ? argv[2] : ""); + } + + return true; +} + +DEF_CONSOLE_CMD(ConMoveClient) +{ + if (argc < 3) { + IConsoleHelp("Move a client to another company. Usage: move "); + IConsoleHelp("For valid client-id see 'clients', for valid company-id see 'companies', use 255 for moving to spectators"); + return true; + } + + const NetworkClientInfo *ci = NetworkFindClientInfoFromClientID((ClientID)atoi(argv[1])); + CompanyID company_id = (CompanyID)(atoi(argv[2]) <= MAX_COMPANIES ? atoi(argv[2]) - 1 : atoi(argv[2])); + + /* check the client exists */ + if (ci == NULL) { + IConsoleError("Invalid client-id, check the command 'clients' for valid client-id's."); + return true; + } + + if (!IsValidCompanyID(company_id) && company_id != COMPANY_SPECTATOR) { + IConsolePrintF(CC_ERROR, "Company does not exist. Company-id must be between 1 and %d.", MAX_COMPANIES); + return true; + } + + if (ci->client_id == CLIENT_ID_SERVER && _network_dedicated) { + IConsoleError("Silly boy, you cannot move the server!"); + return true; + } + + if (ci->client_playas == company_id) { + IConsoleError("You cannot move someone to where he/she already is!"); + return true; + } + + /* we are the server, so force the update */ + NetworkServerDoMove(ci->client_id, company_id); + + return true; +} + DEF_CONSOLE_CMD(ConResetCompany) { CompanyID index; @@ -1703,7 +1783,6 @@ void IConsoleStdLibRegister() IConsoleCmdRegister("connect", ConNetworkConnect); IConsoleCmdHookAdd("connect", ICONSOLE_HOOK_ACCESS, ConHookClientOnly); - IConsoleAliasRegister("join", "connect %A"); IConsoleCmdRegister("clients", ConNetworkClients); IConsoleCmdHookAdd("clients", ICONSOLE_HOOK_ACCESS, ConHookNeedNetwork); IConsoleCmdRegister("status", ConStatus); @@ -1714,6 +1793,11 @@ void IConsoleStdLibRegister() IConsoleCmdRegister("rcon", ConRcon); IConsoleCmdHookAdd("rcon", ICONSOLE_HOOK_ACCESS, ConHookNeedNetwork); + IConsoleCmdRegister("join", ConJoinCompany); + IConsoleCmdHookAdd("join", ICONSOLE_HOOK_ACCESS, ConHookClientOnly); + IConsoleAliasRegister("spectate", "join 255"); + IConsoleCmdRegister("move", ConMoveClient); + IConsoleCmdHookAdd("move", ICONSOLE_HOOK_ACCESS, ConHookServerOnly); IConsoleCmdRegister("reset_company", ConResetCompany); IConsoleCmdHookAdd("reset_company", ICONSOLE_HOOK_ACCESS, ConHookServerOnly); IConsoleAliasRegister("clean_company", "reset_company %A"); diff --git a/src/lang/english.txt b/src/lang/english.txt index a2f760fc5..774535918 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1560,6 +1560,8 @@ STR_NETWORK_GIVE_MONEY_CAPTION :{WHITE}Enter th 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_CLIENT_LIST :{WHITE}Client List +STR_NETWORK_COMPANY_LIST_SPECTATE :{WHITE}Spectate +STR_NETWORK_COMPANY_LIST_NEW_COMPANY :{WHITE}New Company STR_NETWORK_ERR_NOTAVAILABLE :{WHITE} No network devices found or compiled without ENABLE_NETWORK STR_NETWORK_ERR_NOSERVER :{WHITE} Could not find any network games @@ -1606,6 +1608,9 @@ STR_NETWORK_SERVER_MESSAGE_GAME_UNPAUSED_CONNECT_FAIL :Game unpaused ( ############ End of leave-in-this-order STR_NETWORK_CLIENT_LEAVING :leaving STR_NETWORK_CLIENT_JOINED :*** {RAW_STRING} has joined the game +STR_NETWORK_CLIENT_COMPANY_JOIN :*** {RAW_STRING} has joined company #{2:NUM} +STR_NETWORK_CLIENT_COMPANY_SPECTATE :*** {RAW_STRING} has joined spectators +STR_NETWORK_CLIENT_COMPANY_NEW :*** {RAW_STRING} has started a new company (#{2:NUM}) STR_NETWORK_CLIENT_LEFT :*** {RAW_STRING} has left the game ({2:STRING}) STR_NETWORK_NAME_CHANGE :*** {RAW_STRING} has changed his/her name to {RAW_STRING} STR_NETWORK_GIVE_MONEY :*** {RAW_STRING} gave your company {2:CURRENCY} @@ -2405,6 +2410,8 @@ STR_RELOCATE_COMPANY_HEADQUARTERS :{BLACK}Rebuild STR_7071_CAN_T_BUILD_COMPANY_HEADQUARTERS :{WHITE}Can't build company headquarters... STR_7072_VIEW_HQ :{BLACK}View HQ STR_RELOCATE_HQ :{BLACK}Relocate HQ +STR_COMPANY_JOIN :{BLACK}Join +STR_COMPANY_JOIN_TIP :{BLACK}Join and play as this company STR_COMPANY_PASSWORD :{BLACK}Password STR_COMPANY_PASSWORD_TOOLTIP :{BLACK}Password-protect your company to prevent unauthorised users from joining. STR_SET_COMPANY_PASSWORD :{BLACK}Set company password diff --git a/src/network/core/tcp_game.h b/src/network/core/tcp_game.h index 1c0f047ea..3acd61310 100644 --- a/src/network/core/tcp_game.h +++ b/src/network/core/tcp_game.h @@ -54,6 +54,10 @@ enum { PACKET_CLIENT_RCON, PACKET_SERVER_CHECK_NEWGRFS, PACKET_CLIENT_NEWGRFS_CHECKED, + PACKET_SERVER_MOVE, + PACKET_CLIENT_MOVE, + PACKET_SERVER_COMPANY_UPDATE, + PACKET_SERVER_CONFIG_UPDATE, PACKET_END ///< Must ALWAYS be on the end of this list!! (period) }; diff --git a/src/network/network.cpp b/src/network/network.cpp index 4fbad7535..237dbd57a 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -75,6 +75,7 @@ bool _network_first_time; bool _network_udp_server; uint16 _network_udp_broadcast; uint8 _network_advertise_retries; +CompanyMask _network_company_passworded; ///< Bitmask of the password status of all companies. /* Check whether NETWORK_NUM_LANDSCAPES is still in sync with NUM_LANDSCAPE */ assert_compile((int)NETWORK_NUM_LANDSCAPES == (int)NUM_LANDSCAPE); @@ -182,6 +183,16 @@ byte NetworkSpectatorCount() return count; } +/** + * Check if the company we want to join requires a password. + * @param company_id id of the company we want to check the 'passworded' flag for. + * @return true if the company requires a password. + */ +bool NetworkCompanyIsPassworded(CompanyID company_id) +{ + return HasBit(_network_company_passworded, company_id); +} + // This puts a text-message to the console, or in the future, the chat-box, // (to keep it all a bit more general) // If 'self_send' is true, this is the client who is sending the message @@ -199,6 +210,18 @@ void NetworkTextMessage(NetworkAction action, ConsoleColour color, bool self_sen color = CC_DEFAULT; data = STR_NETWORK_SERVER_MESSAGE_GAME_PAUSED_PLAYERS + data; break; + case NETWORK_ACTION_COMPANY_SPECTATOR: + color = CC_DEFAULT; + strid = STR_NETWORK_CLIENT_COMPANY_SPECTATE; + break; + case NETWORK_ACTION_COMPANY_JOIN: + color = CC_DEFAULT; + strid = STR_NETWORK_CLIENT_COMPANY_JOIN; + break; + case NETWORK_ACTION_COMPANY_NEW: + color = CC_DEFAULT; + strid = STR_NETWORK_CLIENT_COMPANY_NEW; + break; case NETWORK_ACTION_JOIN: strid = STR_NETWORK_CLIENT_JOINED; break; case NETWORK_ACTION_LEAVE: strid = STR_NETWORK_CLIENT_LEFT; break; case NETWORK_ACTION_NAME_CHANGE: strid = STR_NETWORK_NAME_CHANGE; break; diff --git a/src/network/network_client.cpp b/src/network/network_client.cpp index c8116fc4c..28be67608 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -7,6 +7,7 @@ #include "../stdafx.h" #include "../debug.h" #include "../openttd.h" +#include "../gfx_func.h" #include "network_internal.h" #include "core/tcp.h" #include "network_client.h" @@ -25,6 +26,7 @@ #include "../company_func.h" #include "../company_base.h" #include "../company_gui.h" +#include "../company_type.h" #include "../settings_type.h" #include "../rev.h" @@ -43,6 +45,11 @@ static uint32 _password_game_seed; /** The other bit of 'entropy' used to generate a salt for the company passwords. */ static char _password_server_unique_id[NETWORK_UNIQUE_ID_LENGTH]; +/** Maximum number of companies of the currently joined server. */ +static uint8 _network_server_max_companies; +/** Maximum number of spectators of the currently joined server. */ +static uint8 _network_server_max_spectators; + /** Make sure the unique ID length is the same as a md5 hash. */ assert_compile(NETWORK_UNIQUE_ID_LENGTH == 16 * 2 + 1); @@ -86,6 +93,10 @@ void HashCurrentCompanyPassword(const char *password) const char *new_pw = GenerateCompanyPasswordHash(password); strecpy(_network_company_states[_local_company].password, new_pw, lastof(_network_company_states[_local_company].password)); + + if (_network_server) { + NetworkServerUpdateCompanyPassworded(_local_company, !StrEmpty(_network_company_states[_local_company].password)); + } } @@ -315,6 +326,14 @@ DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_RCON)(const char *pass, const char * MY_CLIENT->Send_Packet(p); } +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_MOVE)(CompanyID company, const char *pass) +{ + Packet *p = NetworkSend_Init(PACKET_CLIENT_MOVE); + p->Send_uint8(company); + p->Send_string(GenerateCompanyPasswordHash(pass)); + MY_CLIENT->Send_Packet(p); +} + // ********** // Receiving functions @@ -811,6 +830,51 @@ DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_RCON) return NETWORK_RECV_STATUS_OKAY; } +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_MOVE) +{ + /* Nothing more in this packet... */ + ClientID client_id = (ClientID)p->Recv_uint32(); + CompanyID company_id = (CompanyID)p->Recv_uint8(); + + if (client_id == 0) { + /* definitely an invalid client id, debug message and do nothing. */ + DEBUG(net, 0, "[move] received invalid client index = 0"); + return NETWORK_RECV_STATUS_MALFORMED_PACKET; + } + + const NetworkClientInfo *ci = NetworkFindClientInfoFromClientID(client_id); + /* Just make sure we do not try to use a client_index that does not exist */ + if (ci == NULL) return NETWORK_RECV_STATUS_OKAY; + + /* if not valid player, force spectator, else check player exists */ + if (!IsValidCompanyID(company_id)) company_id = COMPANY_SPECTATOR; + + if (client_id == _network_own_client_id) { + _network_playas = company_id; + SetLocalCompany(company_id); + + /* Disable any buttons in any windows the client is now not supposed to get to, and do it fast. */ + /* Do this ASAP else the client has a chance of sending DoCommands with an incorrect company_id (=kick)! */ + MarkWholeScreenDirty(); + } + + return NETWORK_RECV_STATUS_OKAY; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_CONFIG_UPDATE) +{ + _network_server_max_companies = p->Recv_uint8(); + _network_server_max_spectators = p->Recv_uint8(); + + return NETWORK_RECV_STATUS_OKAY; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_COMPANY_UPDATE) +{ + _network_company_passworded = p->Recv_uint16(); + + return NETWORK_RECV_STATUS_OKAY; +} // The layout for the receive-functions by the client @@ -855,6 +919,10 @@ static NetworkClientPacket * const _network_client_packet[] = { NULL, /*PACKET_CLIENT_RCON,*/ RECEIVE_COMMAND(PACKET_SERVER_CHECK_NEWGRFS), NULL, /*PACKET_CLIENT_NEWGRFS_CHECKED,*/ + RECEIVE_COMMAND(PACKET_SERVER_MOVE), + NULL, /* PACKET_CLIENT_MOVE */ + RECEIVE_COMMAND(PACKET_SERVER_COMPANY_UPDATE), + RECEIVE_COMMAND(PACKET_SERVER_CONFIG_UPDATE), }; // If this fails, check the array above with network_data.h @@ -897,6 +965,17 @@ void NetworkClientSendRcon(const char *password, const char *command) SEND_COMMAND(PACKET_CLIENT_RCON)(password, command); } +/** + * Notify the server of this client wanting to be moved to another company. + * @param company_id id of the company the client wishes to be moved to. + * @param pass the password, is only checked on the server end if a password is needed. + * @return void + */ +void NetworkClientRequestMove(CompanyID company_id, const char *pass) +{ + SEND_COMMAND(PACKET_CLIENT_MOVE)(company_id, pass); +} + void NetworkUpdateClientName() { NetworkClientInfo *ci = NetworkFindClientInfoFromClientID(_network_own_client_id); @@ -945,6 +1024,24 @@ bool NetworkClientPreferTeamChat(const NetworkClientInfo *cio) return false; } +/** + * Check if max_companies has been reached on the server (local check only). + * @return true if the max value has been reached or exceeded, false otherwise. + */ +bool NetworkMaxCompaniesReached() +{ + return ActiveCompanyCount() >= (_network_server ? _settings_client.network.max_companies : _network_server_max_companies); +} + +/** + * Check if max_spectatos has been reached on the server (local check only). + * @return true if the max value has been reached or exceeded, false otherwise. + */ +bool NetworkMaxSpectatorsReached() +{ + return NetworkSpectatorCount() >= (_network_server ? _settings_client.network.max_spectators : _network_server_max_spectators); +} + /** * Print all the clients to the console */ diff --git a/src/network/network_client.h b/src/network/network_client.h index 28ab3affe..88747d587 100644 --- a/src/network/network_client.h +++ b/src/network/network_client.h @@ -18,6 +18,7 @@ DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_SET_PASSWORD)(const char *password); DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_SET_NAME)(const char *name); DEF_CLIENT_SEND_COMMAND(PACKET_CLIENT_ACK); DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_RCON)(const char *pass, const char *command); +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_MOVE)(CompanyID company, const char *pass); NetworkRecvStatus NetworkClient_ReadPackets(NetworkClientSocket *cs); void NetworkClient_Connected(); diff --git a/src/network/network_func.h b/src/network/network_func.h index e51ff38d7..70bc96ff0 100644 --- a/src/network/network_func.h +++ b/src/network/network_func.h @@ -38,18 +38,24 @@ void NetworkPopulateCompanyStats(NetworkCompanyStats *stats); void NetworkUpdateClientInfo(ClientID client_id); void NetworkClientConnectGame(NetworkAddress address); +void NetworkClientRequestMove(CompanyID company, const char *pass = ""); void NetworkClientSendRcon(const char *password, const char *command); void NetworkClientSendChat(NetworkAction action, DestType type, int dest, const char *msg, int64 data = 0); void NetworkClientSetPassword(const char *password); bool NetworkClientPreferTeamChat(const NetworkClientInfo *cio); +bool NetworkCompanyIsPassworded(CompanyID company_id); +bool NetworkMaxCompaniesReached(); +bool NetworkMaxSpectatorsReached(); void NetworkPrintClients(); /*** Commands ran by the server ***/ void NetworkServerMonthlyLoop(); void NetworkServerYearlyLoop(); void NetworkServerChangeOwner(Owner current_owner, Owner new_owner); +void NetworkServerSendConfigUpdate(); void NetworkServerShowStatusToConsole(); bool NetworkServerStart(); +void NetworkServerUpdateCompanyPassworded(CompanyID company_id, bool passworded); bool NetworkServerChangeClientName(ClientID client_id, const char *new_name); NetworkClientInfo *NetworkFindClientInfoFromIndex(ClientIndex index); @@ -57,6 +63,7 @@ NetworkClientInfo *NetworkFindClientInfoFromClientID(ClientID client_id); NetworkClientInfo *NetworkFindClientInfoFromIP(const char *ip); const char *GetClientIP(const NetworkClientInfo *ci); +void NetworkServerDoMove(ClientID client_id, CompanyID company_id); void NetworkServerSendRcon(ClientID client_id, ConsoleColour colour_code, const char *string); void NetworkServerSendError(ClientID client_id, NetworkErrorCode error); void NetworkServerSendChat(NetworkAction action, DestType type, int dest, const char *msg, ClientID from_id, int64 data = 0); diff --git a/src/network/network_internal.h b/src/network/network_internal.h index b5c6d4de2..b73fe1d0f 100644 --- a/src/network/network_internal.h +++ b/src/network/network_internal.h @@ -121,6 +121,8 @@ extern uint16 _network_udp_broadcast; extern uint8 _network_advertise_retries; +extern CompanyMask _network_company_passworded; + void NetworkTCPQueryServer(NetworkAddress address); void NetworkAddServer(const char *b); diff --git a/src/network/network_server.cpp b/src/network/network_server.cpp index d291bb92a..647358ecf 100644 --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -586,6 +586,32 @@ DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_RCON)(NetworkClientSocket *cs, uint1 cs->Send_Packet(p); } +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_MOVE)(NetworkClientSocket *cs, ClientID client_id, CompanyID company_id) +{ + Packet *p = NetworkSend_Init(PACKET_SERVER_MOVE); + + p->Send_uint32(client_id); + p->Send_uint8(company_id); + cs->Send_Packet(p); +} + +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_COMPANY_UPDATE)(NetworkClientSocket *cs) +{ + Packet *p = NetworkSend_Init(PACKET_SERVER_COMPANY_UPDATE); + + p->Send_uint16(_network_company_passworded); + cs->Send_Packet(p); +} + +DEF_SERVER_SEND_COMMAND(PACKET_SERVER_CONFIG_UPDATE) +{ + Packet *p = NetworkSend_Init(PACKET_SERVER_CONFIG_UPDATE); + + p->Send_uint8(_settings_client.network.max_companies); + p->Send_uint8(_settings_client.network.max_spectators); + cs->Send_Packet(p); +} + // ********** // Receiving functions // DEF_SERVER_RECEIVE_COMMAND has parameter: NetworkClientSocket *cs, Packet *p @@ -803,6 +829,12 @@ DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_MAP_OK) NetworkServerSendChat(NETWORK_ACTION_SERVER_MESSAGE, DESTTYPE_BROADCAST, 0, "", CLIENT_ID_SERVER, NETWORK_SERVER_MESSAGE_GAME_PAUSED_CONNECT); } + + /* also update the new client with our max values */ + SEND_COMMAND(PACKET_SERVER_CONFIG_UPDATE)(cs); + + /* quickly update the syncing client with company details */ + SEND_COMMAND(PACKET_SERVER_COMPANY_UPDATE)(cs); } else { // Wrong status for this packet, give a warning to client, and close connection SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_NOT_EXPECTED); @@ -872,6 +904,12 @@ DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_COMMAND) return; } + /* Check if we are full - else it's possible for spectators to send a CMD_COMPANY_CTRL and the company is created regardless of max_companies! */ + if (ActiveCompanyCount() >= _settings_client.network.max_companies) { + NetworkServerSendChat(NETWORK_ACTION_SERVER_MESSAGE, DESTTYPE_CLIENT, ci->client_id, "cannot create new company, server full", CLIENT_ID_SERVER); + return; + } + /* XXX - Execute the command as a valid company. Normally this would be done by a * spectator, but that is not allowed any commands. So do an impersonation. The drawback * of this is that the first company's last_built_tile is also updated... */ @@ -1145,6 +1183,7 @@ DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_SET_PASSWORD) if (IsValidCompanyID(ci->client_playas)) { strecpy(_network_company_states[ci->client_playas].password, password, lastof(_network_company_states[ci->client_playas].password)); + NetworkServerUpdateCompanyPassworded(ci->client_playas, !StrEmpty(_network_company_states[ci->client_playas].password)); } } @@ -1197,6 +1236,30 @@ DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_RCON) return; } +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_MOVE) +{ + CompanyID company_id = (Owner)p->Recv_uint8(); + + /* Check if the company is valid */ + if (!IsValidCompanyID(company_id) && company_id != COMPANY_SPECTATOR) return; + + /* Check if we require a password for this company */ + if (company_id != COMPANY_SPECTATOR && !StrEmpty(_network_company_states[company_id].password)) { + /* we need a password from the client - should be in this packet */ + char password[NETWORK_PASSWORD_LENGTH]; + p->Recv_string(password, sizeof(password)); + + /* Incorrect password sent, return! */ + if (strcmp(password, _network_company_states[company_id].password) != 0) { + DEBUG(net, 2, "[move] wrong password from client-id #%d for company #%d", cs->client_id, company_id + 1); + return; + } + } + + /* if we get here we can move the client */ + NetworkServerDoMove(cs->client_id, company_id); +} + // The layout for the receive-functions by the server typedef void NetworkServerPacket(NetworkClientSocket *cs, Packet *p); @@ -1240,6 +1303,10 @@ static NetworkServerPacket * const _network_server_packet[] = { RECEIVE_COMMAND(PACKET_CLIENT_RCON), NULL, /*PACKET_CLIENT_CHECK_NEWGRFS,*/ RECEIVE_COMMAND(PACKET_CLIENT_NEWGRFS_CHECKED), + NULL, /*PACKET_SERVER_MOVE,*/ + RECEIVE_COMMAND(PACKET_CLIENT_MOVE), + NULL, /*PACKET_SERVER_COMPANY_UPDATE,*/ + NULL, /*PACKET_SERVER_CONFIG_UPDATE,*/ }; // If this fails, check the array above with network_data.h @@ -1394,6 +1461,7 @@ static void NetworkAutoCleanCompanies() _network_company_states[c->index].password[0] = '\0'; IConsolePrintF(CC_DEFAULT, "Auto-removed protection from company #%d", c->index + 1); _network_company_states[c->index].months_empty = 0; + NetworkServerUpdateCompanyPassworded(c->index, false); } } else { /* It is not empty, reset the date */ @@ -1635,6 +1703,63 @@ void NetworkServerShowStatusToConsole() } } +/** + * Send Config Update + */ +void NetworkServerSendConfigUpdate() +{ + NetworkClientSocket *cs; + + FOR_ALL_CLIENT_SOCKETS(cs) { + SEND_COMMAND(PACKET_SERVER_CONFIG_UPDATE)(cs); + } +} + +void NetworkServerUpdateCompanyPassworded(CompanyID company_id, bool passworded) +{ + if (NetworkCompanyIsPassworded(company_id) == passworded) return; + + SB(_network_company_passworded, company_id, 1, !!passworded); + + NetworkClientSocket *cs; + FOR_ALL_CLIENT_SOCKETS(cs) { + SEND_COMMAND(PACKET_SERVER_COMPANY_UPDATE)(cs); + } +} + +/** + * Handle the tid-bits of moving a client from one company to another. + * @param client_id id of the client we want to move. + * @param company_id id of the company we want to move the client to. + * @return void + **/ +void NetworkServerDoMove(ClientID client_id, CompanyID company_id) +{ + /* Only allow non-dedicated servers and normal clients to be moved */ + if (client_id == CLIENT_ID_SERVER && _network_dedicated) return; + + NetworkClientInfo *ci = NetworkFindClientInfoFromClientID(client_id); + + /* No need to waste network resources if the client is in the company already! */ + if (ci->client_playas == company_id) return; + + ci->client_playas = company_id; + + if (client_id == CLIENT_ID_SERVER) { + SetLocalCompany(company_id); + } else { + SEND_COMMAND(PACKET_SERVER_MOVE)(NetworkFindClientStateFromClientID(client_id), client_id, company_id); + } + + /* announce the client's move */ + NetworkUpdateClientInfo(client_id); + + NetworkAction action = (company_id == COMPANY_SPECTATOR) ? NETWORK_ACTION_COMPANY_SPECTATOR : NETWORK_ACTION_COMPANY_JOIN; + NetworkServerSendChat(action, DESTTYPE_BROADCAST, 0, "", client_id, company_id + 1); + + CheckMinActiveClients(); +} + void NetworkServerSendRcon(ClientID client_id, ConsoleColour colour_code, const char *string) { SEND_COMMAND(PACKET_SERVER_RCON)(NetworkFindClientStateFromClientID(client_id), colour_code, string); diff --git a/src/network/network_server.h b/src/network/network_server.h index 593579aaa..4950b74d5 100644 --- a/src/network/network_server.h +++ b/src/network/network_server.h @@ -13,6 +13,7 @@ DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_ERROR)(NetworkClientSocket *cs, Netw DEF_SERVER_SEND_COMMAND(PACKET_SERVER_SHUTDOWN); DEF_SERVER_SEND_COMMAND(PACKET_SERVER_NEWGAME); DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_RCON)(NetworkClientSocket *cs, uint16 color, const char *command); +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_MOVE)(NetworkClientSocket *cs, uint16 client_id, CompanyID company_id); bool NetworkServer_ReadPackets(NetworkClientSocket *cs); void NetworkServer_Tick(bool send_frame); diff --git a/src/network/network_type.h b/src/network/network_type.h index 9104e2f0f..f2208b8c7 100644 --- a/src/network/network_type.h +++ b/src/network/network_type.h @@ -80,6 +80,9 @@ enum NetworkAction { NETWORK_ACTION_CHAT_CLIENT, NETWORK_ACTION_GIVE_MONEY, NETWORK_ACTION_NAME_CHANGE, + NETWORK_ACTION_COMPANY_SPECTATOR, + NETWORK_ACTION_COMPANY_JOIN, + NETWORK_ACTION_COMPANY_NEW, }; /** Messages the server can give */ diff --git a/src/settings.cpp b/src/settings.cpp index a4d3fb516..477801bf5 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1180,6 +1180,13 @@ static int32 UpdateRconPassword(int32 p1) return 0; } +static int32 UpdateClientConfigValues(int32 p1) +{ + if (_network_server) NetworkServerSendConfigUpdate(); + + return 0; +} + #endif /* ENABLE_NETWORK */ @@ -1549,9 +1556,9 @@ const SettingDesc _patch_settings[] = { SDTC_BOOL(network.autoclean_companies, S, NO, false, STR_NULL, NULL), SDTC_VAR(network.autoclean_unprotected, SLE_UINT8, S,D0|NO, 12, 0, 240, 0, STR_NULL, NULL), SDTC_VAR(network.autoclean_protected, SLE_UINT8, S,D0|NO, 36, 0, 240, 0, STR_NULL, NULL), - SDTC_VAR(network.max_companies, SLE_UINT8, S, NO, 8, 1,MAX_COMPANIES,0, STR_NULL, NULL), + SDTC_VAR(network.max_companies, SLE_UINT8, S, NO, 8, 1,MAX_COMPANIES,0, STR_NULL, UpdateClientConfigValues), SDTC_VAR(network.max_clients, SLE_UINT8, S, NO, 10, 2, MAX_CLIENTS, 0, STR_NULL, NULL), - SDTC_VAR(network.max_spectators, SLE_UINT8, S, NO, 10, 0, MAX_CLIENTS, 0, STR_NULL, NULL), + SDTC_VAR(network.max_spectators, SLE_UINT8, S, NO, 10, 0, MAX_CLIENTS, 0, STR_NULL, UpdateClientConfigValues), SDTC_VAR(network.restart_game_year, SLE_INT32, S,D0|NO|NC,0, MIN_YEAR, MAX_YEAR, 1, STR_NULL, NULL), SDTC_VAR(network.min_active_clients, SLE_UINT8, S, NO, 0, 0, MAX_CLIENTS, 0, STR_NULL, UpdateMinActiveClients), SDTC_OMANY(network.server_lang, SLE_UINT8, S, NO, 0, 35, "ANY|ENGLISH|GERMAN|FRENCH|BRAZILIAN|BULGARIAN|CHINESE|CZECH|DANISH|DUTCH|ESPERANTO|FINNISH|HUNGARIAN|ICELANDIC|ITALIAN|JAPANESE|KOREAN|LITHUANIAN|NORWEGIAN|POLISH|PORTUGUESE|ROMANIAN|RUSSIAN|SLOVAK|SLOVENIAN|SPANISH|SWEDISH|TURKISH|UKRAINIAN|AFRIKAANS|CROATIAN|CATALAN|ESTONIAN|GALICIAN|GREEK|LATVIAN", STR_NULL, NULL), diff --git a/src/toolbar_gui.cpp b/src/toolbar_gui.cpp index dcea1efc9..96f9b312d 100644 --- a/src/toolbar_gui.cpp +++ b/src/toolbar_gui.cpp @@ -45,6 +45,7 @@ #include "network/network.h" #include "network/network_gui.h" +#include "network/network_func.h" #include "table/strings.h" #include "table/sprites.h" @@ -199,6 +200,13 @@ static void PopupMainToolbMenu(Window *w, int widget, StringID string, int count SndPlayFx(SND_15_BEEP); } +/** Enum for the Company Toolbar's network related buttons */ +enum { + CTMN_CLIENT_LIST = -1, ///< Show the client list + CTMN_NEW_COMPANY = -2, ///< Create a new company + CTMN_SPECTATE = -3, ///< Become spectator +}; + /** * Pop up a generic company list menu. */ @@ -206,17 +214,25 @@ static void PopupMainCompanyToolbMenu(Window *w, int widget, int grey = 0) { DropDownList *list = new DropDownList(); +#ifdef ENABLE_NETWORK if (widget == TBN_COMPANIES && _networking) { /* Add the client list button for the companies menu */ - list->push_back(new DropDownListStringItem(STR_NETWORK_CLIENT_LIST, -1, false)); + list->push_back(new DropDownListStringItem(STR_NETWORK_CLIENT_LIST, CTMN_CLIENT_LIST, false)); + + if (_local_company == COMPANY_SPECTATOR) { + list->push_back(new DropDownListStringItem(STR_NETWORK_COMPANY_LIST_NEW_COMPANY, CTMN_NEW_COMPANY, NetworkMaxCompaniesReached())); + } else { + list->push_back(new DropDownListStringItem(STR_NETWORK_COMPANY_LIST_SPECTATE, CTMN_SPECTATE, NetworkMaxSpectatorsReached())); + } } +#endif /* ENABLE_NETWORK */ for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) { if (!IsValidCompanyID(c)) continue; list->push_back(new DropDownListCompanyItem(c, false, HasBit(grey, c))); } - ShowDropDownList(w, list, _local_company == COMPANY_SPECTATOR ? -1 : _local_company, widget, 240, true, true); + ShowDropDownList(w, list, _local_company == COMPANY_SPECTATOR ? CTMN_CLIENT_LIST : _local_company, widget, 240, true, true); SndPlayFx(SND_15_BEEP); } @@ -448,11 +464,33 @@ static void ToolbarCompaniesClick(Window *w) static void MenuClickCompany(int index) { - if (_networking && index == -1) { - ShowClientList(); - } else { - ShowCompany((CompanyID)index); +#ifdef ENABLE_NETWORK + if (_networking) { + switch (index) { + case CTMN_CLIENT_LIST: + ShowClientList(); + return; + + case CTMN_NEW_COMPANY: + if (_network_server) { + DoCommandP(0, 0, _network_own_client_id, CMD_COMPANY_CTRL); + } else { + NetworkSend_Command(0, 0, 0, CMD_COMPANY_CTRL, NULL, NULL); + } + return; + + case CTMN_SPECTATE: + if (_network_server) { + NetworkServerDoMove(CLIENT_ID_SERVER, COMPANY_SPECTATOR); + MarkWholeScreenDirty(); + } else { + NetworkClientRequestMove(COMPANY_SPECTATOR); + } + return; + } } +#endif /* ENABLE_NETWORK */ + ShowCompany((CompanyID)index); } /* --- Graphs button menu --- */ -- cgit v1.2.3-54-g00ecf