diff --git a/src/company_base.h b/src/company_base.h index 2bebc9aba1..440c9ec64d 100644 --- a/src/company_base.h +++ b/src/company_base.h @@ -181,6 +181,7 @@ struct Company : CompanyPool::PoolItem<&_company_pool>, CompanyProperties { Money CalculateCompanyValue(const Company *c, bool including_loan = true); Money CalculateCompanyValueExcludingShares(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 680bd5cba4..62568219ab 100644 --- a/src/company_cmd.cpp +++ b/src/company_cmd.cpp @@ -767,7 +767,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); } else if ((!_networking || (_network_server && !NetworkCompanyHasClients(best->index))) && !best->is_ai) { /* This company can never accept the offer as there are no clients connected, decline the offer on the company's behalf */ Backup cur_company(_current_company, best->index, FILE_LINE); diff --git a/src/company_func.h b/src/company_func.h index d8d69efffe..95fa35d312 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 dd474b9044..4c12679aa1 100644 --- a/src/company_gui.cpp +++ b/src/company_gui.cpp @@ -2288,6 +2288,13 @@ static const NWidgetPart _nested_company_widgets[] = { EndContainer(), EndContainer(), NWidget(NWID_SPACER), SetFill(1, 0), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_C_SELECT_HOSTILE_TAKEOVER), + NWidget(NWID_VERTICAL), + NWidget(NWID_SPACER), SetFill(0, 1), SetMinimalSize(90, 0), + 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_SPACER), SetFill(1, 0), NWidget(NWID_SELECTION, INVALID_COLOUR, WID_C_SELECT_GIVE_MONEY), NWidget(NWID_VERTICAL), NWidget(NWID_SPACER), SetFill(0, 1), SetMinimalSize(90, 0), @@ -2432,6 +2439,13 @@ struct CompanyWindow : Window wi->SetDisplayedPlane(plane); reinit = true; } + /* Enable/disable 'Hostile Takeover' button. */ + plane = ((local || _local_company == COMPANY_SPECTATOR || !c->is_ai || _networking || _settings_game.economy.allow_shares) ? 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)); @@ -2507,12 +2521,16 @@ struct CompanyWindow : Window case WID_C_BUILD_HQ: 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; size->width = std::max(size->width, GetStringBoundingBox(STR_COMPANY_VIEW_BUILD_HQ_BUTTON).width); 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; @@ -2733,6 +2751,10 @@ struct CompanyWindow : Window DoCommandP(0, this->window_number, 0, CMD_SELL_SHARE_IN_COMPANY | CMD_MSG(STR_ERROR_CAN_T_SELL_25_SHARE_IN)); 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; @@ -2868,16 +2890,18 @@ void DirtyAllCompanyInfrastructureWindows() } 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); this->owner = _local_company; + const Company *c = Company::Get((CompanyID)this->window_number); + this->company_value = hostile_takeover ? CalculateHostileTakeoverValue(c) : c->bankrupt_value; } ~BuyCompanyWindow() { const Company *c = Company::GetIfValid((CompanyID)this->window_number); - if (c != nullptr && HasBit(c->bankrupt_asked, this->owner) && _current_company == this->owner) { + if (!this->hostile_takeover && c != nullptr && HasBit(c->bankrupt_asked, this->owner) && _current_company == this->owner) { EnqueueDoCommandP(NewCommandContainerBasic(0, this->window_number, 0, CMD_DECLINE_BUY_COMPANY | CMD_NO_SHIFT_ESTIMATE)); } } @@ -2892,8 +2916,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; } } @@ -2920,8 +2944,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; } } @@ -2935,10 +2959,30 @@ struct BuyCompanyWindow : Window { break; case WID_BC_YES: - DoCommandP(0, this->window_number, 0, CMD_BUY_COMPANY | CMD_MSG(STR_ERROR_CAN_T_BUY_COMPANY)); + DoCommandP(0, this->window_number, (this->hostile_takeover ? 1 : 0), CMD_BUY_COMPANY | CMD_MSG(STR_ERROR_CAN_T_BUY_COMPANY)); break; } } + + /** + * Check on a regular interval if the company value has changed. + */ + void OnHundredthTick() override + { + /* 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[] = { @@ -2970,8 +3014,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 f656148c19..351a5e6d2c 100644 --- a/src/economy.cpp +++ b/src/economy.cpp @@ -112,15 +112,12 @@ static PriceMultipliers _price_base_multiplier; extern int GetAmountOwnedBy(const Company *c, Owner owner); /** - * Calculate the value of the company. That is the value of all - * assets (vehicles, stations, shares) 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) { Money owned_shares_value = 0; @@ -157,6 +154,22 @@ Money CalculateCompanyValueExcludingShares(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; @@ -164,6 +177,39 @@ Money CalculateCompanyValueExcludingShares(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) @@ -2405,7 +2451,7 @@ void CompaniesMonthlyLoop() HandleEconomyFluctuations(); } -static void DoAcquireCompany(Company *c) +static void DoAcquireCompany(Company *c, bool hostile_takeover) { CompanyID ci = c->index; @@ -2414,7 +2460,7 @@ static void DoAcquireCompany(Company *c) 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); @@ -2424,14 +2470,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); c->bankrupt_asked = 0; @@ -2490,7 +2528,7 @@ CommandCost CmdBuyShareInCompany(TileIndex tile, DoCommandFlag flags, uint32 p1, auto current_company_owns_share = [](auto share_owner) { return share_owner == _current_company; }; if (std::all_of(c->share_owners.begin(), c->share_owners.end(), current_company_owns_share)) { c->bankrupt_value = 0; - DoAcquireCompany(c); + DoAcquireCompany(c, false); } InvalidateWindowData(WC_COMPANY, target_company); CompanyAdminUpdate(c); @@ -2544,7 +2582,8 @@ CommandCost CmdSellShareInCompany(TileIndex tile, DoCommandFlag flags, uint32 p1 * @param tile unused * @param flags type of operation * @param p1 company to buy up - * @param p2 unused + * @param p2 various bitstuffed elements + * - p2 = (bit 0) - hostile_takeover whether to buy up the company even if it is not bankrupt * @param text unused * @return the cost of this operation or an error */ @@ -2554,8 +2593,18 @@ CommandCost CmdBuyCompany(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 Company *c = Company::GetIfValid(target_company); if (c == nullptr) return CMD_ERROR; + bool hostile_takeover = HasBit(p2, 0); + if (hostile_takeover && _settings_game.economy.allow_shares) 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; @@ -2566,11 +2615,13 @@ CommandCost CmdBuyCompany(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 /* 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/widgets/company_widget.h b/src/widgets/company_widget.h index 4d05d4be07..c427d2dc5c 100644 --- a/src/widgets/company_widget.h +++ b/src/widgets/company_widget.h @@ -49,6 +49,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.