1 /*****************************************************************************
2 * Copyright (c) 2014-2020 OpenRCT2 developers
3 *
4 * For a complete list of all authors, please refer to contributors.md
5 * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6 *
7 * OpenRCT2 is licensed under the GNU General Public License version 3.
8 *****************************************************************************/
9
10 #include "Finance.h"
11
12 #include "../Context.h"
13 #include "../Game.h"
14 #include "../OpenRCT2.h"
15 #include "../interface/Window.h"
16 #include "../localisation/Date.h"
17 #include "../localisation/Localisation.h"
18 #include "../peep/Peep.h"
19 #include "../peep/Staff.h"
20 #include "../ride/Ride.h"
21 #include "../scenario/Scenario.h"
22 #include "../util/Util.h"
23 #include "../windows/Intent.h"
24 #include "../world/Park.h"
25
26 // Monthly research funding costs
27 const money32 research_cost_table[RESEARCH_FUNDING_COUNT] = {
28 MONEY(0, 00), // No funding
29 MONEY(100, 00), // Minimum funding
30 MONEY(200, 00), // Normal funding
31 MONEY(400, 00), // Maximum funding
32 };
33
34 static constexpr const int32_t dword_988E60[static_cast<int32_t>(ExpenditureType::Count)] = {
35 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0,
36 };
37
38 money64 gInitialCash;
39 money64 gCash;
40 money64 gBankLoan;
41 uint8_t gBankLoanInterestRate;
42 money64 gMaxBankLoan;
43 money64 gCurrentExpenditure;
44 money64 gCurrentProfit;
45 money64 gHistoricalProfit;
46 money64 gWeeklyProfitAverageDividend;
47 uint16_t gWeeklyProfitAverageDivisor;
48 money64 gCashHistory[FINANCE_GRAPH_SIZE];
49 money64 gWeeklyProfitHistory[FINANCE_GRAPH_SIZE];
50 money64 gParkValueHistory[FINANCE_GRAPH_SIZE];
51 money64 gExpenditureTable[EXPENDITURE_TABLE_MONTH_COUNT][static_cast<int32_t>(ExpenditureType::Count)];
52
53 /**
54 * Checks the condition if the game is required to use money.
55 * @param flags game command flags.
56 */
finance_check_money_required(uint32_t flags)57 bool finance_check_money_required(uint32_t flags)
58 {
59 if (gParkFlags & PARK_FLAGS_NO_MONEY)
60 return false;
61 if (gScreenFlags & SCREEN_FLAGS_EDITOR)
62 return false;
63 if (flags & GAME_COMMAND_FLAG_NO_SPEND)
64 return false;
65 if (flags & GAME_COMMAND_FLAG_GHOST)
66 return false;
67 return true;
68 }
69
70 /**
71 * Checks if enough money is available.
72 * @param cost.
73 * @param flags game command flags.
74 */
finance_check_affordability(money32 cost,uint32_t flags)75 bool finance_check_affordability(money32 cost, uint32_t flags)
76 {
77 return !finance_check_money_required(flags) || cost <= 0 || cost <= gCash;
78 }
79
80 /**
81 * Pay an amount of money.
82 * rct2: 0x069C674
83 * @param amount (eax)
84 * @param type passed via global var 0x0141F56C (RCT2_ADDRESS_NEXT_EXPENDITURE_TYPE), our type is that var/4.
85 */
finance_payment(money32 amount,ExpenditureType type)86 void finance_payment(money32 amount, ExpenditureType type)
87 {
88 // overflow check
89 gCash = add_clamp_money32(gCash, -amount);
90
91 gExpenditureTable[0][static_cast<int32_t>(type)] -= amount;
92 if (dword_988E60[static_cast<int32_t>(type)] & 1)
93 {
94 // Cumulative amount of money spent this day
95 gCurrentExpenditure -= amount;
96 }
97
98 auto intent = Intent(INTENT_ACTION_UPDATE_CASH);
99 context_broadcast_intent(&intent);
100 }
101
102 /**
103 * Pays the wages of all active staff members in the park.
104 * rct2: 0x006C18A9
105 */
finance_pay_wages()106 void finance_pay_wages()
107 {
108 if (gParkFlags & PARK_FLAGS_NO_MONEY)
109 {
110 return;
111 }
112
113 for (auto peep : EntityList<Staff>())
114 {
115 finance_payment(GetStaffWage(peep->AssignedStaffType) / 4, ExpenditureType::Wages);
116 }
117 }
118
119 /**
120 * Pays the current research level's cost.
121 * rct2: 0x00684DA5
122 **/
finance_pay_research()123 void finance_pay_research()
124 {
125 if (gParkFlags & PARK_FLAGS_NO_MONEY)
126 {
127 return;
128 }
129
130 const uint8_t level = gResearchFundingLevel;
131 finance_payment(research_cost_table[level] / 4, ExpenditureType::Research);
132 }
133
134 /**
135 * Pay interest on current loans.
136 * rct2: 0x0069E092
137 */
finance_pay_interest()138 void finance_pay_interest()
139 {
140 if (gParkFlags & PARK_FLAGS_NO_MONEY)
141 {
142 return;
143 }
144
145 // This variable uses the 64-bit type as the computation below can involve multiplying very large numbers
146 // that will overflow money32 if the loan is greater than (1 << 31) / (5 * current_interest_rate)
147 const money64 current_loan = gBankLoan;
148 const uint8_t current_interest_rate = gBankLoanInterestRate;
149 const money32 interest_to_pay = (current_loan * 5 * current_interest_rate) >> 14;
150
151 finance_payment(interest_to_pay, ExpenditureType::Interest);
152 }
153
154 /**
155 *
156 * rct2: 0x006AC885
157 */
finance_pay_ride_upkeep()158 void finance_pay_ride_upkeep()
159 {
160 for (auto& ride : GetRideManager())
161 {
162 if (!(ride.lifecycle_flags & RIDE_LIFECYCLE_EVER_BEEN_OPENED))
163 {
164 ride.Renew();
165 }
166
167 if (ride.status != RideStatus::Closed && !(gParkFlags & PARK_FLAGS_NO_MONEY))
168 {
169 int16_t upkeep = ride.upkeep_cost;
170 if (upkeep != -1)
171 {
172 ride.total_profit -= upkeep;
173 ride.window_invalidate_flags |= RIDE_INVALIDATE_RIDE_INCOME;
174 finance_payment(upkeep, ExpenditureType::RideRunningCosts);
175 }
176 }
177
178 if (ride.last_crash_type != RIDE_CRASH_TYPE_NONE)
179 {
180 ride.last_crash_type--;
181 }
182 }
183 }
184
finance_reset_history()185 void finance_reset_history()
186 {
187 for (int32_t i = 0; i < FINANCE_GRAPH_SIZE; i++)
188 {
189 gCashHistory[i] = MONEY64_UNDEFINED;
190 gWeeklyProfitHistory[i] = MONEY64_UNDEFINED;
191 gParkValueHistory[i] = MONEY64_UNDEFINED;
192 }
193 }
194
195 /**
196 *
197 * rct2: 0x0069DEFB
198 */
finance_init()199 void finance_init()
200 {
201 // It only initialises the first month
202 for (uint32_t i = 0; i < static_cast<int32_t>(ExpenditureType::Count); i++)
203 {
204 gExpenditureTable[0][i] = 0;
205 }
206
207 gCurrentExpenditure = 0;
208 gCurrentProfit = 0;
209
210 gWeeklyProfitAverageDividend = 0;
211 gWeeklyProfitAverageDivisor = 0;
212
213 gInitialCash = MONEY(10000, 00); // Cheat detection
214
215 gCash = MONEY(10000, 00);
216 gBankLoan = MONEY(10000, 00);
217 gMaxBankLoan = MONEY(20000, 00);
218
219 gHistoricalProfit = 0;
220
221 gBankLoanInterestRate = 10;
222 gParkValue = 0;
223 gCompanyValue = 0;
224 gScenarioCompletedCompanyValue = MONEY64_UNDEFINED;
225 gTotalAdmissions = 0;
226 gTotalIncomeFromAdmissions = 0;
227 gScenarioCompletedBy = "?";
228 }
229
230 /**
231 *
232 * rct2: 0x0069E79A
233 */
finance_update_daily_profit()234 void finance_update_daily_profit()
235 {
236 gCurrentProfit = 7 * gCurrentExpenditure;
237 gCurrentExpenditure = 0; // Reset daily expenditure
238
239 money32 current_profit = 0;
240
241 if (!(gParkFlags & PARK_FLAGS_NO_MONEY))
242 {
243 // Staff costs
244 for (auto peep : EntityList<Staff>())
245 {
246 current_profit -= GetStaffWage(peep->AssignedStaffType);
247 }
248
249 // Research costs
250 uint8_t level = gResearchFundingLevel;
251 current_profit -= research_cost_table[level];
252
253 // Loan costs
254 money32 current_loan = gBankLoan;
255 current_profit -= current_loan / 600;
256
257 // Ride costs
258 for (auto& ride : GetRideManager())
259 {
260 if (ride.status != RideStatus::Closed && ride.upkeep_cost != MONEY16_UNDEFINED)
261 {
262 current_profit -= 2 * ride.upkeep_cost;
263 }
264 }
265 }
266
267 // This is not equivalent to / 4 due to rounding of negative numbers
268 current_profit = current_profit >> 2;
269
270 gCurrentProfit += current_profit;
271
272 // These are related to weekly profit graph
273 gWeeklyProfitAverageDividend += gCurrentProfit;
274 gWeeklyProfitAverageDivisor += 1;
275
276 window_invalidate_by_class(WC_FINANCES);
277 }
278
finance_get_initial_cash()279 money64 finance_get_initial_cash()
280 {
281 return gInitialCash;
282 }
283
finance_get_current_loan()284 money64 finance_get_current_loan()
285 {
286 return gBankLoan;
287 }
288
finance_get_maximum_loan()289 money64 finance_get_maximum_loan()
290 {
291 return gMaxBankLoan;
292 }
293
finance_get_current_cash()294 money64 finance_get_current_cash()
295 {
296 return gCash;
297 }
298
299 /**
300 * Shift the expenditure table history one month to the left
301 * If the table is full, accumulate the sum of the oldest month first
302 * rct2: 0x0069DEAD
303 */
finance_shift_expenditure_table()304 void finance_shift_expenditure_table()
305 {
306 // If EXPENDITURE_TABLE_MONTH_COUNT months have passed then is full, sum the oldest month
307 if (gDateMonthsElapsed >= EXPENDITURE_TABLE_MONTH_COUNT)
308 {
309 money64 sum = 0;
310 for (uint32_t i = 0; i < static_cast<int32_t>(ExpenditureType::Count); i++)
311 {
312 sum += gExpenditureTable[EXPENDITURE_TABLE_MONTH_COUNT - 1][i];
313 }
314 gHistoricalProfit += sum;
315 }
316
317 // Shift the table
318 for (size_t i = EXPENDITURE_TABLE_MONTH_COUNT - 1; i >= 1; i--)
319 {
320 for (size_t j = 0; j < static_cast<int32_t>(ExpenditureType::Count); j++)
321 {
322 gExpenditureTable[i][j] = gExpenditureTable[i - 1][j];
323 }
324 }
325
326 // Zero the beginning of the table, which is the new month
327 for (uint32_t i = 0; i < static_cast<int32_t>(ExpenditureType::Count); i++)
328 {
329 gExpenditureTable[0][i] = 0;
330 }
331
332 window_invalidate_by_class(WC_FINANCES);
333 }
334
335 /**
336 *
337 * rct2: 0x0069E89B
338 */
finance_reset_cash_to_initial()339 void finance_reset_cash_to_initial()
340 {
341 gCash = gInitialCash;
342 }
343
344 /**
345 * Gets the last month's profit from food, drink and merchandise.
346 */
finance_get_last_month_shop_profit()347 money64 finance_get_last_month_shop_profit()
348 {
349 money64 profit = 0;
350 if (gDateMonthsElapsed != 0)
351 {
352 const auto* lastMonthExpenditure = gExpenditureTable[1];
353
354 profit += lastMonthExpenditure[static_cast<int32_t>(ExpenditureType::ShopSales)];
355 profit += lastMonthExpenditure[static_cast<int32_t>(ExpenditureType::ShopStock)];
356 profit += lastMonthExpenditure[static_cast<int32_t>(ExpenditureType::FoodDrinkSales)];
357 profit += lastMonthExpenditure[static_cast<int32_t>(ExpenditureType::FoodDrinkStock)];
358 }
359 return profit;
360 }
361