/***************************************************************************** * Copyright (c) 2014-2024 OpenRCT2 developers * * For a complete list of all authors, please refer to contributors.md * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 * * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ #include "TestData.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace OpenRCT2; class PlayTests : public testing::Test { }; static std::unique_ptr localStartGame(const std::string& parkPath) { gOpenRCT2Headless = true; gOpenRCT2NoGraphics = true; auto context = CreateContext(); if (!context->Initialise()) return {}; auto importer = ParkImporter::CreateS6(context->GetObjectRepository()); auto loadResult = importer->LoadSavedGame(parkPath.c_str(), false); context->GetObjectManager().LoadObjects(loadResult.RequiredObjects); // TODO: Have a separate GameState and exchange once loaded. auto& gameState = GetGameState(); importer->Import(gameState); ResetEntitySpatialIndices(); ResetAllSpriteQuadrantPlacements(); ScenerySetDefaultPlacementConfiguration(); LoadPalette(); EntityTweener::Get().Reset(); MapAnimationAutoCreate(); FixInvalidVehicleSpriteSizes(); gGameSpeed = 1; return context; } template static bool updateUntil(int maxSteps, Fn&& fn) { while (maxSteps-- && !fn()) { gameStateUpdateLogic(); } return maxSteps > 0; } template static void execute(Args&&... args) { GA ga(std::forward(args)...); GameActions::Execute(&ga); } TEST_F(PlayTests, SecondGuestInQueueShouldNotRideIfNoFunds) { /* This test verifies that a guest, when second in queue, won't be forced to enter * the ride if it has not enough money to pay for it. * To simulate this scenario, two guests (a rich and a poor) are encouraged to enter * the ride queue, and then the price is raised such that the second guest in line * (the poor one) cannot pay. The poor guest should not enter the ride. */ std::string initStateFile = TestData::GetParkPath("small_park_with_ferris_wheel.sv6"); auto context = localStartGame(initStateFile); ASSERT_NE(context.get(), nullptr); auto& gameState = GetGameState(); // Open park for free but charging for rides execute(ParkParameter::Open); execute(0); gameState.Park.Flags |= PARK_FLAGS_UNLOCK_ALL_PRICES; // Find ferris wheel auto rideManager = GetRideManager(); auto it = std::find_if( rideManager.begin(), rideManager.end(), [](auto& ride) { return ride.type == RIDE_TYPE_FERRIS_WHEEL; }); ASSERT_NE(it, rideManager.end()); Ride& ferrisWheel = *it; // Open it for free execute(ferrisWheel.id, RideStatus::Open); execute(ferrisWheel.id, 0, true); // Ignore intensity to stimulate peeps to queue into ferris wheel gameState.Cheats.IgnoreRideIntensity = true; // Insert a rich guest auto richGuest = Park::GenerateGuest(); richGuest->CashInPocket = 3000; // Wait for rich guest to get in queue bool matched = updateUntil(1000, [&]() { return richGuest->State == PeepState::Queuing; }); ASSERT_TRUE(matched); // Insert poor guest auto poorGuest = Park::GenerateGuest(); poorGuest->CashInPocket = 5; // Wait for poor guest to get in queue matched = updateUntil(1000, [&]() { return poorGuest->State == PeepState::Queuing; }); ASSERT_TRUE(matched); // Raise the price of the ride to a value poor guest can't pay execute(ferrisWheel.id, 10, true); // Verify that the poor guest goes back to walking without riding // since it doesn't have enough money to pay for it bool enteredTheRide = false; matched = updateUntil(10000, [&]() { enteredTheRide |= poorGuest->State == PeepState::OnRide; return poorGuest->State == PeepState::Walking || enteredTheRide; }); ASSERT_TRUE(matched); ASSERT_FALSE(enteredTheRide); } TEST_F(PlayTests, CarRideWithOneCarOnlyAcceptsTwoGuests) { // This test verifies that a car ride with one car will accept at most two guests std::string initStateFile = TestData::GetParkPath("small_park_car_ride_one_car.sv6"); auto context = localStartGame(initStateFile); ASSERT_NE(context.get(), nullptr); auto& gameState = GetGameState(); // Open park for free but charging for rides execute(ParkParameter::Open); execute(0); gameState.Park.Flags |= PARK_FLAGS_UNLOCK_ALL_PRICES; // Find car ride auto rideManager = GetRideManager(); auto it = std::find_if(rideManager.begin(), rideManager.end(), [](auto& ride) { return ride.type == RIDE_TYPE_CAR_RIDE; }); ASSERT_NE(it, rideManager.end()); Ride& carRide = *it; // Open it for free execute(carRide.id, RideStatus::Open); execute(carRide.id, 0, true); // Ignore intensity to stimulate peeps to queue into the ride gameState.Cheats.IgnoreRideIntensity = true; // Create some guests std::vector guests; for (int i = 0; i < 25; i++) { guests.push_back(Park::GenerateGuest()); } // Wait until one of them is riding auto guestIsOnRide = [](auto* g) { return g->State == PeepState::OnRide; }; bool matched = updateUntil(10000, [&]() { return std::any_of(guests.begin(), guests.end(), guestIsOnRide); }); ASSERT_TRUE(matched); // For the next few ticks at most two guests can be on the ride for (int i = 0; i < 100; i++) { int numRiding = std::count_if(guests.begin(), guests.end(), guestIsOnRide); ASSERT_LE(numRiding, 2); gameStateUpdateLogic(); } }