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 "Scenario.h"
11
12 #include "../Cheats.h"
13 #include "../Context.h"
14 #include "../FileClassifier.h"
15 #include "../Game.h"
16 #include "../GameState.h"
17 #include "../OpenRCT2.h"
18 #include "../ParkImporter.h"
19 #include "../audio/audio.h"
20 #include "../config/Config.h"
21 #include "../core/Guard.hpp"
22 #include "../core/Random.hpp"
23 #include "../interface/Viewport.h"
24 #include "../localisation/Date.h"
25 #include "../localisation/Localisation.h"
26 #include "../management/Award.h"
27 #include "../management/Finance.h"
28 #include "../management/Marketing.h"
29 #include "../management/NewsItem.h"
30 #include "../management/Research.h"
31 #include "../network/network.h"
32 #include "../object/Object.h"
33 #include "../object/ObjectList.h"
34 #include "../peep/Guest.h"
35 #include "../peep/Staff.h"
36 #include "../platform/platform.h"
37 #include "../rct1/RCT1.h"
38 #include "../rct12/RCT12.h"
39 #include "../ride/Ride.h"
40 #include "../ride/Track.h"
41 #include "../util/SawyerCoding.h"
42 #include "../util/Util.h"
43 #include "../windows/Intent.h"
44 #include "../world/Climate.h"
45 #include "../world/Duck.h"
46 #include "../world/Map.h"
47 #include "../world/Park.h"
48 #include "../world/Scenery.h"
49 #include "../world/Water.h"
50 #include "ScenarioRepository.h"
51 #include "ScenarioSources.h"
52
53 #include <algorithm>
54 #include <bitset>
55
56 const rct_string_id ScenarioCategoryStringIds[SCENARIO_CATEGORY_COUNT] = {
57 STR_BEGINNER_PARKS, STR_CHALLENGING_PARKS, STR_EXPERT_PARKS, STR_REAL_PARKS, STR_OTHER_PARKS,
58
59 STR_DLC_PARKS, STR_BUILD_YOUR_OWN_PARKS,
60 };
61
62 SCENARIO_CATEGORY gScenarioCategory;
63 std::string gScenarioName;
64 std::string gScenarioDetails;
65 std::string gScenarioCompletedBy;
66 std::string gScenarioSavePath;
67 char gScenarioExpansionPacks[3256];
68 bool gFirstTimeSaving = true;
69 uint16_t gSavedAge;
70 uint32_t gLastAutoSaveUpdate = 0;
71
72 random_engine_t gScenarioRand;
73
74 Objective gScenarioObjective;
75
76 bool gAllowEarlyCompletionInNetworkPlay;
77 uint16_t gScenarioParkRatingWarningDays;
78 money64 gScenarioCompletedCompanyValue;
79 money64 gScenarioCompanyValueRecord;
80
81 char gScenarioFileName[MAX_PATH];
82
83 static void scenario_objective_check();
84
85 using namespace OpenRCT2;
86
scenario_begin()87 void scenario_begin()
88 {
89 game_load_init();
90
91 // Set the scenario pseudo-random seeds
92 Random::Rct2::Seed s{ 0x1234567F ^ platform_get_ticks(), 0x789FABCD ^ platform_get_ticks() };
93 gScenarioRand.seed(s);
94
95 gParkFlags &= ~PARK_FLAGS_NO_MONEY;
96 if (gParkFlags & PARK_FLAGS_NO_MONEY_SCENARIO)
97 gParkFlags |= PARK_FLAGS_NO_MONEY;
98 research_reset_current_item();
99 scenery_set_default_placement_configuration();
100 News::InitQueue();
101 if (gScenarioObjective.Type != OBJECTIVE_NONE && !gLoadKeepWindowsOpen)
102 context_open_window_view(WV_PARK_OBJECTIVE);
103
104 auto& park = GetContext()->GetGameState()->GetPark();
105 gParkRating = park.CalculateParkRating();
106 gParkValue = park.CalculateParkValue();
107 gCompanyValue = park.CalculateCompanyValue();
108 gHistoricalProfit = gInitialCash - gBankLoan;
109 gCash = gInitialCash;
110
111 {
112 utf8 normalisedName[64];
113 ScenarioSources::NormaliseName(normalisedName, sizeof(normalisedName), gScenarioName.c_str());
114
115 rct_string_id localisedStringIds[3];
116 if (language_get_localised_scenario_strings(normalisedName, localisedStringIds))
117 {
118 if (localisedStringIds[0] != STR_NONE)
119 {
120 gScenarioName = language_get_string(localisedStringIds[0]);
121 }
122 if (localisedStringIds[1] != STR_NONE)
123 {
124 park.Name = language_get_string(localisedStringIds[1]);
125 }
126 if (localisedStringIds[2] != STR_NONE)
127 {
128 gScenarioDetails = language_get_string(localisedStringIds[2]);
129 }
130 }
131 }
132
133 // Set the last saved game path
134 char savePath[MAX_PATH];
135 platform_get_user_directory(savePath, "save", sizeof(savePath));
136 safe_strcat_path(savePath, park.Name.c_str(), sizeof(savePath));
137 path_append_extension(savePath, ".sv6", sizeof(savePath));
138 gScenarioSavePath = savePath;
139
140 gCurrentExpenditure = 0;
141 gCurrentProfit = 0;
142 gWeeklyProfitAverageDividend = 0;
143 gWeeklyProfitAverageDivisor = 0;
144 gScenarioCompletedCompanyValue = MONEY64_UNDEFINED;
145 gTotalAdmissions = 0;
146 gTotalIncomeFromAdmissions = 0;
147 gScenarioCompletedBy = "?";
148 park.ResetHistories();
149 finance_reset_history();
150 award_reset();
151 reset_all_ride_build_dates();
152 date_reset();
153 Duck::RemoveAll();
154 park_calculate_size();
155 map_count_remaining_land_rights();
156 Staff::ResetStats();
157 gLastEntranceStyle = 0;
158 gMarketingCampaigns.clear();
159 gParkRatingCasualtyPenalty = 0;
160
161 // Open park with free entry when there is no money
162 if (gParkFlags & PARK_FLAGS_NO_MONEY)
163 {
164 gParkFlags |= PARK_FLAGS_PARK_OPEN;
165 gParkEntranceFee = 0;
166 }
167
168 gParkFlags |= PARK_FLAGS_SPRITES_INITIALISED;
169
170 gScreenAge = 0;
171 }
172
scenario_end()173 static void scenario_end()
174 {
175 game_reset_speed();
176 window_close_by_class(WC_DROPDOWN);
177 window_close_all_except_flags(WF_STICK_TO_BACK | WF_STICK_TO_FRONT);
178 context_open_window_view(WV_PARK_OBJECTIVE);
179 }
180
181 /**
182 *
183 * rct2: 0x0066A752
184 */
scenario_failure()185 void scenario_failure()
186 {
187 gScenarioCompletedCompanyValue = COMPANY_VALUE_ON_FAILED_OBJECTIVE;
188 scenario_end();
189 }
190
191 /**
192 *
193 * rct2: 0x0066A75E
194 */
scenario_success()195 void scenario_success()
196 {
197 auto companyValue = gCompanyValue;
198
199 gScenarioCompletedCompanyValue = companyValue;
200 peep_applause();
201
202 if (scenario_repository_try_record_highscore(gScenarioFileName, companyValue, nullptr))
203 {
204 // Allow name entry
205 gParkFlags |= PARK_FLAGS_SCENARIO_COMPLETE_NAME_INPUT;
206 gScenarioCompanyValueRecord = companyValue;
207 }
208 scenario_end();
209 }
210
211 /**
212 *
213 * rct2: 0x006695E8
214 */
scenario_success_submit_name(const char * name)215 void scenario_success_submit_name(const char* name)
216 {
217 if (scenario_repository_try_record_highscore(gScenarioFileName, gScenarioCompanyValueRecord, name))
218 {
219 gScenarioCompletedBy = name;
220 }
221 gParkFlags &= ~PARK_FLAGS_SCENARIO_COMPLETE_NAME_INPUT;
222 }
223
224 /**
225 * Send a warning when entrance price is too high.
226 * rct2: 0x0066A80E
227 */
scenario_entrance_fee_too_high_check()228 static void scenario_entrance_fee_too_high_check()
229 {
230 money16 totalRideValueForMoney = gTotalRideValueForMoney;
231 money16 max_fee = totalRideValueForMoney + (totalRideValueForMoney / 2);
232
233 if ((gParkFlags & PARK_FLAGS_PARK_OPEN) && park_get_entrance_fee() > max_fee)
234 {
235 if (!gParkEntrances.empty())
236 {
237 const auto& entrance = gParkEntrances[0];
238 auto x = entrance.x + 16;
239 auto y = entrance.y + 16;
240
241 uint32_t packed_xy = (y << 16) | x;
242 if (gConfigNotifications.park_warnings)
243 {
244 News::AddItemToQueue(News::ItemType::Blank, STR_ENTRANCE_FEE_TOO_HI, packed_xy, {});
245 }
246 }
247 }
248 }
249
scenario_autosave_check()250 void scenario_autosave_check()
251 {
252 if (gLastAutoSaveUpdate == AUTOSAVE_PAUSE)
253 return;
254
255 // Milliseconds since last save
256 uint32_t timeSinceSave = platform_get_ticks() - gLastAutoSaveUpdate;
257
258 bool shouldSave = false;
259 switch (gConfigGeneral.autosave_frequency)
260 {
261 case AUTOSAVE_EVERY_MINUTE:
262 shouldSave = timeSinceSave >= 1 * 60 * 1000;
263 break;
264 case AUTOSAVE_EVERY_5MINUTES:
265 shouldSave = timeSinceSave >= 5 * 60 * 1000;
266 break;
267 case AUTOSAVE_EVERY_15MINUTES:
268 shouldSave = timeSinceSave >= 15 * 60 * 1000;
269 break;
270 case AUTOSAVE_EVERY_30MINUTES:
271 shouldSave = timeSinceSave >= 30 * 60 * 1000;
272 break;
273 case AUTOSAVE_EVERY_HOUR:
274 shouldSave = timeSinceSave >= 60 * 60 * 1000;
275 break;
276 }
277
278 if (shouldSave)
279 {
280 gLastAutoSaveUpdate = AUTOSAVE_PAUSE;
281 game_autosave();
282 }
283 }
284
scenario_day_update()285 static void scenario_day_update()
286 {
287 finance_update_daily_profit();
288 peep_update_days_in_queue();
289 switch (gScenarioObjective.Type)
290 {
291 case OBJECTIVE_10_ROLLERCOASTERS:
292 case OBJECTIVE_GUESTS_AND_RATING:
293 case OBJECTIVE_10_ROLLERCOASTERS_LENGTH:
294 case OBJECTIVE_FINISH_5_ROLLERCOASTERS:
295 case OBJECTIVE_REPAY_LOAN_AND_PARK_VALUE:
296 scenario_objective_check();
297 break;
298 default:
299 if (AllowEarlyCompletion())
300 scenario_objective_check();
301 break;
302 }
303
304 // Lower the casualty penalty
305 uint16_t casualtyPenaltyModifier = (gParkFlags & PARK_FLAGS_NO_MONEY) ? 40 : 7;
306 gParkRatingCasualtyPenalty = std::max(0, gParkRatingCasualtyPenalty - casualtyPenaltyModifier);
307
308 auto intent = Intent(INTENT_ACTION_UPDATE_DATE);
309 context_broadcast_intent(&intent);
310 }
311
scenario_week_update()312 static void scenario_week_update()
313 {
314 int32_t month = date_get_month(gDateMonthsElapsed);
315
316 finance_pay_wages();
317 finance_pay_research();
318 finance_pay_interest();
319 marketing_update();
320 peep_problem_warnings_update();
321 ride_check_all_reachable();
322 ride_update_favourited_stat();
323
324 auto water_type = static_cast<rct_water_type*>(object_entry_get_chunk(ObjectType::Water, 0));
325
326 if (month <= MONTH_APRIL && water_type != nullptr && water_type->flags & WATER_FLAGS_ALLOW_DUCKS)
327 {
328 // 100 attempts at finding some water to create a few ducks at
329 for (int32_t i = 0; i < 100; i++)
330 {
331 if (scenario_create_ducks())
332 break;
333 }
334 }
335 }
336
scenario_fortnight_update()337 static void scenario_fortnight_update()
338 {
339 finance_pay_ride_upkeep();
340 }
341
scenario_month_update()342 static void scenario_month_update()
343 {
344 finance_shift_expenditure_table();
345 scenario_objective_check();
346 scenario_entrance_fee_too_high_check();
347 award_update_all();
348 }
349
scenario_update_daynight_cycle()350 static void scenario_update_daynight_cycle()
351 {
352 float currentDayNightCycle = gDayNightCycle;
353 gDayNightCycle = 0;
354
355 if (gScreenFlags == SCREEN_FLAGS_PLAYING && gConfigGeneral.day_night_cycle)
356 {
357 float monthFraction = gDateMonthTicks / static_cast<float>(TICKS_PER_MONTH);
358 if (monthFraction < (1 / 8.0f))
359 {
360 gDayNightCycle = 0.0f;
361 }
362 else if (monthFraction < (3 / 8.0f))
363 {
364 gDayNightCycle = (monthFraction - (1 / 8.0f)) / (2 / 8.0f);
365 }
366 else if (monthFraction < (5 / 8.0f))
367 {
368 gDayNightCycle = 1.0f;
369 }
370 else if (monthFraction < (7 / 8.0f))
371 {
372 gDayNightCycle = 1.0f - ((monthFraction - (5 / 8.0f)) / (2 / 8.0f));
373 }
374 else
375 {
376 gDayNightCycle = 0.0f;
377 }
378 }
379
380 // Only update palette if day / night cycle has changed
381 if (gDayNightCycle != currentDayNightCycle)
382 {
383 platform_update_palette(gGamePalette, 10, 236);
384 }
385 }
386
387 /**
388 * Scenario and finance related update iteration.
389 * rct2: 0x006C44B1
390 */
scenario_update()391 void scenario_update()
392 {
393 if (gScreenFlags == SCREEN_FLAGS_PLAYING)
394 {
395 if (date_is_day_start(gDateMonthTicks))
396 {
397 scenario_day_update();
398 }
399 if (date_is_week_start(gDateMonthTicks))
400 {
401 scenario_week_update();
402 }
403 if (date_is_fortnight_start(gDateMonthTicks))
404 {
405 scenario_fortnight_update();
406 }
407 if (date_is_month_start(gDateMonthTicks))
408 {
409 scenario_month_update();
410 }
411 }
412 scenario_update_daynight_cycle();
413 }
414 /**
415 *
416 * rct2: 0x006744A9
417 */
scenario_create_ducks()418 bool scenario_create_ducks()
419 {
420 // Check NxN area around centre tile defined by SquareSize
421 constexpr int32_t SquareSize = 7;
422 constexpr int32_t SquareCentre = SquareSize / 2;
423 constexpr int32_t SquareRadiusSize = SquareCentre * 32;
424
425 CoordsXY centrePos;
426 centrePos.x = SquareRadiusSize + (scenario_rand_max(MAXIMUM_MAP_SIZE_TECHNICAL - SquareCentre) * 32);
427 centrePos.y = SquareRadiusSize + (scenario_rand_max(MAXIMUM_MAP_SIZE_TECHNICAL - SquareCentre) * 32);
428
429 Guard::Assert(map_is_location_valid(centrePos));
430
431 if (!map_is_location_in_park(centrePos))
432 return false;
433
434 int32_t centreWaterZ = (tile_element_water_height(centrePos));
435 if (centreWaterZ == 0)
436 return false;
437
438 CoordsXY innerPos{ centrePos.x - (32 * SquareCentre), centrePos.y - (32 * SquareCentre) };
439 int32_t waterTiles = 0;
440 for (int32_t y = 0; y < SquareSize; y++)
441 {
442 for (int32_t x = 0; x < SquareSize; x++)
443 {
444 if (!map_is_location_valid(innerPos))
445 continue;
446
447 if (!map_is_location_in_park(innerPos))
448 continue;
449
450 int32_t waterZ = (tile_element_water_height(innerPos));
451 if (waterZ == centreWaterZ)
452 waterTiles++;
453
454 innerPos.x += 32;
455 }
456 innerPos.x -= SquareSize * 32;
457 innerPos.y += 32;
458 }
459
460 // Must be at least 25 water tiles of the same height in 7x7 area
461 if (waterTiles < 25)
462 return false;
463
464 // Set x, y to the centre of the tile
465 centrePos.x += 16;
466 centrePos.y += 16;
467
468 uint32_t duckCount = (scenario_rand() % 4) + 2;
469 for (uint32_t i = 0; i < duckCount; i++)
470 {
471 uint32_t r = scenario_rand();
472 innerPos.x = (r >> 16) % SquareRadiusSize;
473 innerPos.y = (r & 0xFFFF) % SquareRadiusSize;
474
475 CoordsXY targetPos{ centrePos.x + innerPos.x - SquareRadiusSize, centrePos.y + innerPos.y - SquareRadiusSize };
476
477 Guard::Assert(map_is_location_valid(targetPos));
478 Duck::Create(targetPos);
479 }
480
481 return true;
482 }
483
scenario_rand_state()484 const random_engine_t::state_type& scenario_rand_state()
485 {
486 return gScenarioRand.state();
487 };
488
scenario_rand_seed(random_engine_t::result_type s0,random_engine_t::result_type s1)489 void scenario_rand_seed(random_engine_t::result_type s0, random_engine_t::result_type s1)
490 {
491 Random::Rct2::Seed s{ s0, s1 };
492 gScenarioRand.seed(s);
493 }
494
495 /**
496 *
497 * rct2: 0x006E37D2
498 *
499 * @return eax
500 */
scenario_rand()501 random_engine_t::result_type scenario_rand()
502 {
503 return gScenarioRand();
504 }
505
scenario_rand_max(uint32_t max)506 uint32_t scenario_rand_max(uint32_t max)
507 {
508 if (max < 2)
509 return 0;
510 if ((max & (max - 1)) == 0)
511 return scenario_rand() & (max - 1);
512 uint32_t rand, cap = ~(static_cast<uint32_t>(0)) - (~(static_cast<uint32_t>(0)) % max) - 1;
513 do
514 {
515 rand = scenario_rand();
516 } while (rand > cap);
517 return rand % max;
518 }
519
520 /**
521 * Prepare rides, for the finish five rollercoasters objective.
522 * rct2: 0x006788F7
523 */
scenario_prepare_rides_for_save()524 static bool scenario_prepare_rides_for_save()
525 {
526 int32_t isFiveCoasterObjective = gScenarioObjective.Type == OBJECTIVE_FINISH_5_ROLLERCOASTERS;
527 uint8_t rcs = 0;
528
529 for (auto& ride : GetRideManager())
530 {
531 const auto* rideEntry = ride.GetRideEntry();
532 if (rideEntry != nullptr)
533 {
534 // If there are more than 5 roller coasters, only mark the first five.
535 if (isFiveCoasterObjective && (ride_entry_has_category(rideEntry, RIDE_CATEGORY_ROLLERCOASTER) && rcs < 5))
536 {
537 ride.lifecycle_flags |= RIDE_LIFECYCLE_INDESTRUCTIBLE_TRACK;
538 rcs++;
539 }
540 else
541 {
542 ride.lifecycle_flags &= ~RIDE_LIFECYCLE_INDESTRUCTIBLE_TRACK;
543 }
544 }
545 }
546
547 if (isFiveCoasterObjective && rcs < 5)
548 {
549 gGameCommandErrorText = STR_NOT_ENOUGH_ROLLER_COASTERS;
550 return false;
551 }
552
553 bool markTrackAsIndestructible;
554 tile_element_iterator it;
555 tile_element_iterator_begin(&it);
556 do
557 {
558 if (it.element->GetType() == TILE_ELEMENT_TYPE_TRACK)
559 {
560 markTrackAsIndestructible = false;
561
562 if (isFiveCoasterObjective)
563 {
564 auto ride = get_ride(it.element->AsTrack()->GetRideIndex());
565
566 // In the previous step, this flag was set on the first five roller coasters.
567 if (ride != nullptr && ride->lifecycle_flags & RIDE_LIFECYCLE_INDESTRUCTIBLE_TRACK)
568 {
569 markTrackAsIndestructible = true;
570 }
571 }
572
573 it.element->AsTrack()->SetIsIndestructible(markTrackAsIndestructible);
574 }
575 } while (tile_element_iterator_next(&it));
576
577 return true;
578 }
579
580 /**
581 *
582 * rct2: 0x006726C7
583 */
scenario_prepare_for_save()584 bool scenario_prepare_for_save()
585 {
586 // This can return false if the goal is 'Finish 5 roller coaster' and there are too few.
587 if (!scenario_prepare_rides_for_save())
588 {
589 return false;
590 }
591
592 if (gScenarioObjective.Type == OBJECTIVE_GUESTS_AND_RATING)
593 gParkFlags |= PARK_FLAGS_PARK_OPEN;
594
595 // Fix #2385: saved scenarios did not initialise temperatures to selected climate
596 climate_reset(gClimate);
597
598 return true;
599 }
600
CheckGuestsBy() const601 ObjectiveStatus Objective::CheckGuestsBy() const
602 {
603 auto parkRating = gParkRating;
604 auto currentMonthYear = gDateMonthsElapsed;
605
606 if (currentMonthYear == MONTH_COUNT * Year || AllowEarlyCompletion())
607 {
608 if (parkRating >= 600 && gNumGuestsInPark >= NumGuests)
609 {
610 return ObjectiveStatus::Success;
611 }
612
613 if (currentMonthYear == MONTH_COUNT * Year)
614 {
615 return ObjectiveStatus::Failure;
616 }
617 }
618
619 return ObjectiveStatus::Undecided;
620 }
621
CheckParkValueBy() const622 ObjectiveStatus Objective::CheckParkValueBy() const
623 {
624 int32_t currentMonthYear = gDateMonthsElapsed;
625 money32 objectiveParkValue = Currency;
626 money32 parkValue = gParkValue;
627
628 if (currentMonthYear == MONTH_COUNT * Year || AllowEarlyCompletion())
629 {
630 if (parkValue >= objectiveParkValue)
631 {
632 return ObjectiveStatus::Success;
633 }
634
635 if (currentMonthYear == MONTH_COUNT * Year)
636 {
637 return ObjectiveStatus::Failure;
638 }
639 }
640
641 return ObjectiveStatus::Undecided;
642 }
643
644 /**
645 * Checks if there are 10 rollercoasters of different subtype with
646 * excitement >= 600 .
647 * rct2:
648 **/
Check10RollerCoasters() const649 ObjectiveStatus Objective::Check10RollerCoasters() const
650 {
651 auto rcs = 0;
652 std::bitset<MAX_RIDE_OBJECTS> type_already_counted;
653 for (const auto& ride : GetRideManager())
654 {
655 if (ride.status == RideStatus::Open && ride.excitement >= RIDE_RATING(6, 00) && ride.subtype != OBJECT_ENTRY_INDEX_NULL)
656 {
657 auto rideEntry = ride.GetRideEntry();
658 if (rideEntry != nullptr)
659 {
660 if (ride_entry_has_category(rideEntry, RIDE_CATEGORY_ROLLERCOASTER) && !type_already_counted[ride.subtype])
661 {
662 type_already_counted[ride.subtype] = true;
663 rcs++;
664 }
665 }
666 }
667 }
668 if (rcs >= 10)
669 {
670 return ObjectiveStatus::Success;
671 }
672
673 return ObjectiveStatus::Undecided;
674 }
675
676 /**
677 *
678 * rct2: 0x0066A13C
679 */
CheckGuestsAndRating() const680 ObjectiveStatus Objective::CheckGuestsAndRating() const
681 {
682 if (gParkRating < 700 && gDateMonthsElapsed >= 1)
683 {
684 gScenarioParkRatingWarningDays++;
685 if (gScenarioParkRatingWarningDays == 1)
686 {
687 if (gConfigNotifications.park_rating_warnings)
688 {
689 News::AddItemToQueue(News::ItemType::Graph, STR_PARK_RATING_WARNING_4_WEEKS_REMAINING, 0, {});
690 }
691 }
692 else if (gScenarioParkRatingWarningDays == 8)
693 {
694 if (gConfigNotifications.park_rating_warnings)
695 {
696 News::AddItemToQueue(News::ItemType::Graph, STR_PARK_RATING_WARNING_3_WEEKS_REMAINING, 0, {});
697 }
698 }
699 else if (gScenarioParkRatingWarningDays == 15)
700 {
701 if (gConfigNotifications.park_rating_warnings)
702 {
703 News::AddItemToQueue(News::ItemType::Graph, STR_PARK_RATING_WARNING_2_WEEKS_REMAINING, 0, {});
704 }
705 }
706 else if (gScenarioParkRatingWarningDays == 22)
707 {
708 if (gConfigNotifications.park_rating_warnings)
709 {
710 News::AddItemToQueue(News::ItemType::Graph, STR_PARK_RATING_WARNING_1_WEEK_REMAINING, 0, {});
711 }
712 }
713 else if (gScenarioParkRatingWarningDays == 29)
714 {
715 News::AddItemToQueue(News::ItemType::Graph, STR_PARK_HAS_BEEN_CLOSED_DOWN, 0, {});
716 gParkFlags &= ~PARK_FLAGS_PARK_OPEN;
717 gGuestInitialHappiness = 50;
718 return ObjectiveStatus::Failure;
719 }
720 }
721 else if (gScenarioCompletedCompanyValue != COMPANY_VALUE_ON_FAILED_OBJECTIVE)
722 {
723 gScenarioParkRatingWarningDays = 0;
724 }
725
726 if (gParkRating >= 700)
727 if (gNumGuestsInPark >= NumGuests)
728 return ObjectiveStatus::Success;
729
730 return ObjectiveStatus::Undecided;
731 }
732
CheckMonthlyRideIncome() const733 ObjectiveStatus Objective::CheckMonthlyRideIncome() const
734 {
735 money32 lastMonthRideIncome = gExpenditureTable[1][static_cast<int32_t>(ExpenditureType::ParkRideTickets)];
736 if (lastMonthRideIncome >= Currency)
737 {
738 return ObjectiveStatus::Success;
739 }
740
741 return ObjectiveStatus::Undecided;
742 }
743
744 /**
745 * Checks if there are 10 rollercoasters of different subtype with
746 * excitement > 700 and a minimum length;
747 * rct2: 0x0066A6B5
748 */
Check10RollerCoastersLength() const749 ObjectiveStatus Objective::Check10RollerCoastersLength() const
750 {
751 std::bitset<MAX_RIDE_OBJECTS> type_already_counted;
752 auto rcs = 0;
753 for (const auto& ride : GetRideManager())
754 {
755 if (ride.status == RideStatus::Open && ride.excitement >= RIDE_RATING(7, 00) && ride.subtype != OBJECT_ENTRY_INDEX_NULL)
756 {
757 auto rideEntry = ride.GetRideEntry();
758 if (rideEntry != nullptr)
759 {
760 if (ride_entry_has_category(rideEntry, RIDE_CATEGORY_ROLLERCOASTER) && !type_already_counted[ride.subtype])
761 {
762 if ((ride_get_total_length(&ride) >> 16) >= MinimumLength)
763 {
764 type_already_counted[ride.subtype] = true;
765 rcs++;
766 }
767 }
768 }
769 }
770 }
771 if (rcs >= 10)
772 {
773 return ObjectiveStatus::Success;
774 }
775
776 return ObjectiveStatus::Undecided;
777 }
778
CheckFinish5RollerCoasters() const779 ObjectiveStatus Objective::CheckFinish5RollerCoasters() const
780 {
781 // Originally, this did not check for null rides, neither did it check if
782 // the rides are even rollercoasters, never mind the right rollercoasters to be finished.
783 auto rcs = 0;
784 for (const auto& ride : GetRideManager())
785 {
786 if (ride.status != RideStatus::Closed && ride.excitement >= MinimumExcitement)
787 {
788 auto rideEntry = ride.GetRideEntry();
789 if (rideEntry != nullptr)
790 {
791 if ((ride.lifecycle_flags & RIDE_LIFECYCLE_INDESTRUCTIBLE_TRACK)
792 && ride_entry_has_category(rideEntry, RIDE_CATEGORY_ROLLERCOASTER))
793 {
794 rcs++;
795 }
796 }
797 }
798 }
799 if (rcs >= 5)
800 {
801 return ObjectiveStatus::Success;
802 }
803
804 return ObjectiveStatus::Undecided;
805 }
806
CheckRepayLoanAndParkValue() const807 ObjectiveStatus Objective::CheckRepayLoanAndParkValue() const
808 {
809 money32 parkValue = gParkValue;
810 money32 currentLoan = gBankLoan;
811
812 if (currentLoan <= 0 && parkValue >= Currency)
813 {
814 return ObjectiveStatus::Success;
815 }
816
817 return ObjectiveStatus::Undecided;
818 }
819
CheckMonthlyFoodIncome() const820 ObjectiveStatus Objective::CheckMonthlyFoodIncome() const
821 {
822 const auto* lastMonthExpenditure = gExpenditureTable[1];
823 auto lastMonthProfit = lastMonthExpenditure[static_cast<int32_t>(ExpenditureType::ShopSales)]
824 + lastMonthExpenditure[static_cast<int32_t>(ExpenditureType::ShopStock)]
825 + lastMonthExpenditure[static_cast<int32_t>(ExpenditureType::FoodDrinkSales)]
826 + lastMonthExpenditure[static_cast<int32_t>(ExpenditureType::FoodDrinkStock)];
827
828 if (lastMonthProfit >= Currency)
829 {
830 return ObjectiveStatus::Success;
831 }
832
833 return ObjectiveStatus::Undecided;
834 }
835
836 /*
837 * Returns the AllowEarlyCompletion-Option to be used
838 * depending on the Current Network-Mode.
839 */
AllowEarlyCompletion()840 bool AllowEarlyCompletion()
841 {
842 switch (network_get_mode())
843 {
844 case NETWORK_MODE_CLIENT:
845 return gAllowEarlyCompletionInNetworkPlay;
846 case NETWORK_MODE_NONE:
847 case NETWORK_MODE_SERVER:
848 default:
849 return gConfigGeneral.allow_early_completion;
850 }
851 }
852
scenario_objective_check()853 static void scenario_objective_check()
854 {
855 auto status = gScenarioObjective.Check();
856 if (status == ObjectiveStatus::Success)
857 {
858 scenario_success();
859 }
860 else if (status == ObjectiveStatus::Failure)
861 {
862 scenario_failure();
863 }
864 }
865
866 /**
867 * Checks the win/lose conditions of the current objective.
868 * rct2: 0x0066A4B2
869 */
Check() const870 ObjectiveStatus Objective::Check() const
871 {
872 if (gScenarioCompletedCompanyValue != MONEY64_UNDEFINED)
873 {
874 return ObjectiveStatus::Undecided;
875 }
876
877 switch (Type)
878 {
879 case OBJECTIVE_GUESTS_BY:
880 return CheckGuestsBy();
881 case OBJECTIVE_PARK_VALUE_BY:
882 return CheckParkValueBy();
883 case OBJECTIVE_10_ROLLERCOASTERS:
884 return Check10RollerCoasters();
885 case OBJECTIVE_GUESTS_AND_RATING:
886 return CheckGuestsAndRating();
887 case OBJECTIVE_MONTHLY_RIDE_INCOME:
888 return CheckMonthlyRideIncome();
889 case OBJECTIVE_10_ROLLERCOASTERS_LENGTH:
890 return Check10RollerCoastersLength();
891 case OBJECTIVE_FINISH_5_ROLLERCOASTERS:
892 return CheckFinish5RollerCoasters();
893 case OBJECTIVE_REPAY_LOAN_AND_PARK_VALUE:
894 return CheckRepayLoanAndParkValue();
895 case OBJECTIVE_MONTHLY_FOOD_INCOME:
896 return CheckMonthlyFoodIncome();
897 }
898
899 return ObjectiveStatus::Undecided;
900 }
901
ObjectiveNeedsMoney(const uint8_t objective)902 bool ObjectiveNeedsMoney(const uint8_t objective)
903 {
904 switch (objective)
905 {
906 case OBJECTIVE_PARK_VALUE_BY:
907 case OBJECTIVE_MONTHLY_RIDE_INCOME:
908 case OBJECTIVE_REPAY_LOAN_AND_PARK_VALUE:
909 case OBJECTIVE_MONTHLY_FOOD_INCOME:
910 return true;
911 }
912
913 return false;
914 }
915