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