diff --git a/src/openrct2/Cheats.cpp b/src/openrct2/Cheats.cpp index 446714dbb7..3ed28301f0 100644 --- a/src/openrct2/Cheats.cpp +++ b/src/openrct2/Cheats.cpp @@ -30,6 +30,8 @@ #include "world/Sprite.h" #include "world/Surface.h" +using namespace OpenRCT2; + bool gCheatsSandboxMode = false; bool gCheatsDisableClearanceChecks = false; bool gCheatsDisableSupportLimits = false; @@ -267,8 +269,9 @@ static void cheat_clear_loan() static void cheat_generate_guests(sint32 count) { + auto park = GetContext()->GetPark(); for (sint32 i = 0; i < count; i++) - park_generate_new_guest(); + park->GenerateGuest(); window_invalidate_by_class(WC_BOTTOM_TOOLBAR); } diff --git a/src/openrct2/Context.cpp b/src/openrct2/Context.cpp index 67801e53e7..a5b31e1440 100644 --- a/src/openrct2/Context.cpp +++ b/src/openrct2/Context.cpp @@ -52,6 +52,7 @@ #include "title/TitleScreen.h" #include "title/TitleSequenceManager.h" #include "ui/WindowManager.h" +#include "world/Park.h" #include "Version.h" #include "audio/audio.h" @@ -104,6 +105,7 @@ namespace OpenRCT2 // Game states std::unique_ptr _titleScreen; + std::unique_ptr _park; sint32 _drawingEngineType = DRAWING_ENGINE_SOFTWARE; std::unique_ptr _drawingEngine; @@ -164,6 +166,11 @@ namespace OpenRCT2 return _uiContext; } + Park * GetPark() override + { + return _park.get(); + } + std::shared_ptr GetPlatformEnvironment() override { return _env; @@ -444,6 +451,7 @@ namespace OpenRCT2 game_init_all(150); _titleScreen = std::make_unique(); + _park = std::make_unique(); return true; } diff --git a/src/openrct2/Context.h b/src/openrct2/Context.h index 15b9a51854..b6e1cb243d 100644 --- a/src/openrct2/Context.h +++ b/src/openrct2/Context.h @@ -71,6 +71,7 @@ enum namespace OpenRCT2 { interface IPlatformEnvironment; + class Park; namespace Audio { @@ -101,6 +102,7 @@ namespace OpenRCT2 virtual std::shared_ptr GetAudioContext() abstract; virtual std::shared_ptr GetUiContext() abstract; + virtual Park * GetPark() abstract; virtual std::shared_ptr GetPlatformEnvironment() abstract; virtual Localisation::LocalisationService& GetLocalisationService() abstract; virtual std::shared_ptr GetObjectManager() abstract; diff --git a/src/openrct2/Game.cpp b/src/openrct2/Game.cpp index a3aa578523..b0934b7565 100644 --- a/src/openrct2/Game.cpp +++ b/src/openrct2/Game.cpp @@ -111,6 +111,8 @@ rct_string_id gGameCommandErrorText; uint8 gErrorType; rct_string_id gErrorStringId; +using namespace OpenRCT2; + sint32 game_command_callback_get_index(GAME_COMMAND_CALLBACK_POINTER * callback) { for (uint32 i = 0; i < Util::CountOf(game_command_callback_table); i++) @@ -489,7 +491,7 @@ void game_logic_update() vehicle_update_all(); sprite_misc_update_all(); ride_update_all(); - park_update(); + GetContext()->GetPark()->Update(); research_update(); ride_ratings_update_all(); ride_measurements_update(); diff --git a/src/openrct2/actions/RideDemolishAction.hpp b/src/openrct2/actions/RideDemolishAction.hpp index f591d51f10..a43e0bd8e4 100644 --- a/src/openrct2/actions/RideDemolishAction.hpp +++ b/src/openrct2/actions/RideDemolishAction.hpp @@ -31,6 +31,8 @@ #include "GameAction.h" #include "MazeSetTrackAction.hpp" +using namespace OpenRCT2; + struct RideDemolishAction : public GameActionBase { private: @@ -190,7 +192,7 @@ public: user_string_free(ride->name); ride->type = RIDE_TYPE_NULL; - gParkValue = calculate_park_value(); + gParkValue = GetContext()->GetPark()->CalculateCompanyValue(); auto res = std::make_unique(); res->ExpenditureType = RCT_EXPENDITURE_TYPE_RIDE_CONSTRUCTION; diff --git a/src/openrct2/rct1/S4Importer.cpp b/src/openrct2/rct1/S4Importer.cpp index ad626dacf7..b3a723f8fb 100644 --- a/src/openrct2/rct1/S4Importer.cpp +++ b/src/openrct2/rct1/S4Importer.cpp @@ -66,6 +66,8 @@ #include "../world/SmallScenery.h" #include "../world/Surface.h" +using namespace OpenRCT2; + static uint8 GetPathType(rct_tile_element * tileElement); static sint32 GetWallType(rct_tile_element * tileElement, sint32 edge); static uint8 GetWallColour(rct_tile_element * tileElement); @@ -316,7 +318,8 @@ public: { // Use the ratio between the old and new park value to calcute the ratio to // use for the park value history and the goal. - _parkValueConversionFactor = (calculate_park_value() * 10) / _s4.park_value; + auto park = GetContext()->GetPark(); + _parkValueConversionFactor = (park->CalculateParkValue() * 10) / _s4.park_value; } else { diff --git a/src/openrct2/scenario/Scenario.cpp b/src/openrct2/scenario/Scenario.cpp index 542178f3e4..1e5e56fced 100644 --- a/src/openrct2/scenario/Scenario.cpp +++ b/src/openrct2/scenario/Scenario.cpp @@ -90,6 +90,8 @@ char gScenarioFileName[MAX_PATH]; static sint32 scenario_create_ducks(); static void scenario_objective_check(); +using namespace OpenRCT2; + void scenario_begin() { game_load_init(); @@ -108,9 +110,10 @@ void scenario_begin() if (gScenarioObjectiveType != OBJECTIVE_NONE && !gLoadKeepWindowsOpen) context_open_window_view(WV_PARK_OBJECTIVE); - gParkRating = calculate_park_rating(); - gParkValue = calculate_park_value(); - gCompanyValue = calculate_company_value(); + auto park = GetContext()->GetPark(); + gParkRating = park->CalculateParkRating(); + gParkValue = park->CalculateParkValue(); + gCompanyValue = park->CalculateCompanyValue(); gHistoricalProfit = gInitialCash - gBankLoan; gCash = gInitialCash; @@ -169,7 +172,7 @@ void scenario_begin() gTotalAdmissions = 0; gTotalIncomeFromAdmissions = 0; safe_strcpy(gScenarioCompletedBy, "?", sizeof(gScenarioCompletedBy)); - park_reset_history(); + park->ResetHistories(); finance_reset_history(); award_reset(); reset_all_ride_build_dates(); @@ -250,8 +253,8 @@ void scenario_success_submit_name(const char *name) static void scenario_entrance_fee_too_high_check() { uint16 x = 0, y = 0; - money16 totalRideValue = gTotalRideValueForMoney; - money16 max_fee = totalRideValue + (totalRideValue / 2); + money16 totalRideValueForMoney = gTotalRideValueForMoney; + money16 max_fee = totalRideValueForMoney + (totalRideValueForMoney / 2); if ((gParkFlags & PARK_FLAGS_PARK_OPEN) && park_get_entrance_fee() > max_fee) { for (sint32 i = 0; i < MAX_PARK_ENTRANCES && gParkEntrances[i].x != LOCATION_NULL; i++) { @@ -345,8 +348,6 @@ static void scenario_week_update() break; } } - park_update_histories(); - park_calculate_size(); } static void scenario_fortnight_update() diff --git a/src/openrct2/world/Park.cpp b/src/openrct2/world/Park.cpp index db33468581..8ad736ef7e 100644 --- a/src/openrct2/world/Park.cpp +++ b/src/openrct2/world/Park.cpp @@ -15,6 +15,7 @@ #pragma endregion #include "../Cheats.h" +#include "../Context.h" #include "../config/Config.h" #include "../core/Math.hpp" #include "../core/Memory.hpp" @@ -36,13 +37,14 @@ #include "../ride/RideData.h" #include "../ride/ShopItem.h" #include "../scenario/Scenario.h" +#include "../windows/Intent.h" #include "Entrance.h" #include "Map.h" #include "Park.h" #include "Sprite.h" #include "Surface.h" -#include "../windows/Intent.h" -#include "../Context.h" + +using namespace OpenRCT2; rct_string_id gParkName; uint32 gParkNameArgs; @@ -79,282 +81,6 @@ sint32 _suggestedGuestMaximum; */ sint32 _guestGenerationProbability; -sint32 park_is_open() -{ - return (gParkFlags & PARK_FLAGS_PARK_OPEN) != 0; -} - -/** - * - * rct2: 0x00667132 - */ -void park_init() -{ - sint32 i; - - gUnk13CA740 = 0; - gParkName = STR_UNNAMED_PARK; - gStaffHandymanColour = COLOUR_BRIGHT_RED; - gStaffMechanicColour = COLOUR_LIGHT_BLUE; - gStaffSecurityColour = COLOUR_YELLOW; - gNumGuestsInPark = 0; - gNumGuestsInParkLastWeek = 0; - gNumGuestsHeadingForPark = 0; - gGuestChangeModifier = 0; - gParkRating = 0; - _guestGenerationProbability = 0; - gTotalRideValueForMoney = 0; - gResearchLastItem.rawValue = RESEARCHED_ITEMS_SEPARATOR; - - for (i = 0; i < 20; i++) - gMarketingCampaignDaysLeft[i] = 0; - - research_reset_items(); - finance_init(); - - set_every_ride_type_not_invented(); - - set_all_scenery_items_invented(); - - gParkEntranceFee = MONEY(10, 00); - - for (auto &peepSpawn : gPeepSpawns) - { - peepSpawn.x = PEEP_SPAWN_UNDEFINED; - } - - gResearchPriorities = - (1 << RESEARCH_CATEGORY_TRANSPORT) | - (1 << RESEARCH_CATEGORY_GENTLE) | - (1 << RESEARCH_CATEGORY_ROLLERCOASTER) | - (1 << RESEARCH_CATEGORY_THRILL) | - (1 << RESEARCH_CATEGORY_WATER) | - (1 << RESEARCH_CATEGORY_SHOP) | - (1 << RESEARCH_CATEGORY_SCENERY_GROUP); - gResearchFundingLevel = RESEARCH_FUNDING_NORMAL; - - gGuestInitialCash = MONEY(50,00); // Cash per guest (average) - gGuestInitialHappiness = calculate_guest_initial_happiness(50); // 50% - gGuestInitialHunger = 200; - gGuestInitialThirst = 200; - gScenarioObjectiveType = OBJECTIVE_GUESTS_BY; - gScenarioObjectiveYear = 4; - gScenarioObjectiveNumGuests = 1000; - gLandPrice = MONEY(90, 00); - gConstructionRightsPrice = MONEY(40,00); - gParkFlags = PARK_FLAGS_NO_MONEY | PARK_FLAGS_SHOW_REAL_GUEST_NAMES; - park_reset_history(); - finance_reset_history(); - award_reset(); - - gS6Info.name[0] = '\0'; - format_string(gS6Info.details, 256, STR_NO_DETAILS_YET, nullptr); -} - -/** - * - * rct2: 0x0066729F - */ -void park_reset_history() -{ - for (sint32 i = 0; i < 32; i++) { - gParkRatingHistory[i] = 255; - gGuestsInParkHistory[i] = 255; - } -} - -/** - * - * rct2: 0x0066A348 - */ -sint32 park_calculate_size() -{ - sint32 tiles; - tile_element_iterator it; - - tiles = 0; - tile_element_iterator_begin(&it); - do { - if (it.element->GetType() == TILE_ELEMENT_TYPE_SURFACE) { - if (it.element->properties.surface.ownership & (OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED | OWNERSHIP_OWNED)) { - tiles++; - } - } - } while (tile_element_iterator_next(&it)); - - if (tiles != gParkSize) { - gParkSize = tiles; - window_invalidate_by_class(WC_PARK_INFORMATION); - } - - return tiles; -} - -/** - * - * rct2: 0x00669EAA - */ -sint32 calculate_park_rating() -{ - if (_forcedParkRating >= 0) - return _forcedParkRating; - - sint32 result; - - result = 1150; - if (gParkFlags & PARK_FLAGS_DIFFICULT_PARK_RATING) - result = 1050; - - // Guests - { - rct_peep* peep; - uint16 spriteIndex; - sint32 num_happy_peeps; - sint32 num_lost_guests; - - // -150 to +3 based on a range of guests from 0 to 2000 - result -= 150 - (Math::Min((uint16)2000, gNumGuestsInPark) / 13); - - // Find the number of happy peeps and the number of peeps who can't find the park exit - num_happy_peeps = 0; - num_lost_guests = 0; - FOR_ALL_GUESTS(spriteIndex, peep) { - if (peep->outside_of_park != 0) - continue; - if (peep->happiness > 128) - num_happy_peeps++; - if ((peep->peep_flags & PEEP_FLAGS_LEAVING_PARK) && (peep->peep_is_lost_countdown < 90)) - num_lost_guests++; - } - - // Peep happiness -500 to +0 - result -= 500; - - if (gNumGuestsInPark > 0) - result += 2 * Math::Min(250, (num_happy_peeps * 300) / gNumGuestsInPark); - - // Up to 25 guests can be lost without affecting the park rating. - if (num_lost_guests > 25) - result -= (num_lost_guests - 25) * 7; - } - - // Rides - { - sint32 i; - sint32 total_ride_uptime = 0, total_ride_intensity = 0, total_ride_excitement = 0; - sint32 num_rides, num_exciting_rides = 0; - Ride* ride; - - num_rides = 0; - FOR_ALL_RIDES(i, ride) - { - total_ride_uptime += 100 - ride->downtime; - - if (ride->excitement != RIDE_RATING_UNDEFINED) - { - total_ride_excitement += ride->excitement / 8; - total_ride_intensity += ride->intensity / 8; - num_exciting_rides++; - } - num_rides++; - } - result -= 200; - if (num_rides > 0) - result += (total_ride_uptime / num_rides) * 2; - - result -= 100; - - if (num_exciting_rides > 0) - { - sint32 average_excitement = total_ride_excitement / num_exciting_rides; - sint32 average_intensity = total_ride_intensity / num_exciting_rides; - - average_excitement -= 46; - if (average_excitement < 0) - { - average_excitement = -average_excitement; - } - - average_intensity -= 65; - if (average_intensity < 0) - { - average_intensity = -average_intensity; - } - - average_excitement = Math::Min((average_excitement / 2), 50); - average_intensity = Math::Min((average_intensity / 2), 50); - result += 100 - average_excitement - average_intensity; - } - - total_ride_excitement = Math::Min(1000, total_ride_excitement); - total_ride_intensity = Math::Min(1000, total_ride_intensity); - result -= 200 - ((total_ride_excitement + total_ride_intensity) / 10); - } - - // Litter - { - rct_litter* litter; - uint16 sprite_idx; - sint16 num_litter; - - num_litter = 0; - for (sprite_idx = gSpriteListHead[SPRITE_LIST_LITTER]; sprite_idx != SPRITE_INDEX_NULL; sprite_idx = litter->next) { - litter = &(get_sprite(sprite_idx)->litter); - - // Ignore recently dropped litter - if (litter->creationTick - gScenarioTicks >= 7680) - num_litter++; - } - result -= 600 - (4 * (150 - Math::Min((sint16)150, num_litter))); - } - - result -= gParkRatingCasualtyPenalty; - result = Math::Clamp(0, result, 999); - return result; -} - -static money32 calculate_ride_value(Ride *ride) -{ - if (ride->type == RIDE_TYPE_NULL) - return 0; - if (ride->value == RIDE_VALUE_UNDEFINED) - return 0; - - // Fair value * (...) - return (ride->value * 10) * (ride_customers_in_last_5_minutes(ride) + rideBonusValue[ride->type] * 4); -} - -/** - * - * rct2: 0x0066A3F6 - */ -money32 calculate_park_value() -{ - - // Sum ride values - money32 result = 0; - for (sint32 i = 0; i < 255; i++) { - Ride* ride = get_ride(i); - result += calculate_ride_value(ride); - } - - // +7.00 per guest - result += gNumGuestsInPark * MONEY(7, 00); - - return result; -} - -/** - * Calculate the company value. - * Cash + Park Value - Loan - * - * rct2: 0x0066A498 - */ -money32 calculate_company_value() -{ - return gCash + gParkValue - gBankLoan; -} - /** * * rct2: 0x00667104 @@ -368,115 +94,6 @@ void reset_park_entry() } } -/** - * Calculates the probability of a new guest. Also sets total ride value and suggested guest maximum. - * Total ride value should probably be set elsewhere, as it's not just used for guest generation. - * Suggested guest maximum should probably be an output result, not a global. - * @returns A probability out of 65535 - * rct2: 0x0066730A - */ -static sint32 park_calculate_guest_generation_probability() -{ - uint32 probability; - sint32 i, suggestedMaxGuests; - money16 totalRideValueForMoney; - Ride *ride; - - // Calculate suggested guest maximum (based on ride type) and total ride value - suggestedMaxGuests = 0; - totalRideValueForMoney = 0; - FOR_ALL_RIDES(i, ride) { - if (ride->status != RIDE_STATUS_OPEN) - continue; - if (ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN) - continue; - if (ride->lifecycle_flags & RIDE_LIFECYCLE_CRASHED) - continue; - - // Add guest score for ride type - suggestedMaxGuests += rideBonusValue[ride->type]; - - // Add ride value - if (ride->value != RIDE_VALUE_UNDEFINED) { - money16 rideValueForMoney = (money16)(ride->value - ride->price); - if (rideValueForMoney > 0) { - totalRideValueForMoney += rideValueForMoney * 2; - } - } - } - - // If difficult guest generation, extra guests are available for good rides - if (gParkFlags & PARK_FLAGS_DIFFICULT_GUEST_GENERATION) { - suggestedMaxGuests = Math::Min(suggestedMaxGuests, 1000); - FOR_ALL_RIDES(i, ride) { - if (ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN) - continue; - if (ride->lifecycle_flags & RIDE_LIFECYCLE_CRASHED) - continue; - - if (!ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_HAS_TRACK)) - continue; - if (!ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_HAS_DATA_LOGGING)) - continue; - if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_TESTED)) - continue; - if (ride->length[0] < (600 << 16)) - continue; - if (ride->excitement < RIDE_RATING(6,00)) - continue; - - // Bonus guests for good ride - suggestedMaxGuests += rideBonusValue[ride->type] * 2; - } - } - - suggestedMaxGuests = Math::Min(suggestedMaxGuests, 65535); - gTotalRideValueForMoney = totalRideValueForMoney; - _suggestedGuestMaximum = suggestedMaxGuests; - - // Begin with 50 + park rating - probability = 50 + Math::Clamp(0u, gParkRating - 200u, 650u); - - // The more guests, the lower the chance of a new one - sint32 numGuests = gNumGuestsInPark + gNumGuestsHeadingForPark; - if (numGuests > suggestedMaxGuests) { - probability /= 4; - - // Even lower for difficult guest generation - if (gParkFlags & PARK_FLAGS_DIFFICULT_GUEST_GENERATION) - probability /= 4; - } - - // Reduces chance for any more than 7000 guests - if (numGuests > 7000) - probability /= 4; - - // Penalty for overpriced entrance fee relative to total ride value - money16 entranceFee = park_get_entrance_fee(); - if (entranceFee > totalRideValueForMoney) { - probability /= 4; - - // Extra penalty for very overpriced entrance fee - if (entranceFee / 2 > totalRideValueForMoney) - probability /= 4; - } - - // Reward or penalties for park awards - for (i = 0; i < MAX_AWARDS; i++) { - Award *award = &gCurrentAwards[i]; - if (award->Time == 0) - continue; - - // +/- 0.25% of the probability - if (award_is_positive(award->Type)) - probability += probability / 4; - else - probability -= probability / 4; - } - - return probability; -} - /** * Choose a random peep spawn and iterates through until defined spawn is found. */ @@ -492,166 +109,6 @@ static uint32 get_random_peep_spawn_index() } } -rct_peep *park_generate_new_guest() -{ - rct_peep *peep = nullptr; - PeepSpawn spawn = gPeepSpawns[get_random_peep_spawn_index()]; - - if (spawn.x != 0xFFFF) { - spawn.direction ^= 2; - peep = peep_generate(spawn.x, spawn.y, spawn.z); - if (peep != nullptr) { - peep->sprite_direction = spawn.direction << 3; - - // Get the centre point of the tile the peep is on - peep->destination_x = (peep->x & 0xFFE0) + 16; - peep->destination_y = (peep->y & 0xFFE0) + 16; - - peep->destination_tolerance = 5; - peep->direction = spawn.direction; - peep->var_37 = 0; - peep->state = PEEP_STATE_ENTERING_PARK; - } - } - - return peep; -} - -static rct_peep *park_generate_new_guest_due_to_campaign(sint32 campaign) -{ - rct_peep *peep = park_generate_new_guest(); - if (peep != nullptr) - marketing_set_guest_campaign(peep, campaign); - return peep; -} - -static void park_generate_new_guests() -{ - // Generate a new guest for some probability - if ((sint32)(scenario_rand() & 0xFFFF) < _guestGenerationProbability) { - sint32 difficultGeneration = (gParkFlags & PARK_FLAGS_DIFFICULT_GUEST_GENERATION) != 0; - if (!difficultGeneration || _suggestedGuestMaximum + 150 >= gNumGuestsInPark) - park_generate_new_guest(); - } - - // Extra guests generated by advertising campaigns - sint32 campaign; - for (campaign = 0; campaign < ADVERTISING_CAMPAIGN_COUNT; campaign++) { - if (gMarketingCampaignDaysLeft[campaign] != 0) { - // Random chance of guest generation - if ((sint32)(scenario_rand() & 0xFFFF) < marketing_get_campaign_guest_generation_probability(campaign)) - park_generate_new_guest_due_to_campaign(campaign); - } - } -} - -/** - * - * rct2: 0x006674F7 - */ -void park_update() -{ - if (gScreenFlags & (SCREEN_FLAGS_SCENARIO_EDITOR | SCREEN_FLAGS_TRACK_DESIGNER | SCREEN_FLAGS_TRACK_MANAGER)) - return; - - // Every 5 seconds approximately - if (gCurrentTicks % 512 == 0) { - gParkRating = calculate_park_rating(); - gParkValue = calculate_park_value(); - gCompanyValue = calculate_company_value(); - window_invalidate_by_class(WC_FINANCES); - _guestGenerationProbability = park_calculate_guest_generation_probability(); - auto intent = Intent(INTENT_ACTION_UPDATE_PARK_RATING); - context_broadcast_intent(&intent); - } - - // Generate new guests - park_generate_new_guests(); -} - -uint8 calculate_guest_initial_happiness(uint8 percentage) { - if (percentage < 15) { - // There is a minimum of 15% happiness - percentage = 15; - } - else if (percentage > 98) { - // There is a maximum of 98% happiness - percentage = 98; - } - - /* The percentages follow this sequence: - 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... - - This sequence can be defined as PI*(9+n)/2 (the value is floored) - */ - uint8 n; - for (n = 1; n < 55; n++) { - if ((3.14159*(9 + n)) / 2 >= percentage) { - return (9 + n) * 4; - } - } - return 40; // This is the lowest possible value -} - -/** - * - * rct2: 0x0066A231 - */ -void park_update_histories() -{ - sint32 guestsInPark = gNumGuestsInPark; - sint32 lastGuestsInPark = gNumGuestsInParkLastWeek; - gNumGuestsInParkLastWeek = guestsInPark; - auto intent = Intent(INTENT_ACTION_UPDATE_GUEST_COUNT); - context_broadcast_intent(&intent); - - sint32 changeInGuestsInPark = guestsInPark - lastGuestsInPark; - sint32 guestChangeModifier = 1; - if (changeInGuestsInPark > -20) { - guestChangeModifier++; - if (changeInGuestsInPark < 20) - guestChangeModifier = 0; - } - gGuestChangeModifier = guestChangeModifier; - - // Update park rating history - for (sint32 i = 31; i > 0; i--) - gParkRatingHistory[i] = gParkRatingHistory[i - 1]; - gParkRatingHistory[0] = calculate_park_rating() / 4; - window_invalidate_by_class(WC_PARK_INFORMATION); - - // Update guests in park history - for (sint32 i = 31; i > 0; i--) - gGuestsInParkHistory[i] = gGuestsInParkHistory[i - 1]; - gGuestsInParkHistory[0] = Math::Min(guestsInPark, 5000) / 20; - window_invalidate_by_class(WC_PARK_INFORMATION); - - // Update current cash history - for (sint32 i = 127; i > 0; i--) - gCashHistory[i] = gCashHistory[i - 1]; - gCashHistory[0] = gCash - gBankLoan; - window_invalidate_by_class(WC_FINANCES); - - // Update weekly profit history - money32 currentWeeklyProfit = gWeeklyProfitAverageDividend; - if (gWeeklyProfitAverageDivisor != 0) { - currentWeeklyProfit /= gWeeklyProfitAverageDivisor; - } - - for (sint32 i = 127; i > 0; i--) - gWeeklyProfitHistory[i] = gWeeklyProfitHistory[i - 1]; - gWeeklyProfitHistory[0] = currentWeeklyProfit; - - gWeeklyProfitAverageDividend = 0; - gWeeklyProfitAverageDivisor = 0; - window_invalidate_by_class(WC_FINANCES); - - // Update park value history - for (sint32 i = 127; i > 0; i--) - gParkValueHistory[i] = gParkValueHistory[i - 1]; - gParkValueHistory[0] = gParkValue; -} - void park_set_open(sint32 open) { game_do_command(0, GAME_COMMAND_FLAG_APPLY, 0, open << 8, GAME_COMMAND_SET_PARK_OPEN, 0, 0); @@ -984,11 +441,11 @@ void game_command_buy_land_rights( } } - void set_forced_park_rating(sint32 rating) { _forcedParkRating = rating; - gParkRating = calculate_park_rating(); + auto park = GetContext()->GetPark(); + gParkRating = park->CalculateParkRating(); auto intent = Intent(INTENT_ACTION_UPDATE_PARK_RATING); context_broadcast_intent(&intent); } @@ -1036,3 +493,592 @@ bool park_entry_price_unlocked() } return false; } + +bool Park::IsOpen() const +{ + return (gParkFlags & PARK_FLAGS_PARK_OPEN) != 0; +} + +uint16 Park::GetParkRating() const +{ + return gParkRating; +} + +money32 Park::GetParkValue() const +{ + return gParkValue; +} + +money32 Park::GetCompanyValue() const +{ + return gCompanyValue; +} + +void Park::Initialise() +{ + gUnk13CA740 = 0; + gParkName = STR_UNNAMED_PARK; + gStaffHandymanColour = COLOUR_BRIGHT_RED; + gStaffMechanicColour = COLOUR_LIGHT_BLUE; + gStaffSecurityColour = COLOUR_YELLOW; + gNumGuestsInPark = 0; + gNumGuestsInParkLastWeek = 0; + gNumGuestsHeadingForPark = 0; + gGuestChangeModifier = 0; + gParkRating = 0; + _guestGenerationProbability = 0; + gTotalRideValueForMoney = 0; + gResearchLastItem.rawValue = RESEARCHED_ITEMS_SEPARATOR; + + for (size_t i = 0; i < 20; i++) + { + gMarketingCampaignDaysLeft[i] = 0; + } + + research_reset_items(); + finance_init(); + + set_every_ride_type_not_invented(); + + set_all_scenery_items_invented(); + + gParkEntranceFee = MONEY(10, 00); + + for (auto &peepSpawn : gPeepSpawns) + { + peepSpawn.x = PEEP_SPAWN_UNDEFINED; + } + + gResearchPriorities = + (1 << RESEARCH_CATEGORY_TRANSPORT) | + (1 << RESEARCH_CATEGORY_GENTLE) | + (1 << RESEARCH_CATEGORY_ROLLERCOASTER) | + (1 << RESEARCH_CATEGORY_THRILL) | + (1 << RESEARCH_CATEGORY_WATER) | + (1 << RESEARCH_CATEGORY_SHOP) | + (1 << RESEARCH_CATEGORY_SCENERY_GROUP); + gResearchFundingLevel = RESEARCH_FUNDING_NORMAL; + + gGuestInitialCash = MONEY(50,00); + gGuestInitialHappiness = CalculateGuestInitialHappiness(50); + gGuestInitialHunger = 200; + gGuestInitialThirst = 200; + gScenarioObjectiveType = OBJECTIVE_GUESTS_BY; + gScenarioObjectiveYear = 4; + gScenarioObjectiveNumGuests = 1000; + gLandPrice = MONEY(90,00); + gConstructionRightsPrice = MONEY(40,00); + gParkFlags = PARK_FLAGS_NO_MONEY | PARK_FLAGS_SHOW_REAL_GUEST_NAMES; + ResetHistories(); + finance_reset_history(); + award_reset(); + + gS6Info.name[0] = '\0'; + format_string(gS6Info.details, 256, STR_NO_DETAILS_YET, nullptr); +} + +void Park::Update() +{ + // Every 5 seconds approximately + if (gCurrentTicks % 512 == 0) + { + gParkRating = CalculateParkRating(); + gParkValue = CalculateParkValue(); + gCompanyValue = CalculateCompanyValue(); + gTotalRideValueForMoney = CalculateTotalRideValueForMoney(); + _suggestedGuestMaximum = CalculateSuggestedMaxGuests(); + _guestGenerationProbability = CalculateGuestGenerationProbability(); + + window_invalidate_by_class(WC_FINANCES); + auto intent = Intent(INTENT_ACTION_UPDATE_PARK_RATING); + context_broadcast_intent(&intent); + } + + // Every week + if (date_is_week_start(gDateMonthTicks)) + { + UpdateHistories(); + gParkSize = CalculateParkSize(); + window_invalidate_by_class(WC_PARK_INFORMATION); + } + + GenerateGuests(); +} + +sint32 Park::CalculateParkSize() const +{ + sint32 tiles; + tile_element_iterator it; + + tiles = 0; + tile_element_iterator_begin(&it); + do { + if (it.element->GetType() == TILE_ELEMENT_TYPE_SURFACE) + { + if (it.element->properties.surface.ownership & (OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED | OWNERSHIP_OWNED)) + { + tiles++; + } + } + } while (tile_element_iterator_next(&it)); + + if (tiles != gParkSize) { + gParkSize = tiles; + window_invalidate_by_class(WC_PARK_INFORMATION); + } + + return tiles; +} + +sint32 Park::CalculateParkRating() const +{ + if (_forcedParkRating >= 0) + { + return _forcedParkRating; + } + + sint32 result = 1150; + if (gParkFlags & PARK_FLAGS_DIFFICULT_PARK_RATING) + { + result = 1050; + } + + // Guests + { + // -150 to +3 based on a range of guests from 0 to 2000 + result -= 150 - (std::min(2000, gNumGuestsInPark) / 13); + + // Find the number of happy peeps and the number of peeps who can't find the park exit + sint32 happyGuestCount = 0; + sint32 lostGuestCount = 0; + uint16 spriteIndex; + rct_peep * peep; + FOR_ALL_GUESTS(spriteIndex, peep) + { + if (peep->outside_of_park == 0) + { + if (peep->happiness > 128) + { + happyGuestCount++; + } + if ((peep->peep_flags & PEEP_FLAGS_LEAVING_PARK) && + (peep->peep_is_lost_countdown < 90)) + { + lostGuestCount++; + } + } + } + + // Peep happiness -500 to +0 + result -= 500; + if (gNumGuestsInPark > 0) + { + result += 2 * std::min(250, (happyGuestCount * 300) / gNumGuestsInPark); + } + + // Up to 25 guests can be lost without affecting the park rating. + if (lostGuestCount > 25) + { + result -= (lostGuestCount - 25) * 7; + } + } + + // Rides + { + sint32 rideCount = 0; + sint32 excitingRideCount = 0; + sint32 totalRideUptime = 0; + sint32 totalRideIntensity = 0; + sint32 totalRideExcitement = 0; + + sint32 i; + Ride * ride; + FOR_ALL_RIDES(i, ride) + { + totalRideUptime += 100 - ride->downtime; + if (ride->excitement != RIDE_RATING_UNDEFINED) + { + totalRideExcitement += ride->excitement / 8; + totalRideIntensity += ride->intensity / 8; + excitingRideCount++; + } + rideCount++; + } + result -= 200; + if (rideCount > 0) + { + result += (totalRideUptime / rideCount) * 2; + } + result -= 100; + if (excitingRideCount > 0) + { + sint32 averageExcitement = totalRideExcitement / excitingRideCount; + sint32 averageIntensity = totalRideIntensity / excitingRideCount; + + averageExcitement -= 46; + if (averageExcitement < 0) + { + averageExcitement = -averageExcitement; + } + + averageIntensity -= 65; + if (averageIntensity < 0) + { + averageIntensity = -averageIntensity; + } + + averageExcitement = std::min(averageExcitement / 2, 50); + averageIntensity = std::min(averageIntensity / 2, 50); + result += 100 - averageExcitement - averageIntensity; + } + + totalRideExcitement = std::min(1000, totalRideExcitement); + totalRideIntensity = std::min(1000, totalRideIntensity); + result -= 200 - ((totalRideExcitement + totalRideIntensity) / 10); + } + + // Litter + { + rct_litter * litter; + sint32 litterCount = 0; + for (uint16 spriteIndex = gSpriteListHead[SPRITE_LIST_LITTER]; spriteIndex != SPRITE_INDEX_NULL; spriteIndex = litter->next) + { + litter = &(get_sprite(spriteIndex)->litter); + + // Ignore recently dropped litter + if (litter->creationTick - gScenarioTicks >= 7680) + { + litterCount++; + } + } + result -= 600 - (4 * (150 - std::min(150, litterCount))); + } + + result -= gParkRatingCasualtyPenalty; + result = Math::Clamp(0, result, 999); + return result; +} + +money32 Park::CalculateParkValue() const +{ + money32 result = 0; + + // Sum ride values + for (sint32 i = 0; i < MAX_RIDES; i++) + { + auto ride = get_ride(i); + result += CalculateRideValue(ride); + } + + // +7.00 per guest + result += gNumGuestsInPark * MONEY(7, 00); + + return result; +} + +money32 Park::CalculateRideValue(const Ride * ride) const +{ + money32 result = 0; + if (ride->type != RIDE_TYPE_NULL && + ride->value != RIDE_VALUE_UNDEFINED) + { + result = (ride->value * 10) * (ride_customers_in_last_5_minutes(ride) + rideBonusValue[ride->type] * 4); + } + return result; +} + +money32 Park::CalculateCompanyValue() const +{ + return finance_get_current_cash() + gParkValue - gBankLoan; +} + +money16 Park::CalculateTotalRideValueForMoney() const +{ + money16 totalRideValue = 0; + sint32 i; + Ride * ride; + FOR_ALL_RIDES(i, ride) + { + if (ride->status != RIDE_STATUS_OPEN) continue; + if (ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN) continue; + if (ride->lifecycle_flags & RIDE_LIFECYCLE_CRASHED) continue; + + // Add ride value + if (ride->value != RIDE_VALUE_UNDEFINED) + { + money16 rideValue = (money16)(ride->value - ride->price); + if (rideValue > 0) + { + totalRideValue += rideValue * 2; + } + } + } + return totalRideValue; +} + +uint32 Park::CalculateSuggestedMaxGuests() const +{ + uint32 suggestedMaxGuests = 0; + + // TODO combine the two ride loops + sint32 i; + Ride * ride; + FOR_ALL_RIDES(i, ride) + { + if (ride->status != RIDE_STATUS_OPEN) continue; + if (ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN) continue; + if (ride->lifecycle_flags & RIDE_LIFECYCLE_CRASHED) continue; + + // Add guest score for ride type + suggestedMaxGuests += rideBonusValue[ride->type]; + } + + // If difficult guest generation, extra guests are available for good rides + if (gParkFlags & PARK_FLAGS_DIFFICULT_GUEST_GENERATION) + { + suggestedMaxGuests = std::min(suggestedMaxGuests, 1000); + FOR_ALL_RIDES(i, ride) + { + if (ride->lifecycle_flags & RIDE_LIFECYCLE_CRASHED) continue; + if (ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN) continue; + if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_TESTED)) continue; + if (!ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_HAS_TRACK)) continue; + if (!ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_HAS_DATA_LOGGING)) continue; + if (ride->length[0] < (600 << 16)) continue; + if (ride->excitement < RIDE_RATING(6, 00)) continue; + + // Bonus guests for good ride + suggestedMaxGuests += rideBonusValue[ride->type] * 2; + } + } + + suggestedMaxGuests = std::min(suggestedMaxGuests, 65535); + return suggestedMaxGuests; +} + +uint32 Park::CalculateGuestGenerationProbability() const +{ + // Begin with 50 + park rating + uint32 probability = 50 + Math::Clamp(0, gParkRating - 200, 650); + + // The more guests, the lower the chance of a new one + sint32 numGuests = gNumGuestsInPark + gNumGuestsHeadingForPark; + if (numGuests > _suggestedGuestMaximum) + { + probability /= 4; + // Even lower for difficult guest generation + if (gParkFlags & PARK_FLAGS_DIFFICULT_GUEST_GENERATION) + { + probability /= 4; + } + } + + // Reduces chance for any more than 7000 guests + if (numGuests > 7000) + { + probability /= 4; + } + + // Penalty for overpriced entrance fee relative to total ride value + money16 entranceFee = park_get_entrance_fee(); + if (entranceFee > gTotalRideValueForMoney) + { + probability /= 4; + // Extra penalty for very overpriced entrance fee + if (entranceFee / 2 > gTotalRideValueForMoney) + { + probability /= 4; + } + } + + // Reward or penalties for park awards + for (size_t i = 0; i < MAX_AWARDS; i++) + { + const auto award = &gCurrentAwards[i]; + if (award->Time != 0) + { + // +/- 0.25% of the probability + if (award_is_positive(award->Type)) + { + probability += probability / 4; + } + else + { + probability -= probability / 4; + } + } + } + + return probability; +} + +uint8 Park::CalculateGuestInitialHappiness(uint8 percentage) +{ + percentage = Math::Clamp(15, percentage, 98); + + // The percentages follow this sequence: + // 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... + // This sequence can be defined as PI*(9+n)/2 (the value is floored) + for (uint8 n = 1; n < 55; n++) + { + if ((3.14159 * (9 + n)) / 2 >= percentage) + { + return (9 + n) * 4; + } + } + + // This is the lowest possible value: + return 40; +} + +void Park::GenerateGuests() +{ + // Generate a new guest for some probability + if ((sint32)(scenario_rand() & 0xFFFF) < _guestGenerationProbability) + { + bool difficultGeneration = (gParkFlags & PARK_FLAGS_DIFFICULT_GUEST_GENERATION) != 0; + if (!difficultGeneration || _suggestedGuestMaximum + 150 >= gNumGuestsInPark) + { + GenerateGuest(); + } + } + + // Extra guests generated by advertising campaigns + for (sint32 campaign = 0; campaign < ADVERTISING_CAMPAIGN_COUNT; campaign++) + { + if (gMarketingCampaignDaysLeft[campaign] != 0) + { + // Random chance of guest generation + if ((sint32)(scenario_rand() & 0xFFFF) < marketing_get_campaign_guest_generation_probability(campaign)) + { + GenerateGuestFromCampaign(campaign); + } + } + } +} + +rct_peep * Park::GenerateGuestFromCampaign(sint32 campaign) +{ + auto peep = GenerateGuest(); + if (peep != nullptr) + { + marketing_set_guest_campaign(peep, campaign); + } + return peep; +} + +rct_peep * Park::GenerateGuest() +{ + rct_peep * peep = nullptr; + PeepSpawn spawn = gPeepSpawns[get_random_peep_spawn_index()]; + + if (spawn.x != PEEP_SPAWN_UNDEFINED) + { + spawn.direction ^= 2; + peep = peep_generate(spawn.x, spawn.y, spawn.z); + if (peep != nullptr) + { + peep->sprite_direction = spawn.direction << 3; + + // Get the centre point of the tile the peep is on + peep->destination_x = (peep->x & 0xFFE0) + 16; + peep->destination_y = (peep->y & 0xFFE0) + 16; + + peep->destination_tolerance = 5; + peep->direction = spawn.direction; + peep->var_37 = 0; + peep->state = PEEP_STATE_ENTERING_PARK; + } + } + return peep; +} + +template +static void HistoryPushRecord(T history[TSize], T newItem) +{ + for (size_t i = TSize - 1; i > 0; i--) + { + history[i] = history[i - 1]; + } + history[0] = newItem; +} + +void Park::ResetHistories() +{ + for (size_t i = 0; i < 32; i++) + { + gParkRatingHistory[i] = 255; + gGuestsInParkHistory[i] = 255; + } +} + +void Park::UpdateHistories() +{ + uint8 guestChangeModifier = 1; + sint32 changeInGuestsInPark = (sint32)gNumGuestsInPark - (sint32)gNumGuestsInParkLastWeek; + if (changeInGuestsInPark > -20) + { + guestChangeModifier++; + if (changeInGuestsInPark < 20) + { + guestChangeModifier = 0; + } + } + gGuestChangeModifier = guestChangeModifier; + gNumGuestsInParkLastWeek = gNumGuestsInPark; + + // Update park rating, guests in park and current cash history + HistoryPushRecord(gParkRatingHistory, CalculateParkRating() / 4); + HistoryPushRecord(gGuestsInParkHistory, std::min(gNumGuestsInPark, 5000) / 20); + HistoryPushRecord(gCashHistory, finance_get_current_cash() - gBankLoan); + + // Update weekly profit history + money32 currentWeeklyProfit = gWeeklyProfitAverageDividend; + if (gWeeklyProfitAverageDivisor != 0) + { + currentWeeklyProfit /= gWeeklyProfitAverageDivisor; + } + HistoryPushRecord(gWeeklyProfitHistory, currentWeeklyProfit); + gWeeklyProfitAverageDividend = 0; + gWeeklyProfitAverageDivisor = 0; + + // Update park value history + HistoryPushRecord(gParkValueHistory, gParkValue); + + // Invalidate relevant windows + auto intent = Intent(INTENT_ACTION_UPDATE_GUEST_COUNT); + context_broadcast_intent(&intent); + window_invalidate_by_class(WC_PARK_INFORMATION); + window_invalidate_by_class(WC_FINANCES); +} + +sint32 park_is_open() +{ + return GetContext()->GetPark()->IsOpen(); +} + +void park_init() +{ + auto park = GetContext()->GetPark(); + if (park == nullptr) + { + return; + } + park->Initialise(); +} + +sint32 park_calculate_size() +{ + auto tiles = GetContext()->GetPark()->CalculateParkSize(); + if (tiles != gParkSize) + { + gParkSize = tiles; + window_invalidate_by_class(WC_PARK_INFORMATION); + } + return tiles; +} + +uint8 calculate_guest_initial_happiness(uint8 percentage) +{ + return Park::CalculateGuestInitialHappiness(percentage); +} diff --git a/src/openrct2/world/Park.h b/src/openrct2/world/Park.h index c6a50c2e15..31a1462cd1 100644 --- a/src/openrct2/world/Park.h +++ b/src/openrct2/world/Park.h @@ -14,10 +14,11 @@ *****************************************************************************/ #pragma endregion -#ifndef _PARK_H_ -#define _PARK_H_ +#pragma once #include "../common.h" +#include "../ride/Ride.h" +#include "Map.h" #define DECRYPT_MONEY(money) ((money32)rol32((money) ^ 0xF4EC9621, 13)) #define ENCRYPT_MONEY(money) ((money32)(ror32((money), 13) ^ 0xF4EC9621)) @@ -49,6 +50,46 @@ enum : uint32 PARK_FLAGS_UNLOCK_ALL_PRICES = (1u << 31), // OpenRCT2 only! }; +struct rct_peep; +struct rct_ride; + +namespace OpenRCT2 +{ + class Park final + { + public: + bool IsOpen() const; + + uint16 GetParkRating() const; + money32 GetParkValue() const; + money32 GetCompanyValue() const; + + void Initialise(); + void Update(); + + sint32 CalculateParkSize() const; + sint32 CalculateParkRating() const; + money32 CalculateParkValue() const; + money32 CalculateCompanyValue() const; + static uint8 CalculateGuestInitialHappiness(uint8 percentage); + + rct_peep * GenerateGuest(); + + void ResetHistories(); + void UpdateHistories(); + + private: + money32 CalculateRideValue(const Ride * ride) const; + money16 CalculateTotalRideValueForMoney() const; + uint32 CalculateSuggestedMaxGuests() const; + uint32 CalculateGuestGenerationProbability() const; + + void GenerateGuests(); + rct_peep * GenerateGuestFromCampaign(sint32 campaign); + + }; +} + enum { BUY_LAND_RIGHTS_FLAG_BUY_LAND, @@ -86,17 +127,10 @@ sint32 get_forced_park_rating(); sint32 park_is_open(); void park_init(); -void park_reset_history(); sint32 park_calculate_size(); -sint32 calculate_park_rating(); -money32 calculate_park_value(); -money32 calculate_company_value(); void reset_park_entry(); -rct_peep * park_generate_new_guest(); -void park_update(); -void park_update_histories(); void update_park_fences(sint32 x, sint32 y); void update_park_fences_around_tile(sint32 x, sint32 y); @@ -117,5 +151,3 @@ money16 park_get_entrance_fee(); bool park_ride_prices_unlocked(); bool park_entry_price_unlocked(); - -#endif