OpenRCT2/test/tests/PlayTests.cpp

199 lines
6.6 KiB
C++
Raw Normal View History

/*****************************************************************************
* 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 <gtest/gtest.h>
2022-07-29 18:45:10 +02:00
#include <memory>
#include <openrct2/Context.h>
#include <openrct2/Game.h>
#include <openrct2/GameState.h>
#include <openrct2/OpenRCT2.h>
#include <openrct2/ParkImporter.h>
#include <openrct2/actions/ParkSetEntranceFeeAction.h>
Split actions hpp files into separate h and cpp files (#13548) * Split up SmallSceneryPlace/Remove Added undo function for Remove Scenery * Refactor: Balloon and Banner actions hpp=>h/cpp * Refactor: rename all action *.hpp files to *.cpp This is preparation for separation in later commits. Note that without the complete set of commits in this branch, the code will not build. * Refactor Clear, Climate, Custom, and Footpath actions hpp=>h/cpp * VSCode: add src subdirectories to includePath * Refactor Guest actions hpp=>h/cpp * Refactor Land actions hpp=>h/cpp * Refactor LargeScenery actions hpp=>h/cpp * Refactor Load, Maze, Network actions hpp=>h/cpp * Refactor Park actions hpp=>h/cpp * Refactor/style: move private function declarations in actions *.h Previous action .h files included private function declarations with private member variables, before public function declarations. This commit re-orders the header files to the following order: - public member variables - private member variables - public functions - private functions * Refactor Pause action hpp=>h/cpp * Refactor Peep, Place, Player actions hpp=>h/cpp * Refactor Ride actions hpp=>h/cpp * Refactor Scenario, Set*, Sign* actions hpp=>h/cpp * Refactor SmallScenerySetColourAction hpp=>h/cpp * Refactor Staff actions hpp=>h/cpp * Refactor Surface, Tile, Track* actions hpp=>h/cpp * Refactor Wall and Water actions hpp=>h/cpp * Fix various includes and other compile errors Update includes for tests. Move static function declarations to .h files Add explicit includes to various files that were previously implicit (the required header was a nested include in an action hpp file, and the action .h file does not include that header) Move RideSetStatus string enum to the cpp file to avoid unused imports * Xcode: modify project file for actions refactor * Cleanup whitespace and end-of-file newlines Co-authored-by: duncanspumpkin <duncans_pumpkin@hotmail.co.uk>
2020-12-10 07:39:10 +01:00
#include <openrct2/actions/ParkSetParameterAction.h>
#include <openrct2/actions/RideSetPriceAction.h>
2022-08-11 00:00:58 +02:00
#include <openrct2/actions/RideSetStatusAction.h>
2021-11-24 15:58:01 +01:00
#include <openrct2/entity/EntityRegistry.h>
2021-11-24 15:48:33 +01:00
#include <openrct2/entity/EntityTweener.h>
2021-11-25 22:47:24 +01:00
#include <openrct2/entity/Peep.h>
#include <openrct2/object/ObjectManager.h>
#include <openrct2/platform/Platform.h>
#include <openrct2/ride/Ride.h>
#include <openrct2/world/MapAnimation.h>
#include <openrct2/world/Park.h>
#include <openrct2/world/Scenery.h>
#include <string>
using namespace OpenRCT2;
class PlayTests : public testing::Test
{
};
static std::unique_ptr<IContext> 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);
2021-09-13 20:24:42 +02:00
context->GetObjectManager().LoadObjects(loadResult.RequiredObjects);
// TODO: Have a separate GameState and exchange once loaded.
auto& gameState = GetGameState();
importer->Import(gameState);
2021-11-24 14:37:47 +01:00
ResetEntitySpatialIndices();
ResetAllSpriteQuadrantPlacements();
ScenerySetDefaultPlacementConfiguration();
LoadPalette();
EntityTweener::Get().Reset();
MapAnimationAutoCreate();
FixInvalidVehicleSpriteSizes();
gGameSpeed = 1;
return context;
}
2024-03-26 15:08:17 +01:00
template<class Fn> static bool updateUntil(int maxSteps, Fn&& fn)
{
while (maxSteps-- && !fn())
{
2024-03-26 15:08:17 +01:00
gameStateUpdateLogic();
}
return maxSteps > 0;
}
template<class GA, class... Args> static void execute(Args&&... args)
{
GA ga(std::forward<Args>(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);
2024-03-26 14:37:30 +01:00
auto& gameState = GetGameState();
// Open park for free but charging for rides
execute<ParkSetParameterAction>(ParkParameter::Open);
execute<ParkSetEntranceFeeAction>(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
2022-08-11 00:00:58 +02:00
execute<RideSetStatusAction>(ferrisWheel.id, RideStatus::Open);
execute<RideSetPriceAction>(ferrisWheel.id, 0, true);
// Ignore intensity to stimulate peeps to queue into ferris wheel
2024-03-26 14:37:30 +01:00
gameState.Cheats.IgnoreRideIntensity = true;
// Insert a rich guest
auto richGuest = Park::GenerateGuest();
richGuest->CashInPocket = 3000;
// Wait for rich guest to get in queue
2024-03-26 15:08:17 +01:00
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
2024-03-26 15:08:17 +01:00
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<RideSetPriceAction>(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;
2024-03-26 15:08:17 +01:00
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);
2024-03-26 14:37:30 +01:00
auto& gameState = GetGameState();
// Open park for free but charging for rides
execute<ParkSetParameterAction>(ParkParameter::Open);
execute<ParkSetEntranceFeeAction>(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
2022-08-11 00:00:58 +02:00
execute<RideSetStatusAction>(carRide.id, RideStatus::Open);
execute<RideSetPriceAction>(carRide.id, 0, true);
// Ignore intensity to stimulate peeps to queue into the ride
2024-03-26 14:37:30 +01:00
gameState.Cheats.IgnoreRideIntensity = true;
// Create some guests
std::vector<Peep*> 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; };
2024-03-26 15:08:17 +01:00
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);
2024-03-26 15:08:17 +01:00
gameStateUpdateLogic();
}
}