From 402660e4471377f5d33716331f498447a294c86f Mon Sep 17 00:00:00 2001 From: Samu Date: Fri, 1 Feb 2019 14:34:01 +0000 Subject: [PATCH] Add: Client setting gui.start_spectator Activates a spectator slot in single player and initiate games as a spectator --- src/cheat_gui.cpp | 6 ++--- src/company_cmd.cpp | 3 --- src/company_gui.cpp | 7 +++++- src/console_cmds.cpp | 6 ++--- src/economy.cpp | 27 ++++++++++----------- src/lang/english.txt | 3 +++ src/openttd.cpp | 37 +++++++++++++++++------------ src/saveload/afterload.cpp | 9 ++++--- src/script/script_gui.cpp | 4 +--- src/settings_type.h | 1 + src/table/settings/gui_settings.ini | 6 +++++ src/toolbar_gui.cpp | 27 +++++++++++++++++++-- 12 files changed, 86 insertions(+), 50 deletions(-) diff --git a/src/cheat_gui.cpp b/src/cheat_gui.cpp index 0835afbe9f..a7607c69f4 100644 --- a/src/cheat_gui.cpp +++ b/src/cheat_gui.cpp @@ -72,8 +72,8 @@ static int32_t ClickMoneyCheat(int32_t, int32_t change_direction) */ static int32_t ClickChangeCompanyCheat(int32_t new_value, int32_t change_direction) { - while ((uint)new_value < Company::GetPoolSize()) { - if (Company::IsValidID((CompanyID)new_value)) { + while ((uint)new_value <= COMPANY_SPECTATOR) { + if (Company::IsValidID((CompanyID)new_value) || (new_value == COMPANY_SPECTATOR && _settings_client.gui.start_spectator)) { SetLocalCompany((CompanyID)new_value); return _local_company.base(); } @@ -303,7 +303,7 @@ struct CheatWindow : Window { case STR_CHEAT_CHANGE_COMPANY: { SetDParam(0, val + 1); uint offset = WidgetDimensions::scaled.hsep_indent + GetStringBoundingBox(ce->str).width; - DrawCompanyIcon(_local_company, rtl ? text_right - offset - WidgetDimensions::scaled.hsep_indent : text_left + offset, y + icon_y_offset); + if (_local_company != COMPANY_SPECTATOR) DrawCompanyIcon(_local_company, rtl ? text_right - offset - WidgetDimensions::scaled.hsep_indent : text_left + offset, y + icon_y_offset); break; } diff --git a/src/company_cmd.cpp b/src/company_cmd.cpp index e13233c5ca..5ae7b520a7 100644 --- a/src/company_cmd.cpp +++ b/src/company_cmd.cpp @@ -925,9 +925,6 @@ CommandCost CmdCompanyCtrl(DoCommandFlags flags, CompanyCtrlAction cca, CompanyI case CCA_DELETE: { // Delete a company if (reason >= CRR_END) return CMD_ERROR; - /* We can't delete the last existing company in singleplayer mode. */ - if (!_networking && Company::GetNumItems() == 1) return CMD_ERROR; - Company *c = Company::GetIfValid(company_id); if (c == nullptr) return CMD_ERROR; diff --git a/src/company_gui.cpp b/src/company_gui.cpp index f91f4f4d6a..f7563c3ea2 100644 --- a/src/company_gui.cpp +++ b/src/company_gui.cpp @@ -2246,7 +2246,7 @@ struct CompanyWindow : Window reinit |= this->GetWidget(WID_C_SELECT_HOSTILE_TAKEOVER)->SetDisplayedPlane((local || _local_company == COMPANY_SPECTATOR || !c->is_ai || _networking) ? SZSP_NONE : 0); /* Multiplayer buttons. */ - reinit |= this->GetWidget(WID_C_SELECT_MULTIPLAYER)->SetDisplayedPlane((!_networking || !NetworkCanJoinCompany(c->index) || _local_company == c->index) ? (int)SZSP_NONE : 0); + reinit |= this->GetWidget(WID_C_SELECT_MULTIPLAYER)->SetDisplayedPlane(((!_networking && (c->is_ai || local || _local_company != COMPANY_SPECTATOR)) || (_networking && (!NetworkCanJoinCompany(c->index) || _local_company == c->index))) ? (int)SZSP_NONE : 0); this->SetWidgetDisabledState(WID_C_COMPANY_JOIN, c->is_ai); @@ -2512,6 +2512,11 @@ struct CompanyWindow : Window break; case WID_C_COMPANY_JOIN: { + if (!_networking) { + if (_local_company == COMPANY_SPECTATOR) SetLocalCompany((CompanyID)this->window_number); + break; + } + this->query_widget = WID_C_COMPANY_JOIN; CompanyID company = this->window_number; if (_network_server) { diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index e66af26f6a..a19edb1f6a 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -1480,8 +1480,7 @@ DEF_CONSOLE_CMD(ConReloadAI) return true; } - /* In singleplayer mode the player can be in an AI company, after cheating or loading network save with an AI in first slot. */ - if (Company::IsHumanID(company_id) || company_id == _local_company) { + if (Company::IsHumanID(company_id)) { IConsolePrint(CC_ERROR, "Company is not controlled by an AI."); return true; } @@ -1518,8 +1517,7 @@ DEF_CONSOLE_CMD(ConStopAI) return true; } - /* In singleplayer mode the player can be in an AI company, after cheating or loading network save with an AI in first slot. */ - if (Company::IsHumanID(company_id) || company_id == _local_company) { + if (Company::IsHumanID(company_id)) { IConsolePrint(CC_ERROR, "Company is not controlled by an AI."); return true; } diff --git a/src/economy.cpp b/src/economy.cpp index 88482c87ae..3a174c9bfb 100644 --- a/src/economy.cpp +++ b/src/economy.cpp @@ -328,16 +328,10 @@ void ChangeOwnershipOfCompanyItems(Owner old_owner, Owner new_owner) /* In all cases, make spectators of clients connected to that company */ if (_networking) NetworkClientsToSpectators(old_owner); if (old_owner == _local_company) { - /* Single player cheated to AI company. - * There are no spectators in singleplayer mode, so we must pick some other company. */ + /* Single player company bankrupts. Move player to Spectator. */ assert(!_networking); Backup cur_company2(_current_company); - for (const Company *c : Company::Iterate()) { - if (c->index != old_owner) { - SetLocalCompany(c->index); - break; - } - } + SetLocalCompany(COMPANY_SPECTATOR); cur_company2.Restore(); assert(old_owner != _local_company); } @@ -609,10 +603,11 @@ static void CompanyCheckBankrupt(Company *c) default: case 10: { if (!_networking && _local_company == c->index) { - /* If we are in singleplayer mode, leave the company playing. Eg. there - * is no THE-END, otherwise mark the client as spectator to make sure - * they are no longer in control of this company. However... when you - * join another company (cheat) the "unowned" company can bankrupt. */ + /* If we are in singleplayer mode and if spectator mode isn't active, + * leave the company playing. Eg. there is no THE-END, otherwise mark + * the client as spectator to make sure they are no longer in control + * of this company. However... when you join another company (cheat) + * the "unowned" company can bankrupt. */ c->bankrupt_asked.Set(); break; } @@ -624,8 +619,8 @@ static void CompanyCheckBankrupt(Company *c) * updating the local company triggers an assert later on. In the * case of a network game the command will be processed at a time * that changing the current company is okay. In case of single - * player we are sure (the above check) that we are not the local - * company and thus we won't be moved. */ + * player we ensure that the local company remains the same until + * the end of StateGameLoop, where it could be changed. */ if (!_networking || _network_server) { Command::Post(CCA_DELETE, c->index, CRR_BANKRUPT, INVALID_CLIENT_ID); return; @@ -645,7 +640,11 @@ static void CompaniesGenStatistics() { /* Check for bankruptcy each month */ for (Company *c : Company::Iterate()) { + Backup cur_company(_current_company); + Backup loc_company(_local_company); CompanyCheckBankrupt(c); + loc_company.Restore(); + cur_company.Restore(); } Backup cur_company(_current_company); diff --git a/src/lang/english.txt b/src/lang/english.txt index d9cdda01e3..d17c703724 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -644,6 +644,9 @@ STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP :{BLACK}Show det STR_GRAPH_KEY_CAPTION :{WHITE}Key to company graphs STR_GRAPH_KEY_COMPANY_SELECTION_TOOLTIP :{BLACK}Toggle graph of this company +# Company list toolbar dropdown +STR_COMPANY_LIST_NEW_COMPANY :New company + # Company league window STR_COMPANY_LEAGUE_TABLE_CAPTION :{WHITE}Company League Table STR_COMPANY_LEAGUE_COMPANY_NAME :{ORANGE}{COMPANY} {BLACK}{COMPANY_NUM} '{STRING}' diff --git a/src/openttd.cpp b/src/openttd.cpp index 269c9c2d38..8e10d0ac81 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -842,8 +842,8 @@ static void OnStartScenario() static void OnStartGame(bool dedicated_server) { /* Update the local company for a loaded game. It is either the first available company - * or in the case of a dedicated server, a spectator */ - SetLocalCompany(dedicated_server ? COMPANY_SPECTATOR : GetFirstPlayableCompanyID()); + * or, in the case of starting as spectator or a dedicated server, a spectator. */ + SetLocalCompany((dedicated_server || _settings_client.gui.start_spectator) ? COMPANY_SPECTATOR : GetFirstPlayableCompanyID()); NetworkOnGameStart(); @@ -862,23 +862,25 @@ static void MakeNewGameDone() return; } - /* Create a single company */ - DoStartupNewCompany(false); + if (!_settings_client.gui.start_spectator) { + /* Create a single company */ + DoStartupNewCompany(false); - Company *c = Company::Get(CompanyID::Begin()); - c->settings = _settings_client.company; + Company *c = Company::Get(CompanyID::Begin()); + c->settings = _settings_client.company; - /* Overwrite color from settings if needed - * COLOUR_END corresponds to Random colour */ + /* Overwrite color from settings if needed + * COLOUR_END corresponds to Random colour */ - if (_settings_client.gui.starting_colour != COLOUR_END) { - c->colour = _settings_client.gui.starting_colour; - ResetCompanyLivery(c); - _company_colours[c->index] = c->colour; - } + if (_settings_client.gui.starting_colour != COLOUR_END) { + c->colour = _settings_client.gui.starting_colour; + ResetCompanyLivery(c); + _company_colours[c->index] = c->colour; + } - if (_settings_client.gui.starting_colour_secondary != COLOUR_END && HasBit(_loaded_newgrf_features.used_liveries, LS_DEFAULT)) { - Command::Post(LS_DEFAULT, false, _settings_client.gui.starting_colour_secondary); + if (_settings_client.gui.starting_colour_secondary != COLOUR_END && HasBit(_loaded_newgrf_features.used_liveries, LS_DEFAULT)) { + Command::Post(LS_DEFAULT, false, _settings_client.gui.starting_colour_secondary); + } } OnStartGame(false); @@ -1231,6 +1233,7 @@ void StateGameLoop() Layouter::ReduceLineCache(); + bool valid_local_company = _game_mode != GM_EDITOR && !_networking && _settings_client.gui.start_spectator && Company::IsValidID(_local_company); if (_game_mode == GM_EDITOR) { BasePersistentStorageArray::SwitchMode(PSM_ENTER_GAMELOOP); RunTileLoop(); @@ -1281,6 +1284,10 @@ void StateGameLoop() } assert(IsLocalCompany()); + if (valid_local_company && !Company::IsValidID(_local_company)) { + /* _local_company no longer exists due to bankruptcy. */ + SetLocalCompany(COMPANY_SPECTATOR); + } } /** Interval for regular autosaves. Initialized at zero to disable till settings are loaded. */ diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 792f28a846..96a346f1af 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -3377,11 +3377,10 @@ bool AfterLoadGame() * starting a new company. */ StartScripts(); - /* If Load Scenario / New (Scenario) Game is used, - * a company does not exist yet. So create one here. - * 1 exception: network-games. Those can have 0 companies - * But this exception is not true for non-dedicated network servers! */ - if (!_networking || (_networking && _network_server && !_network_dedicated)) { + /* If Load Scenario / New (Scenario) Game is used, a company does not exist yet. + * Create one here whenever start_spectator is disabled. + * 1 exception: dedicated network servers. Those can have 0 companies. */ + if (!_settings_client.gui.start_spectator && !_networking || (_networking && _network_server && !_network_dedicated)) { CompanyID first_human_company = GetFirstPlayableCompanyID(); if (!Company::IsValidID(first_human_company)) { Company *c = DoStartupNewCompany(false, first_human_company); diff --git a/src/script/script_gui.cpp b/src/script/script_gui.cpp index 08e7909720..382c1c095d 100644 --- a/src/script/script_gui.cpp +++ b/src/script/script_gui.cpp @@ -1139,11 +1139,9 @@ struct ScriptDebugWindow : public Window { this->SetWidgetDisabledState(WID_SCRD_SETTINGS, this->filter.script_debug_company == CompanyID::Invalid() || GetConfig(this->filter.script_debug_company)->GetConfigList()->empty()); - extern CompanyID _local_company; this->SetWidgetDisabledState(WID_SCRD_RELOAD_TOGGLE, this->filter.script_debug_company == CompanyID::Invalid() || - this->filter.script_debug_company == OWNER_DEITY || - this->filter.script_debug_company == _local_company); + this->filter.script_debug_company == OWNER_DEITY); this->SetWidgetDisabledState(WID_SCRD_CONTINUE_BTN, this->filter.script_debug_company == CompanyID::Invalid() || (this->filter.script_debug_company == OWNER_DEITY ? !Game::IsPaused() : !AI::IsPaused(this->filter.script_debug_company))); } diff --git a/src/settings_type.h b/src/settings_type.h index e9b589586b..49c2599882 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -230,6 +230,7 @@ struct GUISettings { bool show_date_in_logs; ///< whether to show dates in console logs bool newgrf_developer_tools; ///< activate NewGRF developer tools and allow modifying NewGRFs in an existing game bool ai_developer_tools; ///< activate AI/GS developer tools + bool start_spectator; ///< activate a spectator slot in single player and initiate games as a spectator bool scenario_developer; ///< activate scenario developer: allow modifying NewGRFs in an existing game uint8_t settings_restriction_mode; ///< selected restriction mode in adv. settings GUI. @see RestrictionMode bool newgrf_show_old_versions; ///< whether to show old versions in the NewGRF list diff --git a/src/table/settings/gui_settings.ini b/src/table/settings/gui_settings.ini index 39c44675aa..145beee5eb 100644 --- a/src/table/settings/gui_settings.ini +++ b/src/table/settings/gui_settings.ini @@ -816,6 +816,12 @@ def = false post_cb = [](auto) { InvalidateWindowClassesData(WC_GAME_OPTIONS); InvalidateWindowClassesData(WC_SCRIPT_DEBUG); InvalidateWindowClassesData(WC_SCRIPT_SETTINGS); } cat = SC_EXPERT +[SDTC_BOOL] +var = gui.start_spectator +flags = SettingFlag::NotInSave, SettingFlag::NoNetworkSync +def = false +post_cb = [](auto) { InvalidateWindowClassesData(WC_COMPANY); } + [SDTC_BOOL] var = gui.scenario_developer flags = SettingFlag::NotInSave, SettingFlag::NoNetworkSync diff --git a/src/toolbar_gui.cpp b/src/toolbar_gui.cpp index 69ed37dba7..51e58c242a 100644 --- a/src/toolbar_gui.cpp +++ b/src/toolbar_gui.cpp @@ -141,6 +141,7 @@ static void PopupMainToolbarMenu(Window *w, WidgetID widget, const std::initiali static const int CTMN_CLIENT_LIST = -1; ///< Show the client list static const int CTMN_SPECTATE = -2; ///< Become spectator static const int CTMN_SPECTATOR = -3; ///< Show a company window as spectator +static const int CTMN_NEW_COMPANY = -4; ///< Create a new company /** * Pop up a generic company list menu. @@ -154,8 +155,19 @@ static void PopupMainCompanyToolbMenu(Window *w, WidgetID widget, CompanyMask gr switch (widget) { case WID_TN_COMPANIES: - if (!_networking) break; - + if (!_networking) { + if (_local_company == COMPANY_SPECTATOR) { + bool human = false; + for (CompanyID c = CompanyID::Begin(); c < MAX_COMPANIES; ++c) { + if (Company::IsValidHumanID(c)) { + human = true; + break; + } + } + if (!human) list.push_back(MakeDropDownListStringItem(STR_COMPANY_LIST_NEW_COMPANY, CTMN_NEW_COMPANY, Company::GetNumItems() >= MAX_COMPANIES)); + } + break; + } /* Add the client list button for the companies menu */ list.push_back(MakeDropDownListStringItem(STR_NETWORK_COMPANY_LIST_CLIENT_LIST, CTMN_CLIENT_LIST)); @@ -594,6 +606,17 @@ static CallBackFunction MenuClickCompany(int index) return CBF_NONE; } } + + if (!_networking && _local_company == COMPANY_SPECTATOR) { + if (index == CTMN_NEW_COMPANY) { + extern Company *DoStartupNewCompany(bool is_ai, CompanyID company = CompanyID::Invalid()); + Company *c = DoStartupNewCompany(false); + c->settings = _settings_client.company; + SetLocalCompany(c->index); + return CBF_NONE; + } + } + ShowCompany((CompanyID)index); return CBF_NONE; }