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 "Park.h"
11
12 #include "../Cheats.h"
13 #include "../Context.h"
14 #include "../Date.h"
15 #include "../Game.h"
16 #include "../GameState.h"
17 #include "../OpenRCT2.h"
18 #include "../actions/ParkSetParameterAction.h"
19 #include "../config/Config.h"
20 #include "../core/Memory.hpp"
21 #include "../core/String.hpp"
22 #include "../interface/Colour.h"
23 #include "../interface/Window.h"
24 #include "../localisation/Localisation.h"
25 #include "../management/Award.h"
26 #include "../management/Finance.h"
27 #include "../management/Marketing.h"
28 #include "../management/Research.h"
29 #include "../network/network.h"
30 #include "../peep/Peep.h"
31 #include "../peep/Staff.h"
32 #include "../ride/Ride.h"
33 #include "../ride/RideData.h"
34 #include "../ride/ShopItem.h"
35 #include "../scenario/Scenario.h"
36 #include "../util/Util.h"
37 #include "../windows/Intent.h"
38 #include "Entrance.h"
39 #include "Litter.h"
40 #include "Map.h"
41 #include "Surface.h"
42
43 #include <algorithm>
44 #include <limits>
45
46 using namespace OpenRCT2;
47
48 uint64_t gParkFlags;
49 uint16_t gParkRating;
50 money16 gParkEntranceFee;
51 uint16_t gParkSize;
52 money16 gLandPrice;
53 money16 gConstructionRightsPrice;
54
55 uint64_t gTotalAdmissions;
56 money64 gTotalIncomeFromAdmissions;
57
58 money64 gParkValue;
59 money64 gCompanyValue;
60
61 int16_t gParkRatingCasualtyPenalty;
62 uint8_t gParkRatingHistory[32];
63 uint32_t gGuestsInParkHistory[32];
64
65 // If this value is more than or equal to 0, the park rating is forced to this value. Used for cheat
66 static int32_t _forcedParkRating = -1;
67
68 /**
69 * In a difficult guest generation scenario, no guests will be generated if over this value.
70 */
71 uint32_t _suggestedGuestMaximum;
72
73 /**
74 * Probability out of 65535, of gaining a new guest per game tick.
75 * new guests per second = 40 * (probability / 65535)
76 * With a full park rating, non-overpriced entrance fee, less guests than the suggested maximum and four positive awards,
77 * approximately 1 guest per second can be generated (+60 guests in one minute).
78 */
79 int32_t _guestGenerationProbability;
80
81 /**
82 * Choose a random peep spawn and iterates through until defined spawn is found.
83 */
get_random_peep_spawn()84 static PeepSpawn* get_random_peep_spawn()
85 {
86 if (!gPeepSpawns.empty())
87 {
88 return &gPeepSpawns[scenario_rand() % gPeepSpawns.size()];
89 }
90
91 return nullptr;
92 }
93
park_set_open(bool open)94 void park_set_open(bool open)
95 {
96 auto parkSetParameter = ParkSetParameterAction(open ? ParkParameter::Open : ParkParameter::Close);
97 GameActions::Execute(&parkSetParameter);
98 }
99
100 /**
101 *
102 * rct2: 0x00664D05
103 */
update_park_fences(const CoordsXY & coords)104 void update_park_fences(const CoordsXY& coords)
105 {
106 if (map_is_edge(coords))
107 return;
108
109 auto surfaceElement = map_get_surface_element_at(coords);
110 if (surfaceElement == nullptr)
111 return;
112
113 uint8_t newFences = 0;
114 if ((surfaceElement->GetOwnership() & OWNERSHIP_OWNED) == 0)
115 {
116 bool fenceRequired = true;
117
118 TileElement* tileElement = map_get_first_element_at(coords);
119 if (tileElement == nullptr)
120 return;
121 // If an entrance element do not place flags around surface
122 do
123 {
124 if (tileElement->GetType() != TILE_ELEMENT_TYPE_ENTRANCE)
125 continue;
126
127 if (tileElement->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_PARK_ENTRANCE)
128 continue;
129
130 if (!(tileElement->IsGhost()))
131 {
132 fenceRequired = false;
133 break;
134 }
135 } while (!(tileElement++)->IsLastForTile());
136
137 if (fenceRequired)
138 {
139 // As map_is_location_in_park sets the error text
140 // will require to back it up.
141 rct_string_id previous_error = gGameCommandErrorText;
142 if (map_is_location_in_park({ coords.x - COORDS_XY_STEP, coords.y }))
143 {
144 newFences |= 0x8;
145 }
146
147 if (map_is_location_in_park({ coords.x, coords.y - COORDS_XY_STEP }))
148 {
149 newFences |= 0x4;
150 }
151
152 if (map_is_location_in_park({ coords.x + COORDS_XY_STEP, coords.y }))
153 {
154 newFences |= 0x2;
155 }
156
157 if (map_is_location_in_park({ coords.x, coords.y + COORDS_XY_STEP }))
158 {
159 newFences |= 0x1;
160 }
161
162 gGameCommandErrorText = previous_error;
163 }
164 }
165
166 if (surfaceElement->GetParkFences() != newFences)
167 {
168 int32_t baseZ = surfaceElement->GetBaseZ();
169 int32_t clearZ = baseZ + 16;
170 map_invalidate_tile({ coords, baseZ, clearZ });
171 surfaceElement->SetParkFences(newFences);
172 }
173 }
174
update_park_fences_around_tile(const CoordsXY & coords)175 void update_park_fences_around_tile(const CoordsXY& coords)
176 {
177 update_park_fences(coords);
178 update_park_fences({ coords.x + COORDS_XY_STEP, coords.y });
179 update_park_fences({ coords.x - COORDS_XY_STEP, coords.y });
180 update_park_fences({ coords.x, coords.y + COORDS_XY_STEP });
181 update_park_fences({ coords.x, coords.y - COORDS_XY_STEP });
182 }
183
set_forced_park_rating(int32_t rating)184 void set_forced_park_rating(int32_t rating)
185 {
186 _forcedParkRating = rating;
187 auto& park = GetContext()->GetGameState()->GetPark();
188 gParkRating = park.CalculateParkRating();
189 auto intent = Intent(INTENT_ACTION_UPDATE_PARK_RATING);
190 context_broadcast_intent(&intent);
191 }
192
get_forced_park_rating()193 int32_t get_forced_park_rating()
194 {
195 return _forcedParkRating;
196 }
197
park_get_entrance_fee()198 money16 park_get_entrance_fee()
199 {
200 if (gParkFlags & PARK_FLAGS_NO_MONEY)
201 {
202 return 0;
203 }
204 if (!park_entry_price_unlocked())
205 {
206 return 0;
207 }
208 return gParkEntranceFee;
209 }
210
park_ride_prices_unlocked()211 bool park_ride_prices_unlocked()
212 {
213 if (gParkFlags & PARK_FLAGS_UNLOCK_ALL_PRICES)
214 {
215 return true;
216 }
217 if (gParkFlags & PARK_FLAGS_PARK_FREE_ENTRY)
218 {
219 return true;
220 }
221 return false;
222 }
223
park_entry_price_unlocked()224 bool park_entry_price_unlocked()
225 {
226 if (gParkFlags & PARK_FLAGS_UNLOCK_ALL_PRICES)
227 {
228 return true;
229 }
230 if (!(gParkFlags & PARK_FLAGS_PARK_FREE_ENTRY))
231 {
232 return true;
233 }
234 return false;
235 }
236
IsOpen() const237 bool Park::IsOpen() const
238 {
239 return (gParkFlags & PARK_FLAGS_PARK_OPEN) != 0;
240 }
241
GetParkRating() const242 uint16_t Park::GetParkRating() const
243 {
244 return gParkRating;
245 }
246
GetParkValue() const247 money64 Park::GetParkValue() const
248 {
249 return gParkValue;
250 }
251
GetCompanyValue() const252 money64 Park::GetCompanyValue() const
253 {
254 return gCompanyValue;
255 }
256
Initialise()257 void Park::Initialise()
258 {
259 Name = format_string(STR_UNNAMED_PARK, nullptr);
260 gStaffHandymanColour = COLOUR_BRIGHT_RED;
261 gStaffMechanicColour = COLOUR_LIGHT_BLUE;
262 gStaffSecurityColour = COLOUR_YELLOW;
263 gNumGuestsInPark = 0;
264 gNumGuestsInParkLastWeek = 0;
265 gNumGuestsHeadingForPark = 0;
266 gGuestChangeModifier = 0;
267 gParkRating = 0;
268 _guestGenerationProbability = 0;
269 gTotalRideValueForMoney = 0;
270 gResearchLastItem = std::nullopt;
271 gMarketingCampaigns.clear();
272
273 research_reset_items();
274 finance_init();
275
276 set_every_ride_type_not_invented();
277
278 set_all_scenery_items_invented();
279
280 gParkEntranceFee = MONEY(10, 00);
281
282 gPeepSpawns.clear();
283 reset_park_entrance();
284
285 gResearchPriorities = EnumsToFlags(
286 ResearchCategory::Transport, ResearchCategory::Gentle, ResearchCategory::Rollercoaster, ResearchCategory::Thrill,
287 ResearchCategory::Water, ResearchCategory::Shop, ResearchCategory::SceneryGroup);
288 gResearchFundingLevel = RESEARCH_FUNDING_NORMAL;
289
290 gGuestInitialCash = MONEY(50, 00);
291 gGuestInitialHappiness = CalculateGuestInitialHappiness(50);
292 gGuestInitialHunger = 200;
293 gGuestInitialThirst = 200;
294 gScenarioObjective.Type = OBJECTIVE_GUESTS_BY;
295 gScenarioObjective.Year = 4;
296 gScenarioObjective.NumGuests = 1000;
297 gLandPrice = MONEY(90, 00);
298 gConstructionRightsPrice = MONEY(40, 00);
299 gParkFlags = PARK_FLAGS_NO_MONEY | PARK_FLAGS_SHOW_REAL_GUEST_NAMES;
300 ResetHistories();
301 finance_reset_history();
302 award_reset();
303
304 gScenarioName = "";
305 gScenarioDetails = String::ToStd(language_get_string(STR_NO_DETAILS_YET));
306 }
307
Update(const Date & date)308 void Park::Update(const Date& date)
309 {
310 // Every ~13 seconds
311 if (gCurrentTicks % 512 == 0)
312 {
313 gParkRating = CalculateParkRating();
314 gParkValue = CalculateParkValue();
315 gCompanyValue = CalculateCompanyValue();
316 gTotalRideValueForMoney = CalculateTotalRideValueForMoney();
317 _suggestedGuestMaximum = CalculateSuggestedMaxGuests();
318 _guestGenerationProbability = CalculateGuestGenerationProbability();
319
320 window_invalidate_by_class(WC_FINANCES);
321 auto intent = Intent(INTENT_ACTION_UPDATE_PARK_RATING);
322 context_broadcast_intent(&intent);
323 }
324 // Every ~102 seconds
325 if (gCurrentTicks % 4096 == 0)
326 {
327 gParkSize = CalculateParkSize();
328 window_invalidate_by_class(WC_PARK_INFORMATION);
329 }
330 // Every new week
331 if (date.IsWeekStart())
332 {
333 UpdateHistories();
334 }
335 GenerateGuests();
336 }
337
CalculateParkSize() const338 int32_t Park::CalculateParkSize() const
339 {
340 int32_t tiles = 0;
341 tile_element_iterator it;
342 tile_element_iterator_begin(&it);
343 do
344 {
345 if (it.element->GetType() == TILE_ELEMENT_TYPE_SURFACE)
346 {
347 if (it.element->AsSurface()->GetOwnership() & (OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED | OWNERSHIP_OWNED))
348 {
349 tiles++;
350 }
351 }
352 } while (tile_element_iterator_next(&it));
353
354 if (tiles != gParkSize)
355 {
356 gParkSize = tiles;
357 window_invalidate_by_class(WC_PARK_INFORMATION);
358 }
359
360 return tiles;
361 }
362
CalculateParkRating() const363 int32_t Park::CalculateParkRating() const
364 {
365 if (_forcedParkRating >= 0)
366 {
367 return _forcedParkRating;
368 }
369
370 int32_t result = 1150;
371 if (gParkFlags & PARK_FLAGS_DIFFICULT_PARK_RATING)
372 {
373 result = 1050;
374 }
375
376 // Guests
377 {
378 // -150 to +3 based on a range of guests from 0 to 2000
379 result -= 150 - (std::min<int16_t>(2000, gNumGuestsInPark) / 13);
380
381 // Find the number of happy peeps and the number of peeps who can't find the park exit
382 uint32_t happyGuestCount = 0;
383 uint32_t lostGuestCount = 0;
384 for (auto peep : EntityList<Guest>())
385 {
386 if (!peep->OutsideOfPark)
387 {
388 if (peep->Happiness > 128)
389 {
390 happyGuestCount++;
391 }
392 if ((peep->PeepFlags & PEEP_FLAGS_LEAVING_PARK) && (peep->GuestIsLostCountdown < 90))
393 {
394 lostGuestCount++;
395 }
396 }
397 }
398
399 // Peep happiness -500 to +0
400 result -= 500;
401 if (gNumGuestsInPark > 0)
402 {
403 result += 2 * std::min(250u, (happyGuestCount * 300) / gNumGuestsInPark);
404 }
405
406 // Up to 25 guests can be lost without affecting the park rating.
407 if (lostGuestCount > 25)
408 {
409 result -= (lostGuestCount - 25) * 7;
410 }
411 }
412
413 // Rides
414 {
415 int32_t rideCount = 0;
416 int32_t excitingRideCount = 0;
417 int32_t totalRideUptime = 0;
418 int32_t totalRideIntensity = 0;
419 int32_t totalRideExcitement = 0;
420 for (auto& ride : GetRideManager())
421 {
422 totalRideUptime += 100 - ride.downtime;
423 if (ride_has_ratings(&ride))
424 {
425 totalRideExcitement += ride.excitement / 8;
426 totalRideIntensity += ride.intensity / 8;
427 excitingRideCount++;
428 }
429 rideCount++;
430 }
431 result -= 200;
432 if (rideCount > 0)
433 {
434 result += (totalRideUptime / rideCount) * 2;
435 }
436 result -= 100;
437 if (excitingRideCount > 0)
438 {
439 int32_t averageExcitement = totalRideExcitement / excitingRideCount;
440 int32_t averageIntensity = totalRideIntensity / excitingRideCount;
441
442 averageExcitement -= 46;
443 if (averageExcitement < 0)
444 {
445 averageExcitement = -averageExcitement;
446 }
447
448 averageIntensity -= 65;
449 if (averageIntensity < 0)
450 {
451 averageIntensity = -averageIntensity;
452 }
453
454 averageExcitement = std::min(averageExcitement / 2, 50);
455 averageIntensity = std::min(averageIntensity / 2, 50);
456 result += 100 - averageExcitement - averageIntensity;
457 }
458
459 totalRideExcitement = std::min<int16_t>(1000, totalRideExcitement);
460 totalRideIntensity = std::min<int16_t>(1000, totalRideIntensity);
461 result -= 200 - ((totalRideExcitement + totalRideIntensity) / 10);
462 }
463
464 // Litter
465 {
466 // Counts the amount of litter whose age is min. 7680 ticks (5~ min) old.
467 const auto litterList = EntityList<Litter>();
468 const auto litterCount = std::count_if(
469 litterList.begin(), litterList.end(), [](auto* litter) { return litter->GetAge() >= 7680; });
470
471 result -= 600 - (4 * (150 - std::min<int32_t>(150, litterCount)));
472 }
473
474 result -= gParkRatingCasualtyPenalty;
475 result = std::clamp(result, 0, 999);
476 return result;
477 }
478
CalculateParkValue() const479 money64 Park::CalculateParkValue() const
480 {
481 // Sum ride values
482 money64 result = 0;
483 for (const auto& ride : GetRideManager())
484 {
485 result += CalculateRideValue(&ride);
486 }
487
488 // +7.00 per guest
489 result += gNumGuestsInPark * MONEY(7, 00);
490
491 return result;
492 }
493
CalculateRideValue(const Ride * ride) const494 money64 Park::CalculateRideValue(const Ride* ride) const
495 {
496 money64 result = 0;
497 if (ride != nullptr && ride->value != RIDE_VALUE_UNDEFINED)
498 {
499 const auto& rtd = ride->GetRideTypeDescriptor();
500 result = (ride->value * 10LL) * (static_cast<money64>(ride_customers_in_last_5_minutes(ride)) + rtd.BonusValue * 4);
501 }
502 return result;
503 }
504
CalculateCompanyValue() const505 money64 Park::CalculateCompanyValue() const
506 {
507 auto result = gParkValue - gBankLoan;
508
509 // Clamp addition to prevent overflow
510 result = add_clamp_money64(result, finance_get_current_cash());
511
512 return result;
513 }
514
CalculateTotalRideValueForMoney() const515 money16 Park::CalculateTotalRideValueForMoney() const
516 {
517 money16 totalRideValue = 0;
518 bool ridePricesUnlocked = park_ride_prices_unlocked() && !(gParkFlags & PARK_FLAGS_NO_MONEY);
519 for (auto& ride : GetRideManager())
520 {
521 if (ride.status != RideStatus::Open)
522 continue;
523 if (ride.lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN)
524 continue;
525 if (ride.lifecycle_flags & RIDE_LIFECYCLE_CRASHED)
526 continue;
527
528 // Add ride value
529 if (ride.value != RIDE_VALUE_UNDEFINED)
530 {
531 money16 rideValue = static_cast<money16>(ride.value);
532 if (ridePricesUnlocked)
533 {
534 rideValue -= ride.price[0];
535 }
536 if (rideValue > 0)
537 {
538 totalRideValue += rideValue * 2;
539 }
540 }
541 }
542 return totalRideValue;
543 }
544
CalculateSuggestedMaxGuests() const545 uint32_t Park::CalculateSuggestedMaxGuests() const
546 {
547 uint32_t suggestedMaxGuests = 0;
548
549 // TODO combine the two ride loops
550 for (auto& ride : GetRideManager())
551 {
552 if (ride.status != RideStatus::Open)
553 continue;
554 if (ride.lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN)
555 continue;
556 if (ride.lifecycle_flags & RIDE_LIFECYCLE_CRASHED)
557 continue;
558
559 // Add guest score for ride type
560 suggestedMaxGuests += ride.GetRideTypeDescriptor().BonusValue;
561 }
562
563 // If difficult guest generation, extra guests are available for good rides
564 if (gParkFlags & PARK_FLAGS_DIFFICULT_GUEST_GENERATION)
565 {
566 suggestedMaxGuests = std::min<uint32_t>(suggestedMaxGuests, 1000);
567 for (auto& ride : GetRideManager())
568 {
569 if (ride.lifecycle_flags & RIDE_LIFECYCLE_CRASHED)
570 continue;
571 if (ride.lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN)
572 continue;
573 if (!(ride.lifecycle_flags & RIDE_LIFECYCLE_TESTED))
574 continue;
575 if (!ride.GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_TRACK))
576 continue;
577 if (!ride.GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_DATA_LOGGING))
578 continue;
579 if (ride.stations[0].SegmentLength < (600 << 16))
580 continue;
581 if (ride.excitement < RIDE_RATING(6, 00))
582 continue;
583
584 // Bonus guests for good ride
585 suggestedMaxGuests += ride.GetRideTypeDescriptor().BonusValue * 2;
586 }
587 }
588
589 suggestedMaxGuests = std::min<uint32_t>(suggestedMaxGuests, 65535);
590 return suggestedMaxGuests;
591 }
592
CalculateGuestGenerationProbability() const593 uint32_t Park::CalculateGuestGenerationProbability() const
594 {
595 // Begin with 50 + park rating
596 uint32_t probability = 50 + std::clamp(gParkRating - 200, 0, 650);
597
598 // The more guests, the lower the chance of a new one
599 uint32_t numGuests = gNumGuestsInPark + gNumGuestsHeadingForPark;
600 if (numGuests > _suggestedGuestMaximum)
601 {
602 probability /= 4;
603 // Even lower for difficult guest generation
604 if (gParkFlags & PARK_FLAGS_DIFFICULT_GUEST_GENERATION)
605 {
606 probability /= 4;
607 }
608 }
609
610 // Reduces chance for any more than 7000 guests
611 if (numGuests > 7000)
612 {
613 probability /= 4;
614 }
615
616 // Penalty for overpriced entrance fee relative to total ride value
617 money16 entranceFee = park_get_entrance_fee();
618 if (entranceFee > gTotalRideValueForMoney)
619 {
620 probability /= 4;
621 // Extra penalty for very overpriced entrance fee
622 if (entranceFee / 2 > gTotalRideValueForMoney)
623 {
624 probability /= 4;
625 }
626 }
627
628 // Reward or penalties for park awards
629 for (size_t i = 0; i < MAX_AWARDS; i++)
630 {
631 const auto award = &gCurrentAwards[i];
632 if (award->Time != 0)
633 {
634 // +/- 0.25% of the probability
635 if (award_is_positive(award->Type))
636 {
637 probability += probability / 4;
638 }
639 else
640 {
641 probability -= probability / 4;
642 }
643 }
644 }
645
646 return probability;
647 }
648
CalculateGuestInitialHappiness(uint8_t percentage)649 uint8_t Park::CalculateGuestInitialHappiness(uint8_t percentage)
650 {
651 percentage = std::clamp<uint8_t>(percentage, 15, 98);
652
653 // The percentages follow this sequence:
654 // 15 17 18 20 21 23 25 26 28 29 31 32 34 36 37 39 40 42 43 45 47 48 50 51 53...
655 // This sequence can be defined as PI*(9+n)/2 (the value is floored)
656 for (uint8_t n = 1; n < 55; n++)
657 {
658 // Avoid floating point math by rescaling PI up.
659 constexpr int32_t SCALE = 100000;
660 constexpr int32_t PI_SCALED = 314159; // PI * SCALE;
661 if (((PI_SCALED * (9 + n)) / SCALE) / 2 >= percentage)
662 {
663 return (9 + n) * 4;
664 }
665 }
666
667 // This is the lowest possible value:
668 return 40;
669 }
670
GenerateGuests()671 void Park::GenerateGuests()
672 {
673 // Generate a new guest for some probability
674 if (static_cast<int32_t>(scenario_rand() & 0xFFFF) < _guestGenerationProbability)
675 {
676 bool difficultGeneration = (gParkFlags & PARK_FLAGS_DIFFICULT_GUEST_GENERATION) != 0;
677 if (!difficultGeneration || _suggestedGuestMaximum + 150 >= gNumGuestsInPark)
678 {
679 GenerateGuest();
680 }
681 }
682
683 // Extra guests generated by advertising campaigns
684 for (const auto& campaign : gMarketingCampaigns)
685 {
686 // Random chance of guest generation
687 auto probability = marketing_get_campaign_guest_generation_probability(campaign.Type);
688 auto random = scenario_rand_max(std::numeric_limits<uint16_t>::max());
689 if (random < probability)
690 {
691 GenerateGuestFromCampaign(campaign.Type);
692 }
693 }
694 }
695
GenerateGuestFromCampaign(int32_t campaign)696 Guest* Park::GenerateGuestFromCampaign(int32_t campaign)
697 {
698 auto peep = GenerateGuest();
699 if (peep != nullptr)
700 {
701 marketing_set_guest_campaign(peep, campaign);
702 }
703 return peep;
704 }
705
GenerateGuest()706 Guest* Park::GenerateGuest()
707 {
708 Guest* peep = nullptr;
709 const auto spawn = get_random_peep_spawn();
710 if (spawn != nullptr)
711 {
712 auto direction = direction_reverse(spawn->direction);
713 peep = Guest::Generate({ spawn->x, spawn->y, spawn->z });
714 if (peep != nullptr)
715 {
716 peep->sprite_direction = direction << 3;
717
718 auto destination = peep->GetLocation().ToTileCentre();
719 peep->SetDestination(destination, 5);
720 peep->PeepDirection = direction;
721 peep->Var37 = 0;
722 peep->State = PeepState::EnteringPark;
723 }
724 }
725 return peep;
726 }
727
HistoryPushRecord(T history[TSize],T newItem)728 template<typename T, size_t TSize> static void HistoryPushRecord(T history[TSize], T newItem)
729 {
730 for (size_t i = TSize - 1; i > 0; i--)
731 {
732 history[i] = history[i - 1];
733 }
734 history[0] = newItem;
735 }
736
ResetHistories()737 void Park::ResetHistories()
738 {
739 std::fill(std::begin(gParkRatingHistory), std::end(gParkRatingHistory), ParkRatingHistoryUndefined);
740 std::fill(std::begin(gGuestsInParkHistory), std::end(gGuestsInParkHistory), GuestsInParkHistoryUndefined);
741 }
742
UpdateHistories()743 void Park::UpdateHistories()
744 {
745 uint8_t guestChangeModifier = 1;
746 int32_t changeInGuestsInPark = static_cast<int32_t>(gNumGuestsInPark) - static_cast<int32_t>(gNumGuestsInParkLastWeek);
747 if (changeInGuestsInPark > -20)
748 {
749 guestChangeModifier++;
750 if (changeInGuestsInPark < 20)
751 {
752 guestChangeModifier = 0;
753 }
754 }
755 gGuestChangeModifier = guestChangeModifier;
756 gNumGuestsInParkLastWeek = gNumGuestsInPark;
757
758 // Update park rating, guests in park and current cash history
759 HistoryPushRecord<uint8_t, 32>(gParkRatingHistory, CalculateParkRating() / 4);
760 HistoryPushRecord<uint32_t, 32>(gGuestsInParkHistory, gNumGuestsInPark);
761 HistoryPushRecord<money64, std::size(gCashHistory)>(gCashHistory, finance_get_current_cash() - gBankLoan);
762
763 // Update weekly profit history
764 auto currentWeeklyProfit = gWeeklyProfitAverageDividend;
765 if (gWeeklyProfitAverageDivisor != 0)
766 {
767 currentWeeklyProfit /= gWeeklyProfitAverageDivisor;
768 }
769 HistoryPushRecord<money64, std::size(gWeeklyProfitHistory)>(gWeeklyProfitHistory, currentWeeklyProfit);
770 gWeeklyProfitAverageDividend = 0;
771 gWeeklyProfitAverageDivisor = 0;
772
773 // Update park value history
774 HistoryPushRecord<money64, std::size(gParkValueHistory)>(gParkValueHistory, gParkValue);
775
776 // Invalidate relevant windows
777 auto intent = Intent(INTENT_ACTION_UPDATE_GUEST_COUNT);
778 context_broadcast_intent(&intent);
779 window_invalidate_by_class(WC_PARK_INFORMATION);
780 window_invalidate_by_class(WC_FINANCES);
781 }
782
park_is_open()783 int32_t park_is_open()
784 {
785 return GetContext()->GetGameState()->GetPark().IsOpen();
786 }
787
park_calculate_size()788 int32_t park_calculate_size()
789 {
790 auto tiles = GetContext()->GetGameState()->GetPark().CalculateParkSize();
791 if (tiles != gParkSize)
792 {
793 gParkSize = tiles;
794 window_invalidate_by_class(WC_PARK_INFORMATION);
795 }
796 return tiles;
797 }
798
calculate_guest_initial_happiness(uint8_t percentage)799 uint8_t calculate_guest_initial_happiness(uint8_t percentage)
800 {
801 return Park::CalculateGuestInitialHappiness(percentage);
802 }
803