From 3b1407d240853e13fea13ed4e7a35821675353c2 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Mon, 5 Jun 2023 19:32:22 +0200 Subject: [PATCH] Feature: allow to do a hostile takeover of an AI company (in singleplayer) (#10914) With the removal of the share-system, you could no longer make an AI disappear in a single player game. At least, not without going into the console. --- src/company_base.h | 1 + src/company_cmd.cpp | 2 +- src/company_func.h | 2 +- src/company_gui.cpp | 61 ++++++++++++++--- src/economy.cpp | 94 ++++++++++++++++++++------- src/economy_cmd.h | 2 +- src/lang/english.txt | 5 +- src/script/api/script_event_types.cpp | 2 +- src/widgets/company_widget.h | 3 + 9 files changed, 136 insertions(+), 36 deletions(-) diff --git a/src/company_base.h b/src/company_base.h index 88bd49c7c5..5735cda990 100644 --- a/src/company_base.h +++ b/src/company_base.h @@ -165,6 +165,7 @@ struct Company : CompanyProperties, CompanyPool::PoolItem<&_company_pool> { }; Money CalculateCompanyValue(const Company *c, bool including_loan = true); +Money CalculateHostileTakeoverValue(const Company *c); extern uint _cur_company_tick_index; diff --git a/src/company_cmd.cpp b/src/company_cmd.cpp index 79bd6153e4..5d186d831b 100644 --- a/src/company_cmd.cpp +++ b/src/company_cmd.cpp @@ -709,7 +709,7 @@ static void HandleBankruptcyTakeover(Company *c) AI::NewEvent(best->index, new ScriptEventCompanyAskMerger(c->index, c->bankrupt_value)); if (IsInteractiveCompany(best->index)) { - ShowBuyCompanyDialog(c->index); + ShowBuyCompanyDialog(c->index, false); } } diff --git a/src/company_func.h b/src/company_func.h index 5c58ee8da4..1212c4aa6a 100644 --- a/src/company_func.h +++ b/src/company_func.h @@ -19,7 +19,7 @@ bool MayCompanyTakeOver(CompanyID cbig, CompanyID small); void ChangeOwnershipOfCompanyItems(Owner old_owner, Owner new_owner); void GetNameOfOwner(Owner owner, TileIndex tile); void SetLocalCompany(CompanyID new_company); -void ShowBuyCompanyDialog(CompanyID company); +void ShowBuyCompanyDialog(CompanyID company, bool hostile_takeover); void CompanyAdminUpdate(const Company *company); void CompanyAdminBankrupt(CompanyID company_id); void UpdateLandscapingLimits(); diff --git a/src/company_gui.cpp b/src/company_gui.cpp index ce9025af9f..45b5aa8d15 100644 --- a/src/company_gui.cpp +++ b/src/company_gui.cpp @@ -2228,6 +2228,12 @@ static const NWidgetPart _nested_company_widgets[] = { /* Multi player buttons. */ NWidget(NWID_VERTICAL), SetPIP(4, 2, 4), NWidget(NWID_SPACER), SetFill(0, 1), + NWidget(NWID_HORIZONTAL), SetPIP(0, 4, 0), + NWidget(NWID_SPACER), SetFill(1, 0), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_C_SELECT_HOSTILE_TAKEOVER), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_C_HOSTILE_TAKEOVER), SetDataTip(STR_COMPANY_VIEW_HOSTILE_TAKEOVER_BUTTON, STR_COMPANY_VIEW_HOSTILE_TAKEOVER_TOOLTIP), + EndContainer(), + EndContainer(), NWidget(NWID_HORIZONTAL), SetPIP(0, 4, 0), NWidget(NWID_SPACER), SetFill(1, 0), NWidget(NWID_SELECTION, INVALID_COLOUR, WID_C_SELECT_GIVE_MONEY), @@ -2332,6 +2338,13 @@ struct CompanyWindow : Window wi->SetDisplayedPlane(plane); reinit = true; } + /* Enable/disable 'Hostile Takeover' button. */ + plane = ((local || _local_company == COMPANY_SPECTATOR || !c->is_ai || _networking) ? SZSP_NONE : 0); + wi = this->GetWidget(WID_C_SELECT_HOSTILE_TAKEOVER); + if (plane != wi->shown_plane) { + wi->SetDisplayedPlane(plane); + reinit = true; + } /* Multiplayer buttons. */ plane = ((!_networking) ? (int)SZSP_NONE : (int)(local ? CWP_MP_C_PWD : CWP_MP_C_JOIN)); @@ -2398,6 +2411,7 @@ struct CompanyWindow : Window case WID_C_RELOCATE_HQ: case WID_C_VIEW_INFRASTRUCTURE: case WID_C_GIVE_MONEY: + case WID_C_HOSTILE_TAKEOVER: case WID_C_COMPANY_PASSWORD: case WID_C_COMPANY_JOIN: size->width = GetStringBoundingBox(STR_COMPANY_VIEW_VIEW_HQ_BUTTON).width; @@ -2405,6 +2419,7 @@ struct CompanyWindow : Window size->width = std::max(size->width, GetStringBoundingBox(STR_COMPANY_VIEW_RELOCATE_HQ).width); size->width = std::max(size->width, GetStringBoundingBox(STR_COMPANY_VIEW_INFRASTRUCTURE_BUTTON).width); size->width = std::max(size->width, GetStringBoundingBox(STR_COMPANY_VIEW_GIVE_MONEY_BUTTON).width); + size->width = std::max(size->width, GetStringBoundingBox(STR_COMPANY_VIEW_HOSTILE_TAKEOVER_BUTTON).width); size->width = std::max(size->width, GetStringBoundingBox(STR_COMPANY_VIEW_PASSWORD).width); size->width = std::max(size->width, GetStringBoundingBox(STR_COMPANY_VIEW_JOIN).width); size->width += padding.width; @@ -2600,6 +2615,10 @@ struct CompanyWindow : Window ShowQueryString(STR_EMPTY, STR_COMPANY_VIEW_GIVE_MONEY_QUERY_CAPTION, 30, this, CS_NUMERAL, QSF_NONE); break; + case WID_C_HOSTILE_TAKEOVER: + ShowBuyCompanyDialog((CompanyID)this->window_number, true); + break; + case WID_C_COMPANY_PASSWORD: if (this->window_number == _local_company) ShowNetworkCompanyPasswordWindow(this); break; @@ -2697,9 +2716,12 @@ void DirtyCompanyInfrastructureWindows(CompanyID company) } struct BuyCompanyWindow : Window { - BuyCompanyWindow(WindowDesc *desc, WindowNumber window_number) : Window(desc) + BuyCompanyWindow(WindowDesc *desc, WindowNumber window_number, bool hostile_takeover) : Window(desc), hostile_takeover(hostile_takeover) { this->InitNested(window_number); + + const Company *c = Company::Get((CompanyID)this->window_number); + this->company_value = hostile_takeover ? CalculateHostileTakeoverValue(c) : c->bankrupt_value; } void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override @@ -2712,8 +2734,8 @@ struct BuyCompanyWindow : Window { case WID_BC_QUESTION: const Company *c = Company::Get((CompanyID)this->window_number); SetDParam(0, c->index); - SetDParam(1, c->bankrupt_value); - size->height = GetStringHeight(STR_BUY_COMPANY_MESSAGE, size->width); + SetDParam(1, this->company_value); + size->height = GetStringHeight(this->hostile_takeover ? STR_BUY_COMPANY_HOSTILE_TAKEOVER : STR_BUY_COMPANY_MESSAGE, size->width); break; } } @@ -2740,8 +2762,8 @@ struct BuyCompanyWindow : Window { case WID_BC_QUESTION: { const Company *c = Company::Get((CompanyID)this->window_number); SetDParam(0, c->index); - SetDParam(1, c->bankrupt_value); - DrawStringMultiLine(r.left, r.right, r.top, r.bottom, STR_BUY_COMPANY_MESSAGE, TC_FROMSTRING, SA_CENTER); + SetDParam(1, this->company_value); + DrawStringMultiLine(r.left, r.right, r.top, r.bottom, this->hostile_takeover ? STR_BUY_COMPANY_HOSTILE_TAKEOVER : STR_BUY_COMPANY_MESSAGE, TC_FROMSTRING, SA_CENTER); break; } } @@ -2755,10 +2777,29 @@ struct BuyCompanyWindow : Window { break; case WID_BC_YES: - Command::Post(STR_ERROR_CAN_T_BUY_COMPANY, (CompanyID)this->window_number); + Command::Post(STR_ERROR_CAN_T_BUY_COMPANY, (CompanyID)this->window_number, this->hostile_takeover); break; } } + + /** + * Check on a regular interval if the company value has changed. + */ + IntervalTimer rescale_interval = {std::chrono::seconds(3), [this](auto) { + /* Value can't change when in bankruptcy. */ + if (!this->hostile_takeover) return; + + const Company *c = Company::Get((CompanyID)this->window_number); + auto new_value = CalculateHostileTakeoverValue(c); + if (new_value != this->company_value) { + this->company_value = new_value; + this->ReInit(); + } + }}; + +private: + bool hostile_takeover; ///< Whether the window is showing a hostile takeover. + Money company_value; ///< The value of the company for which the user can buy it. }; static const NWidgetPart _nested_buy_company_widgets[] = { @@ -2790,8 +2831,12 @@ static WindowDesc _buy_company_desc( /** * Show the query to buy another company. * @param company The company to buy. + * @param hostile_takeover Whether this is a hostile takeover. */ -void ShowBuyCompanyDialog(CompanyID company) +void ShowBuyCompanyDialog(CompanyID company, bool hostile_takeover) { - AllocateWindowDescFront(&_buy_company_desc, company); + auto window = BringWindowToFrontById(WC_BUY_COMPANY, company); + if (window == nullptr) { + new BuyCompanyWindow(&_buy_company_desc, company, hostile_takeover); + } } diff --git a/src/economy.cpp b/src/economy.cpp index defef7ec80..0e93f9e3ba 100644 --- a/src/economy.cpp +++ b/src/economy.cpp @@ -105,15 +105,12 @@ Prices _price; static PriceMultipliers _price_base_multiplier; /** - * Calculate the value of the company. That is the value of all - * assets (vehicles, stations) and money minus the loan, - * except when including_loan is \c false which is useful when - * we want to calculate the value for bankruptcy. - * @param c the company to get the value of. - * @param including_loan include the loan in the company value. - * @return the value of the company. + * Calculate the value of the assets of a company. + * + * @param c The company to calculate the value of. + * @return The value of the assets of the company. */ -Money CalculateCompanyValue(const Company *c, bool including_loan) +static Money CalculateCompanyAssetValue(const Company *c) { Owner owner = c->index; @@ -136,6 +133,22 @@ Money CalculateCompanyValue(const Company *c, bool including_loan) } } + return value; +} + +/** + * Calculate the value of the company. That is the value of all + * assets (vehicles, stations) and money (including loan), + * except when including_loan is \c false which is useful when + * we want to calculate the value for bankruptcy. + * @param c the company to get the value of. + * @param including_loan include the loan in the company value. + * @return the value of the company. + */ +Money CalculateCompanyValue(const Company *c, bool including_loan) +{ + Money value = CalculateCompanyAssetValue(c); + /* Add real money value */ if (including_loan) value -= c->current_loan; value += c->money; @@ -143,6 +156,39 @@ Money CalculateCompanyValue(const Company *c, bool including_loan) return std::max(value, 1); } +/** + * Calculate what you have to pay to take over a company. + * + * This is different from bankruptcy and company value, and involves a few + * more parameters to make it more realistic. + * + * You have to pay for: + * - The value of all the assets in the company. + * - The loan the company has (the investors really want their money back). + * - The profit for the next two years (if positive) based on the last four quarters. + * + * And on top of that, they walk away with all the money they have in the bank. + * + * @param c the company to get the value of. + * @return The value of the company. + */ +Money CalculateHostileTakeoverValue(const Company *c) +{ + Money value = CalculateCompanyAssetValue(c); + + value += c->current_loan; + /* Negative balance is basically a loan. */ + if (c->money < 0) { + value += -c->money; + } + + for (int quarter = 0; quarter < 4; quarter++) { + value += std::max(c->old_economy[quarter].income - c->old_economy[quarter].expenses, 0) * 2; + } + + return std::max(value, 1); +} + /** * if update is set to true, the economy is updated with this score * (also the house is updated, should only be true in the on-tick event) @@ -1940,14 +1986,14 @@ static IntervalTimer _companies_monthly({TimerGameCalendar::M HandleEconomyFluctuations(); }); -static void DoAcquireCompany(Company *c) +static void DoAcquireCompany(Company *c, bool hostile_takeover) { CompanyID ci = c->index; CompanyNewsInformation *cni = new CompanyNewsInformation(c, Company::Get(_current_company)); SetDParam(0, STR_NEWS_COMPANY_MERGER_TITLE); - SetDParam(1, c->bankrupt_value == 0 ? STR_NEWS_MERGER_TAKEOVER_TITLE : STR_NEWS_COMPANY_MERGER_DESCRIPTION); + SetDParam(1, hostile_takeover ? STR_NEWS_MERGER_TAKEOVER_TITLE : STR_NEWS_COMPANY_MERGER_DESCRIPTION); SetDParamStr(2, cni->company_name); SetDParamStr(3, cni->other_company_name); SetDParam(4, c->bankrupt_value); @@ -1957,14 +2003,6 @@ static void DoAcquireCompany(Company *c) ChangeOwnershipOfCompanyItems(ci, _current_company); - if (c->bankrupt_value == 0) { - Company *owner = Company::Get(_current_company); - - /* Get both the balance and the loan of the company you just bought. */ - SubtractMoneyFromCompany(CommandCost(EXPENSES_OTHER, -c->money)); - owner->current_loan += c->current_loan; - } - if (c->is_ai) AI::Stop(c->index); CloseCompanyWindows(ci); @@ -1983,15 +2021,23 @@ static void DoAcquireCompany(Company *c) * @todo currently this only works for AI companies * @param flags type of operation * @param target_company company to buy up + * @param hostile_takeover whether to buy up the company even if it is not bankrupt * @return the cost of this operation or an error */ -CommandCost CmdBuyCompany(DoCommandFlag flags, CompanyID target_company) +CommandCost CmdBuyCompany(DoCommandFlag flags, CompanyID target_company, bool hostile_takeover) { Company *c = Company::GetIfValid(target_company); if (c == nullptr) return CMD_ERROR; + /* If you do a hostile takeover but the company went bankrupt, buy it via bankruptcy rules. */ + if (hostile_takeover && HasBit(c->bankrupt_asked, _current_company)) hostile_takeover = false; + /* Disable takeovers when not asked */ - if (!HasBit(c->bankrupt_asked, _current_company)) return CMD_ERROR; + if (!hostile_takeover && !HasBit(c->bankrupt_asked, _current_company)) return CMD_ERROR; + + /* Only allow hostile takeover of AI companies and when in single player */ + if (hostile_takeover && !c->is_ai) return CMD_ERROR; + if (hostile_takeover && _networking) return CMD_ERROR; /* Disable taking over the local company in singleplayer mode */ if (!_networking && _local_company == c->index) return CMD_ERROR; @@ -2002,11 +2048,13 @@ CommandCost CmdBuyCompany(DoCommandFlag flags, CompanyID target_company) /* Disable taking over when not allowed. */ if (!MayCompanyTakeOver(_current_company, target_company)) return CMD_ERROR; - /* Get the cost here as the company is deleted in DoAcquireCompany. */ - CommandCost cost(EXPENSES_OTHER, c->bankrupt_value); + /* Get the cost here as the company is deleted in DoAcquireCompany. + * For bankruptcy this amount is calculated when the offer was made; + * for hostile takeover you pay the current price. */ + CommandCost cost(EXPENSES_OTHER, hostile_takeover ? CalculateHostileTakeoverValue(c) : c->bankrupt_value); if (flags & DC_EXEC) { - DoAcquireCompany(c); + DoAcquireCompany(c, hostile_takeover); } return cost; } diff --git a/src/economy_cmd.h b/src/economy_cmd.h index 269c011475..9a770c9785 100644 --- a/src/economy_cmd.h +++ b/src/economy_cmd.h @@ -13,7 +13,7 @@ #include "command_type.h" #include "company_type.h" -CommandCost CmdBuyCompany(DoCommandFlag flags, CompanyID target_company); +CommandCost CmdBuyCompany(DoCommandFlag flags, CompanyID target_company, bool hostile_takeover); DEF_CMD_TRAIT(CMD_BUY_COMPANY, CmdBuyCompany, 0, CMDT_MONEY_MANAGEMENT) diff --git a/src/lang/english.txt b/src/lang/english.txt index 36248f1edd..109360586e 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -846,7 +846,7 @@ STR_NEWS_COMPANY_BANKRUPT_TITLE :{BIG_FONT}{BLAC STR_NEWS_COMPANY_BANKRUPT_DESCRIPTION :{BIG_FONT}{BLACK}{RAW_STRING} has been closed down by creditors and all assets sold off! STR_NEWS_COMPANY_LAUNCH_TITLE :{BIG_FONT}{BLACK}New transport company launched! STR_NEWS_COMPANY_LAUNCH_DESCRIPTION :{BIG_FONT}{BLACK}{RAW_STRING} starts construction near {TOWN}! -STR_NEWS_MERGER_TAKEOVER_TITLE :{BIG_FONT}{BLACK}{RAW_STRING} has been taken over by {RAW_STRING}! +STR_NEWS_MERGER_TAKEOVER_TITLE :{BIG_FONT}{BLACK}{RAW_STRING} has been taken over by {RAW_STRING} for an undisclosed amount! STR_PRESIDENT_NAME_MANAGER :{BLACK}{PRESIDENT_NAME}{}(Manager) STR_NEWS_NEW_TOWN :{BLACK}{BIG_FONT}{RAW_STRING} sponsored construction of new town {TOWN}! @@ -3760,6 +3760,8 @@ STR_COMPANY_VIEW_INFRASTRUCTURE_BUTTON :{BLACK}Details STR_COMPANY_VIEW_INFRASTRUCTURE_TOOLTIP :{BLACK}View detailed infrastructure counts STR_COMPANY_VIEW_GIVE_MONEY_BUTTON :{BLACK}Give money STR_COMPANY_VIEW_GIVE_MONEY_TOOLTIP :{BLACK}Give money to this company +STR_COMPANY_VIEW_HOSTILE_TAKEOVER_BUTTON :{BLACK}Hostile takeover +STR_COMPANY_VIEW_HOSTILE_TAKEOVER_TOOLTIP :{BLACK}Do a hostile takeover of this company STR_COMPANY_VIEW_NEW_FACE_BUTTON :{BLACK}New Face STR_COMPANY_VIEW_NEW_FACE_TOOLTIP :{BLACK}Select new face for manager @@ -3775,6 +3777,7 @@ STR_COMPANY_VIEW_PRESIDENT_S_NAME_QUERY_CAPTION :Manager's Name STR_COMPANY_VIEW_GIVE_MONEY_QUERY_CAPTION :Enter the amount of money you want to give STR_BUY_COMPANY_MESSAGE :{WHITE}We are looking for a transport company to take-over our company.{}{}Do you want to purchase {COMPANY} for {CURRENCY_LONG}? +STR_BUY_COMPANY_HOSTILE_TAKEOVER :{WHITE}In a hostile takeover of {COMPANY} you will purchase all assets, pay off all loans, and pay two years worth of profits.{}{}The total is estimated to be {CURRENCY_LONG}.{}{}Do you want to continue this hostile takeover? # Company infrastructure window STR_COMPANY_INFRASTRUCTURE_VIEW_CAPTION :{WHITE}Infrastructure of {COMPANY} diff --git a/src/script/api/script_event_types.cpp b/src/script/api/script_event_types.cpp index e90b85f0a9..de94ce4a56 100644 --- a/src/script/api/script_event_types.cpp +++ b/src/script/api/script_event_types.cpp @@ -119,7 +119,7 @@ bool ScriptEventEnginePreview::AcceptPreview() bool ScriptEventCompanyAskMerger::AcceptMerger() { EnforceCompanyModeValid(false); - return ScriptObject::Command::Do((::CompanyID)this->owner); + return ScriptObject::Command::Do((::CompanyID)this->owner, false); } ScriptEventAdminPort::ScriptEventAdminPort(const std::string &json) : diff --git a/src/widgets/company_widget.h b/src/widgets/company_widget.h index c82b48b3d0..a2e3cb685f 100644 --- a/src/widgets/company_widget.h +++ b/src/widgets/company_widget.h @@ -44,6 +44,9 @@ enum CompanyWidgets { WID_C_SELECT_GIVE_MONEY, ///< Selection widget for the give money button. WID_C_GIVE_MONEY, ///< Button to give money. + WID_C_SELECT_HOSTILE_TAKEOVER, ///< Selection widget for the hostile takeover button. + WID_C_HOSTILE_TAKEOVER, ///< Button to hostile takeover another company. + WID_C_HAS_PASSWORD, ///< Has company password lock. WID_C_SELECT_MULTIPLAYER, ///< Multiplayer selection panel. WID_C_COMPANY_PASSWORD, ///< Button to set company password.