diff --git a/distribution/changelog.txt b/distribution/changelog.txt index 5ab6921030..13d2797bfa 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -10,6 +10,7 @@ - Fix: [#6119] Advertising campaign for ride window not updated properly (original bug). - Fix: [#11072] Land and water tools working out of bounds (original bug). - Fix: [#11259] Custom JSON object breaks saves. +- Fix: [#11290] Perform funds checking for all peeps entering a ride. - Fix: [#11315] Ride that has never opened is shown as favorite ride of many guests. - Fix: [#11405] Building a path through walls does not always remove the walls. - Fix: RCT1 scenarios have more items in the object list than are present in the park or the research list. diff --git a/src/openrct2/network/Network.cpp b/src/openrct2/network/Network.cpp index 34467d2aa5..8ab46138ab 100644 --- a/src/openrct2/network/Network.cpp +++ b/src/openrct2/network/Network.cpp @@ -31,7 +31,7 @@ // This string specifies which version of network stream current build uses. // It is used for making sure only compatible builds get connected, even within // single OpenRCT2 version. -#define NETWORK_STREAM_VERSION "7" +#define NETWORK_STREAM_VERSION "8" #define NETWORK_STREAM_ID OPENRCT2_VERSION "-" NETWORK_STREAM_VERSION static Peep* _pickup_peep = nullptr; diff --git a/src/openrct2/peep/Guest.cpp b/src/openrct2/peep/Guest.cpp index 8b41de2138..d949f59c73 100644 --- a/src/openrct2/peep/Guest.cpp +++ b/src/openrct2/peep/Guest.cpp @@ -2598,14 +2598,13 @@ bool Guest::FindVehicleToEnter(Ride* ride, std::vector& car_array) uint8_t num_seats = vehicle->num_seats; if (vehicle_is_used_in_pairs(vehicle)) { - num_seats &= VEHICLE_SEAT_NUM_MASK; if (vehicle->next_free_seat & 1) { - current_car = i; - peep_choose_seat_from_car(this, ride, vehicle); - GoToRideEntrance(ride); - return false; + car_array.clear(); + car_array.push_back(i); + return true; } + num_seats &= VEHICLE_SEAT_NUM_MASK; } if (num_seats == vehicle->next_free_seat) continue; diff --git a/test/tests/CMakeLists.txt b/test/tests/CMakeLists.txt index edde30e990..894faaaae4 100644 --- a/test/tests/CMakeLists.txt +++ b/test/tests/CMakeLists.txt @@ -223,6 +223,15 @@ target_link_libraries(test_replays ${GTEST_LIBRARIES} libopenrct2 ${LDL} z) target_link_platform_libraries(test_replays) add_test(NAME replay_tests COMMAND test_replays) +# Play tests +set(PLAY_TEST_SOURCES "${CMAKE_CURRENT_LIST_DIR}/PlayTests.cpp" + "${CMAKE_CURRENT_LIST_DIR}/TestData.cpp") +add_executable(test_plays ${PLAY_TEST_SOURCES}) +SET_CHECK_CXX_FLAGS(test_plays) +target_link_libraries(test_plays ${GTEST_LIBRARIES} libopenrct2 ${LDL} z) +target_link_platform_libraries(test_plays) +add_test(NAME play_tests COMMAND test_plays) + # Pathfinding test set(PATHFINDING_TEST_SOURCES "${CMAKE_CURRENT_LIST_DIR}/Pathfinding.cpp" "${CMAKE_CURRENT_LIST_DIR}/TestData.cpp") diff --git a/test/tests/PlayTests.cpp b/test/tests/PlayTests.cpp new file mode 100644 index 0000000000..bf63ebf776 --- /dev/null +++ b/test/tests/PlayTests.cpp @@ -0,0 +1,195 @@ +/***************************************************************************** + * Copyright (c) 2014-2020 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 + +using namespace OpenRCT2; + +class PlayTests : public testing::Test +{ +}; + +static std::unique_ptr localStartGame(const std::string& parkPath) +{ + gOpenRCT2Headless = true; + gOpenRCT2NoGraphics = true; + core_init(); + + 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.data(), loadResult.RequiredObjects.size()); + importer->Import(); + + reset_sprite_spatial_index(); + + reset_all_sprite_quadrant_placements(); + scenery_set_default_placement_configuration(); + load_palette(); + map_reorganise_elements(); + sprite_position_tween_reset(); + AutoCreateMapAnimations(); + fix_invalid_vehicle_sprite_sizes(); + + gGameSpeed = 1; + + return context; +} + +template static bool updateUntil(GameState& gs, int maxSteps, Fn&& fn) +{ + while (maxSteps-- && !fn()) + { + gs.UpdateLogic(); + } + 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 gs = context->GetGameState(); + ASSERT_NE(gs, nullptr); + + // Open park for free but charging for rides + execute(ParkParameter::Open); + park_set_entrance_fee(0); + gParkFlags |= 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 + ride_set_status(&ferrisWheel, RIDE_STATUS_OPEN); + execute(ferrisWheel.id, 0, true); + + // Ignore intesity to stimulate peeps to queue into ferris wheel + gCheatsIgnoreRideIntensity = true; + + // Insert a rich guest + auto richGuest = gs->GetPark().GenerateGuest(); + richGuest->cash_in_pocket = 3000; + + // Wait for rich guest to get in queue + bool matched = updateUntil(*gs, 1000, [&]() { return richGuest->state == PEEP_STATE_QUEUING; }); + ASSERT_TRUE(matched); + + // Insert poor guest + auto poorGuest = gs->GetPark().GenerateGuest(); + poorGuest->cash_in_pocket = 5; + + // Wait for poor guest to get in queue + matched = updateUntil(*gs, 1000, [&]() { return poorGuest->state == PEEP_STATE_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(*gs, 10000, [&]() { + enteredTheRide |= poorGuest->state == PEEP_STATE_ON_RIDE; + return poorGuest->state == PEEP_STATE_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 gs = context->GetGameState(); + ASSERT_NE(gs, nullptr); + + // Open park for free but charging for rides + execute(ParkParameter::Open); + park_set_entrance_fee(0); + gParkFlags |= 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 + ride_set_status(&carRide, RIDE_STATUS_OPEN); + execute(carRide.id, 0, true); + + // Ignore intesity to stimulate peeps to queue into the ride + gCheatsIgnoreRideIntensity = true; + + // Create some guests + std::vector guests; + for (int i = 0; i < 25; i++) + { + guests.push_back(gs->GetPark().GenerateGuest()); + } + + // Wait until one of them is riding + auto guestIsOnRide = [](auto* g) { return g->state == PEEP_STATE_ON_RIDE; }; + bool matched = updateUntil(*gs, 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); + gs->UpdateLogic(); + } +} diff --git a/test/tests/testdata/parks/small_park_car_ride_one_car.sv6 b/test/tests/testdata/parks/small_park_car_ride_one_car.sv6 new file mode 100644 index 0000000000..bf3e24e43f Binary files /dev/null and b/test/tests/testdata/parks/small_park_car_ride_one_car.sv6 differ diff --git a/test/tests/testdata/parks/small_park_with_ferris_wheel.sv6 b/test/tests/testdata/parks/small_park_with_ferris_wheel.sv6 new file mode 100644 index 0000000000..d13816e90f Binary files /dev/null and b/test/tests/testdata/parks/small_park_with_ferris_wheel.sv6 differ diff --git a/test/tests/tests.vcxproj b/test/tests/tests.vcxproj index 46b9ec1f2c..75678eb34e 100644 --- a/test/tests/tests.vcxproj +++ b/test/tests/tests.vcxproj @@ -66,6 +66,7 @@ +