# HG changeset patch # User Darkvater # Date 2006-11-10 17:52:51 # Node ID 7312b67c8cc3bfe931d1c078b1fe386bbdbe2b2e # Parent 785ea87e93a42ff453eed263d5d2fb00e13cb584 (svn r7125) -Fix: Several errors/glitches related to multiplayer and bankrupcy, mainly such a thing happening to a server, and non updated company-information. Also fixes FS#393. diff --git a/command.c b/command.c --- a/command.c +++ b/command.c @@ -424,9 +424,9 @@ bool DoCommandP(TileIndex tile, uint32 p error_part1 = GB(cmd, 16, 16); _additional_cash_required = 0; - /** Spectator has no rights except for the dedicated server which - * is a spectator but is the server, so can do anything */ - if (_current_player == PLAYER_SPECTATOR && !_network_dedicated) { + /** Spectator has no rights except for the (dedicated) server which + * is/can be a spectator but as the server it can do anything */ + if (_current_player == PLAYER_SPECTATOR && !_network_server) { ShowErrorMessage(_error_message, error_part1, x, y); _cmd_text = NULL; return false; @@ -495,12 +495,14 @@ bool DoCommandP(TileIndex tile, uint32 p * send it to the command-queue and abort execution * If we are a dedicated server temporarily switch local player, otherwise * the other parties won't be able to execute our command and will desync. - * @todo Rewrite dedicated server to something more than a dirty hack! + * We also need to do this if the server's company has gone bankrupt + * @todo Rewrite (dedicated) server to something more than a dirty hack! */ if (_networking && !(cmd & CMD_NETWORK_COMMAND)) { - if (_network_dedicated) _local_player = 0; + PlayerID pbck = _local_player; + if (_network_dedicated || (_network_server && pbck == PLAYER_SPECTATOR)) _local_player = 0; NetworkSend_Command(tile, p1, p2, cmd, callback); - if (_network_dedicated) _local_player = PLAYER_SPECTATOR; + if (_network_dedicated || (_network_server && pbck == PLAYER_SPECTATOR)) _local_player = pbck; _docommand_recursive = 0; _cmd_text = NULL; return true; diff --git a/economy.c b/economy.c --- a/economy.c +++ b/economy.c @@ -414,40 +414,51 @@ static void PlayersCheckBankrupt(Player SetDParam(1, p->name_2); AddNewsItem( (StringID)(owner | NB_BBANKRUPT), NEWS_FLAGS(NM_CALLBACK, 0, NT_COMPANY_INFO, DNC_BANKRUPCY),0,0); - // If the player is human, and it is no network play, leave the player playing - if (IsHumanPlayer(owner) && !_networking) { - p->bankrupt_asked = 255; - p->bankrupt_timeout = 0x456; - } else { + if (IsHumanPlayer(owner)) { + /* XXX - If we are in offline mode, leave the player playing. Eg. there + * is no THE-END, otherwise mark the player as spectator to make sure + * he/she is no long in control of this company */ + if (!_networking) { + p->bankrupt_asked = 0xFF; + p->bankrupt_timeout = 0x456; + break; + } else if (owner == _local_player) { + _local_player = _network_playas = PLAYER_SPECTATOR; + } + #ifdef ENABLE_NETWORK - /* If we are the server make sure it is clear that this player is not active */ - if (IsHumanPlayer(owner) && _network_server) { + /* The server has to handle all administrative issues, for example + * updating and notifying all clients of what has happened */ + if (_network_server) { const NetworkClientState *cs; - /* Find all clients that were in control of this company */ + NetworkClientInfo *ci = NetworkFindClientInfoFromIndex(NETWORK_SERVER_INDEX); + + /* The server has just gone belly-up, mark it as spectator */ + if (owner == ci->client_playas) { + ci->client_playas = PLAYER_SPECTATOR; + NetworkUpdateClientInfo(NETWORK_SERVER_INDEX); + } + + /* Find all clients that were in control of this company, + * and mark them as spectator; broadcast this message to everyone */ FOR_ALL_CLIENTS(cs) { - NetworkClientInfo *ci = DEREF_CLIENT_INFO(cs); + ci = DEREF_CLIENT_INFO(cs); if (ci->client_playas == owner) { ci->client_playas = PLAYER_SPECTATOR; - // Send the new info to all the clients - NetworkUpdateClientInfo(_network_own_client_index); + NetworkUpdateClientInfo(ci->client_index); } } } - // Make sure the player no longer controls the company - if (IsHumanPlayer(owner) && owner == _local_player) { - // Switch the player to spectator.. - _local_player = PLAYER_SPECTATOR; - } #endif /* ENABLE_NETWORK */ + } - /* Remove the player */ - ChangeOwnershipOfPlayerItems(owner, PLAYER_SPECTATOR); - // Register the player as not-active - p->is_active = false; + /* Remove the player */ + ChangeOwnershipOfPlayerItems(owner, PLAYER_SPECTATOR); + /* Register the player as not-active */ + p->is_active = false; - if (!IsHumanPlayer(owner) && (!_networking || _network_server) && _ai.enabled) - AI_PlayerDied(owner); - } + if (!IsHumanPlayer(owner) && (!_networking || _network_server) && _ai.enabled) + AI_PlayerDied(owner); } } } diff --git a/players.c b/players.c --- a/players.c +++ b/players.c @@ -819,74 +819,81 @@ int32 CmdPlayerCtrl(TileIndex tile, uint switch (p1) { case 0: { /* Create a new player */ + /* Joining Client: + * _local_player: PLAYER_SPECTATOR + * _network_playas/cid = requested company/player + * + * Other client(s)/server: + * _local_player/_network_playas: what they play as + * cid = requested company/player of joining client */ Player *p; uint16 cid = p2; // ClientID + /* This command is only executed in a multiplayer game */ + if (!_networking) return CMD_ERROR; + /* ClientID would be valid up to MAX_CLIENT_INFO, but as it has to be a * new player, its valid range is restricted to that of players */ if (!(flags & DC_EXEC) || !IsValidPlayer((PlayerID)cid)) return 0; + /* Delete multiplayer progress bar */ + DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0); + p = DoStartupNewPlayer(false); -#ifdef ENABLE_NETWORK - if (_networking && !_network_server && _local_player == PLAYER_SPECTATOR) { - /* In case we are a client joining a server... */ - DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0); - } -#endif /* ENABLE_NETWORK */ - - if (p != NULL) { - if (_local_player == PLAYER_SPECTATOR) { - /* Check if we do not want to be a spectator in network */ - if (!_networking || - (_network_server && !_network_dedicated) || - _network_playas != PLAYER_SPECTATOR) { - _local_player = p->index; - MarkWholeScreenDirty(); - } - } else if (p->index == _local_player) { - DoCommandP(0, (_patches.autorenew << 15 ) | (_patches.autorenew_months << 16) | 4, _patches.autorenew_money, NULL, CMD_SET_AUTOREPLACE); - } + /* A new player could not be created, revert to being a spectator */ + if (p == NULL) { #ifdef ENABLE_NETWORK if (_network_server) { - /* 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) */ NetworkClientInfo *ci = &_network_client_info[cid]; - ci->client_playas = p->index; + ci->client_playas = PLAYER_SPECTATOR; NetworkUpdateClientInfo(ci->client_index); - - if (IsValidPlayer(ci->client_playas)) { - PlayerID player_backup = _local_player; - _network_player_info[p->index].months_empty = 0; + } else +#endif /* ENABLE_NETWORK */ + { + _local_player = _network_playas = PLAYER_SPECTATOR; + } + break; + } - /* XXX - When a client joins, we automatically set its name to the - * player's name (for some reason). As it stands now only the server - * knows the client's name, so it needs to send out a "broadcast" to - * do this. To achieve this we send a network command. However, it - * uses _local_player to execute the command as. To prevent abuse - * (eg. only yourself can change your name/company), we 'cheat' by - * impersonation _local_player as the server. Not the best solution; - * but it works. - * TODO: Perhaps this could be improved by when the client is ready - * with joining to let it send itself the command, and not the server? - * For example in network_client.c:534? */ - _cmd_text = ci->client_name; - _local_player = ci->client_playas; - NetworkSend_Command(0, 0, 0, CMD_CHANGE_PRESIDENT_NAME, NULL); - _local_player = player_backup; - } - } - } else if (_network_server) { - // Creating player failed, defer client to spectator + /* This is the joining client who wants a new company */ + if (_local_player != _network_playas) { + assert(_local_player == PLAYER_SPECTATOR && _network_playas == p->index); + _local_player = p->index; + MarkWholeScreenDirty(); + } + +#ifdef ENABLE_NETWORK + if (_network_server) { /* 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) */ NetworkClientInfo *ci = &_network_client_info[cid]; - ci->client_playas = PLAYER_SPECTATOR; + ci->client_playas = p->index; NetworkUpdateClientInfo(ci->client_index); + + if (IsValidPlayer(ci->client_playas)) { + PlayerID player_backup = _local_player; + _network_player_info[p->index].months_empty = 0; + + /* XXX - When a client joins, we automatically set its name to the + * player's name (for some reason). As it stands now only the server + * knows the client's name, so it needs to send out a "broadcast" to + * do this. To achieve this we send a network command. However, it + * uses _local_player to execute the command as. To prevent abuse + * (eg. only yourself can change your name/company), we 'cheat' by + * impersonation _local_player as the server. Not the best solution; + * but it works. + * TODO: Perhaps this could be improved by when the client is ready + * with joining to let it send itself the command, and not the server? + * For example in network_client.c:534? */ + _cmd_text = ci->client_name; + _local_player = ci->client_playas; + NetworkSend_Command(0, 0, 0, CMD_CHANGE_PRESIDENT_NAME, NULL); + _local_player = player_backup; + } + } #endif /* ENABLE_NETWORK */ - } } break; case 1: /* Make a new AI player */ @@ -933,6 +940,7 @@ int32 CmdPlayerCtrl(TileIndex tile, uint ChangeOwnershipOfPlayerItems(pid_old, pid_new); DeletePlayerStuff(pid_old); } break; + default: return CMD_ERROR; }