mirror of
https://github.com/OpenRCT2/OpenRCT2.git
synced 2024-08-29 21:46:05 +02:00
763 lines
27 KiB
C++
763 lines
27 KiB
C++
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
|
|
/*****************************************************************************
|
|
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
|
|
*
|
|
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
|
|
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
|
|
*
|
|
* OpenRCT2 is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* A full copy of the GNU General Public License can be found in licence.txt
|
|
*****************************************************************************/
|
|
#pragma endregion
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "TestPaint.hpp"
|
|
#include "FunctionCall.hpp"
|
|
#include "GeneralSupportHeightCall.hpp"
|
|
#include "PaintIntercept.hpp"
|
|
#include "Printer.hpp"
|
|
#include "SegmentSupportHeightCall.hpp"
|
|
#include "SideTunnelCall.hpp"
|
|
#include "String.hpp"
|
|
#include "TestTrack.hpp"
|
|
#include "Utils.hpp"
|
|
#include "VerticalTunnelCall.hpp"
|
|
|
|
extern "C" {
|
|
#include <openrct2/paint/map_element/map_element.h>
|
|
#include <openrct2/paint/supports.h>
|
|
#include <openrct2/ride/ride.h>
|
|
#include <openrct2/ride/track.h>
|
|
#include <openrct2/ride/track_data.h>
|
|
}
|
|
|
|
interface ITestTrackFilter {
|
|
public:
|
|
virtual ~ITestTrackFilter() {}
|
|
|
|
virtual bool AppliesTo(uint8 rideType, uint8 trackType) abstract;
|
|
|
|
virtual int Variations(uint8 rideType, uint8 trackType) abstract;
|
|
|
|
virtual std::string VariantName(uint8 rideType, uint8 trackType, int variant) abstract;
|
|
|
|
virtual void ApplyTo(uint8 rideType, uint8 trackType, int variant,
|
|
rct_map_element *mapElement, rct_map_element *surfaceElement,
|
|
rct_ride *ride, rct_ride_entry *rideEntry
|
|
) abstract;
|
|
};
|
|
|
|
class CableLiftFilter : public ITestTrackFilter {
|
|
public:
|
|
bool AppliesTo(uint8 rideType, uint8 trackType) override {
|
|
return rideType == RIDE_TYPE_GIGA_COASTER;
|
|
}
|
|
|
|
int Variations(uint8 rideType, uint8 trackType) override {
|
|
return 2;
|
|
}
|
|
|
|
std::string VariantName(uint8 rideType, uint8 trackType, int variant) override {
|
|
return String::Format("cableLift:%d", variant);
|
|
}
|
|
|
|
virtual void ApplyTo(uint8 rideType, uint8 trackType, int variant,
|
|
rct_map_element *mapElement, rct_map_element *surfaceElement,
|
|
rct_ride *ride, rct_ride_entry *rideEntry
|
|
) override {
|
|
if (variant == 0) {
|
|
mapElement->properties.track.colour &= ~TRACK_ELEMENT_COLOUR_FLAG_CABLE_LIFT;
|
|
} else {
|
|
mapElement->properties.track.colour |= TRACK_ELEMENT_COLOUR_FLAG_CABLE_LIFT;
|
|
}
|
|
}
|
|
};
|
|
|
|
class ChainLiftFilter : public ITestTrackFilter {
|
|
public:
|
|
bool AppliesTo(uint8 rideType, uint8 trackType) override {
|
|
return !ride_type_has_flag(rideType, RIDE_TYPE_FLAG_FLAT_RIDE);
|
|
}
|
|
|
|
int Variations(uint8 rideType, uint8 trackType) override {
|
|
return 2;
|
|
}
|
|
|
|
std::string VariantName(uint8 rideType, uint8 trackType, int variant) override {
|
|
return String::Format("chainLift:%d", variant);
|
|
}
|
|
|
|
virtual void ApplyTo(uint8 rideType, uint8 trackType, int variant,
|
|
rct_map_element *mapElement, rct_map_element *surfaceElement,
|
|
rct_ride *ride, rct_ride_entry *rideEntry
|
|
) override {
|
|
if (variant == 0) {
|
|
mapElement->type &= ~TRACK_ELEMENT_FLAG_CHAIN_LIFT;
|
|
} else {
|
|
mapElement->type |= TRACK_ELEMENT_FLAG_CHAIN_LIFT;
|
|
}
|
|
}
|
|
};
|
|
|
|
class InvertedFilter : public ITestTrackFilter {
|
|
public:
|
|
bool AppliesTo(uint8 rideType, uint8 trackType) override {
|
|
if (rideType == RIDE_TYPE_MULTI_DIMENSION_ROLLER_COASTER ||
|
|
rideType == RIDE_TYPE_FLYING_ROLLER_COASTER ||
|
|
rideType == RIDE_TYPE_LAY_DOWN_ROLLER_COASTER) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int Variations(uint8 rideType, uint8 trackType) override {
|
|
return 2;
|
|
}
|
|
|
|
std::string VariantName(uint8 rideType, uint8 trackType, int variant) override {
|
|
return String::Format("inverted:%d", variant);
|
|
}
|
|
|
|
virtual void ApplyTo(uint8 rideType, uint8 trackType, int variant,
|
|
rct_map_element *mapElement, rct_map_element *surfaceElement,
|
|
rct_ride *ride, rct_ride_entry *rideEntry
|
|
) override {
|
|
if (variant == 0) {
|
|
mapElement->properties.track.colour &= ~TRACK_ELEMENT_COLOUR_FLAG_INVERTED;
|
|
} else {
|
|
mapElement->properties.track.colour |= TRACK_ELEMENT_COLOUR_FLAG_INVERTED;
|
|
}
|
|
}
|
|
};
|
|
|
|
class EntranceStyleFilter : public ITestTrackFilter {
|
|
public:
|
|
bool AppliesTo(uint8 rideType, uint8 trackType) override {
|
|
if (trackType == TRACK_ELEM_BEGIN_STATION ||
|
|
trackType == TRACK_ELEM_MIDDLE_STATION ||
|
|
trackType == TRACK_ELEM_END_STATION) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int Variations(uint8 rideType, uint8 trackType) override {
|
|
return RIDE_ENTRANCE_STYLE_COUNT - 1;
|
|
}
|
|
|
|
std::string VariantName(uint8 rideType, uint8 trackType, int variant) override {
|
|
return String::Format("entranceStyle:%d", variant);
|
|
}
|
|
|
|
virtual void ApplyTo(uint8 rideType, uint8 trackType, int variant,
|
|
rct_map_element *mapElement, rct_map_element *surfaceElement,
|
|
rct_ride *ride, rct_ride_entry *rideEntry
|
|
) override {
|
|
ride->entrance_style = variant;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
static void CallOriginal(
|
|
uint8 rideType,
|
|
uint8 trackType,
|
|
uint8 direction,
|
|
uint8 trackSequence,
|
|
uint16 height,
|
|
rct_map_element *mapElement
|
|
) {
|
|
uint32 *trackDirectionList = (uint32 *) RideTypeTrackPaintFunctionsOld[rideType][trackType];
|
|
const uint8 rideIndex = 0;
|
|
|
|
// Have to call from this point as it pushes esi and expects callee to pop it
|
|
RCT2_CALLPROC_X(
|
|
0x006C4934,
|
|
rideType,
|
|
(int) trackDirectionList,
|
|
direction,
|
|
height,
|
|
(int) mapElement,
|
|
rideIndex * sizeof(rct_ride),
|
|
trackSequence
|
|
);
|
|
}
|
|
|
|
static void CallNew(
|
|
uint8 rideType,
|
|
uint8 trackType,
|
|
uint8 direction,
|
|
uint8 trackSequence,
|
|
uint16 height,
|
|
rct_map_element *mapElement
|
|
) {
|
|
TRACK_PAINT_FUNCTION_GETTER newPaintFunctionGetter = RideTypeTrackPaintFunctions[rideType];
|
|
TRACK_PAINT_FUNCTION newPaintFunction = newPaintFunctionGetter(trackType, direction);
|
|
|
|
newPaintFunction(0, trackSequence, direction, height, mapElement);
|
|
}
|
|
|
|
typedef uint8 (*TestFunction)(uint8, uint8, uint8, std::string *);
|
|
|
|
static uint8 TestTrackElementPaintCalls(uint8 rideType, uint8 trackType, uint8 trackSequence, std::string *error);
|
|
|
|
static uint8 TestTrackElementSegmentSupportHeight(uint8 rideType, uint8 trackType, uint8 trackSequence, std::string *error);
|
|
|
|
static uint8 TestTrackElementGeneralSupportHeight(uint8 rideType, uint8 trackType, uint8 trackSequence, std::string *error);
|
|
|
|
static uint8 TestTrackElementSideTunnels(uint8 rideType, uint8 trackType, uint8 trackSequence, std::string *error);
|
|
|
|
static uint8 TestTrackElementVerticalTunnels(uint8 rideType, uint8 trackType, uint8 trackSequence, std::string *error);
|
|
|
|
uint8 TestTrack::TestPaintTrackElement(uint8 rideType, uint8 trackType, std::string *out) {
|
|
if (!Utils::rideSupportsTrackType(rideType, trackType)) {
|
|
return TEST_FAILED;
|
|
}
|
|
|
|
if (rideType == RIDE_TYPE_CHAIRLIFT) {
|
|
if (trackType == TRACK_ELEM_BEGIN_STATION || trackType == TRACK_ELEM_MIDDLE_STATION ||
|
|
trackType == TRACK_ELEM_END_STATION) {
|
|
// These rides check neighbouring tiles for tracks
|
|
return TEST_SKIPPED;
|
|
}
|
|
}
|
|
|
|
int sequenceCount = Utils::getTrackSequenceCount(rideType, trackType);
|
|
std::string error = String::Format("rct2: 0x%08X\n", RideTypeTrackPaintFunctionsOld[rideType][trackType]);
|
|
|
|
uint8 retVal = TEST_SUCCESS;
|
|
|
|
static TestFunction functions[] = {
|
|
TestTrackElementPaintCalls,
|
|
TestTrackElementSegmentSupportHeight,
|
|
TestTrackElementGeneralSupportHeight,
|
|
TestTrackElementSideTunnels,
|
|
TestTrackElementVerticalTunnels,
|
|
};
|
|
|
|
for (int trackSequence = 0; trackSequence < sequenceCount; trackSequence++) {
|
|
for (auto &&function : functions) {
|
|
retVal = function(rideType, trackType, trackSequence, &error);
|
|
|
|
if (retVal != TEST_SUCCESS) {
|
|
*out += error + "\n";
|
|
return retVal;
|
|
}
|
|
}
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
static uint8 TestTrackElementPaintCalls(uint8 rideType, uint8 trackType, uint8 trackSequence, std::string *error) {
|
|
uint16 height = 3 * 16;
|
|
|
|
rct_map_element mapElement = {0};
|
|
mapElement.flags |= MAP_ELEMENT_FLAG_LAST_TILE;
|
|
mapElement.properties.track.type = trackType;
|
|
mapElement.base_height = height / 16;
|
|
g_currently_drawn_item = &mapElement;
|
|
|
|
rct_map_element surfaceElement = {0};
|
|
surfaceElement.type = MAP_ELEMENT_TYPE_SURFACE;
|
|
surfaceElement.base_height = 2;
|
|
gSurfaceElement = &surfaceElement;
|
|
gDidPassSurface = true;
|
|
|
|
TestPaint::ResetEnvironment();
|
|
TestPaint::ResetTunnels();
|
|
|
|
function_call callBuffer[256] = {0};
|
|
int callCount = 0;
|
|
|
|
// TODO: test supports
|
|
// TODO: test flat rides
|
|
// TODO: test mazes
|
|
// TODO: test underground (Wooden RC)
|
|
// TODO: test station fences
|
|
|
|
std::vector<ITestTrackFilter *> filters;
|
|
filters.push_back(new CableLiftFilter());
|
|
filters.push_back(new ChainLiftFilter());
|
|
filters.push_back(new InvertedFilter());
|
|
filters.push_back(new EntranceStyleFilter());
|
|
|
|
std::vector<ITestTrackFilter *> activeFilters;
|
|
|
|
for (auto &&filter : filters) {
|
|
if (filter->AppliesTo(rideType, trackType)) {
|
|
activeFilters.push_back(filter);
|
|
}
|
|
}
|
|
|
|
// Add an element so there's always something to add to
|
|
std::vector<uint8> filler;
|
|
filler.push_back(0);
|
|
|
|
std::vector<std::vector<uint8>> argumentPermutations;
|
|
argumentPermutations.push_back(filler);
|
|
for (size_t filterIndex = 0; filterIndex < activeFilters.size(); ++filterIndex) {
|
|
ITestTrackFilter *filter = activeFilters[filterIndex];
|
|
uint8 variantCount = filter->Variations(rideType, trackType);
|
|
|
|
std::vector<std::vector<uint8>> newArgumentPermutations;
|
|
for (int variant = 0; variant < variantCount; variant++) {
|
|
for (auto &&oldPermutation : argumentPermutations) {
|
|
std::vector<uint8> permutation;
|
|
permutation.insert(permutation.begin(), oldPermutation.begin(), oldPermutation.end());
|
|
permutation.push_back(variant);
|
|
newArgumentPermutations.push_back(permutation);
|
|
}
|
|
}
|
|
|
|
argumentPermutations.clear();
|
|
argumentPermutations.insert(argumentPermutations.begin(), newArgumentPermutations.begin(),
|
|
newArgumentPermutations.end());
|
|
}
|
|
|
|
for (auto &&arguments : argumentPermutations) {
|
|
std::string baseCaseName = "[";
|
|
|
|
for (size_t filterIndex = 0; filterIndex < activeFilters.size(); ++filterIndex) {
|
|
uint8 &variant = arguments[1 + filterIndex];
|
|
baseCaseName += activeFilters[filterIndex]->VariantName(rideType, trackType, variant);
|
|
baseCaseName += " ";
|
|
|
|
activeFilters[filterIndex]->ApplyTo(rideType, trackType, variant, &mapElement, &surfaceElement, &(gRideList[0]), gRideEntries[0]);
|
|
}
|
|
|
|
|
|
for (int currentRotation = 0; currentRotation < 4; currentRotation++) {
|
|
gCurrentRotation = currentRotation;
|
|
for (int direction = 0; direction < 4; direction++) {
|
|
RCT2_GLOBAL(0x009DE56A, sint16) = 64; // x
|
|
RCT2_GLOBAL(0x009DE56E, sint16) = 64; // y
|
|
|
|
std::string caseName = String::Format(
|
|
"%srotation:%d direction:%d trackSequence:%d]",
|
|
baseCaseName.c_str(), currentRotation, direction, trackSequence
|
|
);
|
|
|
|
PaintIntercept::ClearCalls();
|
|
TestPaint::ResetSupportHeights();
|
|
gWoodenSupportsPrependTo = nullptr;
|
|
|
|
CallOriginal(rideType, trackType, direction, trackSequence, height, &mapElement);
|
|
|
|
callCount = PaintIntercept::GetCalls(callBuffer);
|
|
std::vector<function_call> oldCalls;
|
|
oldCalls.insert(oldCalls.begin(), callBuffer, callBuffer + callCount);
|
|
|
|
PaintIntercept::ClearCalls();
|
|
testpaint_clear_ignore();
|
|
TestPaint::ResetSupportHeights();
|
|
gWoodenSupportsPrependTo = nullptr;
|
|
|
|
CallNew(rideType, trackType, direction, trackSequence, height, &mapElement);
|
|
|
|
if (testpaint_is_ignored(direction, trackSequence)) {
|
|
*error += String::Format("[ IGNORED ] %s\n", caseName.c_str());
|
|
continue;
|
|
}
|
|
|
|
callCount = PaintIntercept::GetCalls(callBuffer);
|
|
std::vector<function_call> newCalls;
|
|
newCalls.insert(newCalls.begin(), callBuffer, callBuffer + callCount);
|
|
|
|
bool sucess = true;
|
|
if (oldCalls.size() != newCalls.size()) {
|
|
*error += String::Format(
|
|
"Call counts don't match (was %d, expected %d). %s\n",
|
|
newCalls.size(), oldCalls.size(), caseName.c_str()
|
|
);
|
|
sucess = false;
|
|
} else if (!FunctionCall::AssertsEquals(oldCalls, newCalls)) {
|
|
*error += String::Format("Calls don't match. %s\n", caseName.c_str());
|
|
sucess = false;
|
|
}
|
|
|
|
if (!sucess) {
|
|
*error += " Expected:\n";
|
|
*error += Printer::PrintFunctionCalls(oldCalls, height);
|
|
*error += " Actual:\n";
|
|
*error += Printer::PrintFunctionCalls(newCalls, height);
|
|
|
|
return TEST_FAILED;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return TEST_SUCCESS;
|
|
}
|
|
|
|
static uint8 TestTrackElementSegmentSupportHeight(uint8 rideType, uint8 trackType, uint8 trackSequence, std::string *error) {
|
|
uint16 height = 3 * 16;
|
|
|
|
rct_map_element mapElement = {0};
|
|
mapElement.flags |= MAP_ELEMENT_FLAG_LAST_TILE;
|
|
mapElement.properties.track.type = trackType;
|
|
mapElement.base_height = height / 16;
|
|
g_currently_drawn_item = &mapElement;
|
|
|
|
rct_map_element surfaceElement = {0};
|
|
surfaceElement.type = MAP_ELEMENT_TYPE_SURFACE;
|
|
surfaceElement.base_height = 2;
|
|
gSurfaceElement = &surfaceElement;
|
|
gDidPassSurface = true;
|
|
|
|
TestPaint::ResetEnvironment();
|
|
TestPaint::ResetTunnels();
|
|
|
|
// TODO: Test Chainlift
|
|
// TODO: Test Maze
|
|
// TODO: Allow skip
|
|
|
|
std::string state = String::Format("[trackSequence:%d chainLift:%d]", trackSequence, 0);
|
|
|
|
std::vector<SegmentSupportCall> tileSegmentSupportCalls[4];
|
|
|
|
for (int direction = 0; direction < 4; direction++) {
|
|
TestPaint::ResetSupportHeights();
|
|
|
|
CallOriginal(rideType, trackType, direction, trackSequence, height, &mapElement);
|
|
|
|
tileSegmentSupportCalls[direction] = SegmentSupportHeightCall::getSegmentCalls(gSupportSegments, direction);
|
|
}
|
|
|
|
std::vector<SegmentSupportCall> referenceCalls = tileSegmentSupportCalls[0];
|
|
|
|
if (!SegmentSupportHeightCall::CallsMatch(tileSegmentSupportCalls)) {
|
|
bool success = SegmentSupportHeightCall::FindMostCommonSupportCall(tileSegmentSupportCalls, &referenceCalls);
|
|
if (!success) {
|
|
*error += String::Format("Original segment calls didn't match. %s\n", state.c_str());
|
|
for (int direction = 0; direction < 4; direction++) {
|
|
*error += String::Format("# %d\n", direction);
|
|
*error += Printer::PrintSegmentSupportHeightCalls(tileSegmentSupportCalls[direction]);
|
|
}
|
|
return TEST_FAILED;
|
|
}
|
|
}
|
|
|
|
for (int direction = 0; direction < 4; direction++) {
|
|
TestPaint::ResetSupportHeights();
|
|
|
|
testpaint_clear_ignore();
|
|
CallNew(rideType, trackType, direction, trackSequence, height, &mapElement);
|
|
if (testpaint_is_ignored(direction, trackSequence)) {
|
|
continue;
|
|
}
|
|
|
|
std::vector<SegmentSupportCall> newCalls = SegmentSupportHeightCall::getSegmentCalls(gSupportSegments,
|
|
direction);
|
|
if (!SegmentSupportHeightCall::CallsEqual(referenceCalls, newCalls)) {
|
|
*error += String::Format(
|
|
"Segment support heights didn't match. [direction:%d] %s\n",
|
|
direction, state.c_str()
|
|
);
|
|
*error += " Expected:\n";
|
|
*error += Printer::PrintSegmentSupportHeightCalls(referenceCalls);
|
|
*error += " Actual:\n";
|
|
*error += Printer::PrintSegmentSupportHeightCalls(newCalls);
|
|
|
|
return TEST_FAILED;
|
|
}
|
|
}
|
|
|
|
return TEST_SUCCESS;
|
|
}
|
|
|
|
static uint8 TestTrackElementGeneralSupportHeight(uint8 rideType, uint8 trackType, uint8 trackSequence, std::string *error) {
|
|
uint16 height = 3 * 16;
|
|
|
|
rct_map_element mapElement = {0};
|
|
mapElement.flags |= MAP_ELEMENT_FLAG_LAST_TILE;
|
|
mapElement.properties.track.type = trackType;
|
|
mapElement.base_height = height / 16;
|
|
g_currently_drawn_item = &mapElement;
|
|
|
|
rct_map_element surfaceElement = {0};
|
|
surfaceElement.type = MAP_ELEMENT_TYPE_SURFACE;
|
|
surfaceElement.base_height = 2;
|
|
gSurfaceElement = &surfaceElement;
|
|
gDidPassSurface = true;
|
|
|
|
TestPaint::ResetEnvironment();
|
|
TestPaint::ResetTunnels();
|
|
|
|
// TODO: Test Chainlift
|
|
// TODO: Test Maze
|
|
// TODO: Allow skip
|
|
|
|
std::string state = String::Format("[trackSequence:%d chainLift:%d]", trackSequence, 0);
|
|
|
|
SupportCall tileGeneralSupportCalls[4];
|
|
for (int direction = 0; direction < 4; direction++) {
|
|
TestPaint::ResetSupportHeights();
|
|
|
|
CallOriginal(rideType, trackType, direction, trackSequence, height, &mapElement);
|
|
|
|
tileGeneralSupportCalls[direction].height = -1;
|
|
tileGeneralSupportCalls[direction].slope = -1;
|
|
if (gSupport.height != 0) {
|
|
tileGeneralSupportCalls[direction].height = gSupport.height;
|
|
}
|
|
if (gSupport.slope != 0xFF) {
|
|
tileGeneralSupportCalls[direction].slope = gSupport.slope;
|
|
}
|
|
}
|
|
|
|
SupportCall referenceCall = tileGeneralSupportCalls[0];
|
|
if (!GeneralSupportHeightCall::CallsMatch(tileGeneralSupportCalls)) {
|
|
bool success = GeneralSupportHeightCall::FindMostCommonSupportCall(tileGeneralSupportCalls, &referenceCall);
|
|
if (!success) {
|
|
*error += String::Format("Original support calls didn't match. %s\n", state.c_str());
|
|
for (int i = 0; i < 4; ++i) {
|
|
*error += String::Format("[%d, 0x%02X] ", tileGeneralSupportCalls[i].height, tileGeneralSupportCalls[i].slope);
|
|
}
|
|
*error += "\n";
|
|
return TEST_FAILED;
|
|
}
|
|
}
|
|
|
|
for (int direction = 0; direction < 4; direction++) {
|
|
TestPaint::ResetSupportHeights();
|
|
|
|
testpaint_clear_ignore();
|
|
CallNew(rideType, trackType, direction, trackSequence, height, &mapElement);
|
|
if (testpaint_is_ignored(direction, trackSequence)) {
|
|
continue;
|
|
}
|
|
|
|
|
|
if (referenceCall.height != -1) {
|
|
if (gSupport.height != referenceCall.height) {
|
|
*error += String::Format(
|
|
"General support heights didn't match. (expected height + %d, actual: height + %d) [direction:%d] %s\n",
|
|
referenceCall.height - height,
|
|
gSupport.height - height,
|
|
direction,
|
|
state.c_str()
|
|
);
|
|
return TEST_FAILED;
|
|
}
|
|
}
|
|
if (referenceCall.slope != -1) {
|
|
if (gSupport.slope != referenceCall.slope) {
|
|
*error += String::Format(
|
|
"General support slopes didn't match. (expected 0x%02X, actual: 0x%02X) [direction:%d] %s\n",
|
|
referenceCall.slope,
|
|
gSupport.slope,
|
|
direction,
|
|
state.c_str()
|
|
);
|
|
return TEST_FAILED;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TEST_SUCCESS;
|
|
}
|
|
|
|
static uint8 TestTrackElementSideTunnels(uint8 rideType, uint8 trackType, uint8 trackSequence, std::string *error) {
|
|
uint16 height = 3 * 16;
|
|
|
|
rct_map_element mapElement = {0};
|
|
mapElement.flags |= MAP_ELEMENT_FLAG_LAST_TILE;
|
|
mapElement.properties.track.type = trackType;
|
|
mapElement.base_height = height / 16;
|
|
g_currently_drawn_item = &mapElement;
|
|
|
|
rct_map_element surfaceElement = {0};
|
|
surfaceElement.type = MAP_ELEMENT_TYPE_SURFACE;
|
|
surfaceElement.base_height = 2;
|
|
gSurfaceElement = &surfaceElement;
|
|
gDidPassSurface = true;
|
|
|
|
TestPaint::ResetEnvironment();
|
|
TestPaint::ResetTunnels();
|
|
|
|
TunnelCall tileTunnelCalls[4][4];
|
|
|
|
// TODO: test inverted tracks
|
|
|
|
for (int direction = 0; direction < 4; direction++) {
|
|
TestPaint::ResetTunnels();
|
|
|
|
for (sint8 offset = -8; offset <= 8; offset += 8) {
|
|
CallOriginal(rideType, trackType, direction, trackSequence, height + offset, &mapElement);
|
|
}
|
|
|
|
uint8 rightIndex = (direction + 1) % 4;
|
|
uint8 leftIndex = direction;
|
|
|
|
for (int i = 0; i < 4; ++i) {
|
|
tileTunnelCalls[direction][i].call = TUNNELCALL_SKIPPED;
|
|
}
|
|
|
|
bool err = false;
|
|
tileTunnelCalls[direction][rightIndex] = SideTunnelCall::ExtractTunnelCalls(gRightTunnels, gRightTunnelCount, height,
|
|
&err);
|
|
|
|
tileTunnelCalls[direction][leftIndex] = SideTunnelCall::ExtractTunnelCalls(gLeftTunnels, gLeftTunnelCount, height,
|
|
&err);
|
|
|
|
if (err) {
|
|
*error += "Multiple tunnels on one side aren't supported.\n";
|
|
return TEST_FAILED;
|
|
}
|
|
}
|
|
|
|
TunnelCall newTileTunnelCalls[4][4];
|
|
for (int direction = 0; direction < 4; direction++) {
|
|
TestPaint::ResetTunnels();
|
|
|
|
testpaint_clear_ignore();
|
|
|
|
for (sint8 offset = -8; offset <= 8; offset += 8) {
|
|
// TODO: move tunnel pushing to interface so we don't have to check the output 3 times
|
|
CallNew(rideType, trackType, direction, trackSequence, height + offset, &mapElement);
|
|
}
|
|
|
|
uint8 rightIndex = (direction + 1) % 4;
|
|
uint8 leftIndex = direction;
|
|
|
|
for (int i = 0; i < 4; ++i) {
|
|
newTileTunnelCalls[direction][i].call = TUNNELCALL_SKIPPED;
|
|
}
|
|
|
|
bool err = false;
|
|
newTileTunnelCalls[direction][rightIndex] = SideTunnelCall::ExtractTunnelCalls(gRightTunnels, gRightTunnelCount, height,
|
|
&err);
|
|
|
|
newTileTunnelCalls[direction][leftIndex] = SideTunnelCall::ExtractTunnelCalls(gLeftTunnels, gLeftTunnelCount, height,
|
|
&err);
|
|
|
|
if (err) {
|
|
*error += "Multiple tunnels on one side aren't supported.\n";
|
|
return TEST_FAILED;
|
|
}
|
|
}
|
|
|
|
|
|
if (!SideTunnelCall::TunnelCallsLineUp(tileTunnelCalls)) {
|
|
// TODO: Check that new pattern uses the same tunnel group (round, big round, etc.)
|
|
*error += String::Format(
|
|
"Original tunnel calls don\'t line up. Skipping tunnel validation [trackSequence:%d].\n",
|
|
trackSequence
|
|
);
|
|
*error += Printer::PrintSideTunnelCalls(tileTunnelCalls);
|
|
|
|
if (!SideTunnelCall::TunnelCallsLineUp(newTileTunnelCalls)) {
|
|
*error += String::Format("Decompiled tunnel calls don\'t line up. [trackSequence:%d].\n", trackSequence);
|
|
*error += Printer::PrintSideTunnelCalls(newTileTunnelCalls);
|
|
return TEST_FAILED;
|
|
}
|
|
|
|
return TEST_SUCCESS;
|
|
}
|
|
|
|
TunnelCall referencePattern[4];
|
|
SideTunnelCall::GetTunnelCallReferencePattern(tileTunnelCalls, &referencePattern);
|
|
|
|
TunnelCall actualPattern[4];
|
|
SideTunnelCall::GetTunnelCallReferencePattern(newTileTunnelCalls, &actualPattern);
|
|
|
|
if (!SideTunnelCall::TunnelPatternsMatch(referencePattern, actualPattern)) {
|
|
*error += String::Format("Tunnel calls don't match expected pattern. [trackSequence:%d]\n", trackSequence);
|
|
*error += " Expected:\n";
|
|
*error += Printer::PrintSideTunnelCalls(tileTunnelCalls);
|
|
*error += " Actual:\n";
|
|
*error += Printer::PrintSideTunnelCalls(newTileTunnelCalls);
|
|
return TEST_FAILED;
|
|
}
|
|
|
|
return TEST_SUCCESS;
|
|
}
|
|
|
|
static uint8 TestTrackElementVerticalTunnels(uint8 rideType, uint8 trackType, uint8 trackSequence, std::string *error) {
|
|
uint16 height = 3 * 16;
|
|
|
|
rct_map_element mapElement = {0};
|
|
mapElement.flags |= MAP_ELEMENT_FLAG_LAST_TILE;
|
|
mapElement.properties.track.type = trackType;
|
|
mapElement.base_height = height / 16;
|
|
g_currently_drawn_item = &mapElement;
|
|
|
|
rct_map_element surfaceElement = {0};
|
|
surfaceElement.type = MAP_ELEMENT_TYPE_SURFACE;
|
|
surfaceElement.base_height = 2;
|
|
gSurfaceElement = &surfaceElement;
|
|
gDidPassSurface = true;
|
|
|
|
TestPaint::ResetEnvironment();
|
|
TestPaint::ResetTunnels();
|
|
|
|
uint16 verticalTunnelHeights[4];
|
|
|
|
for (int direction = 0; direction < 4; direction++) {
|
|
uint8 tunnelHeights[3] = {0};
|
|
|
|
for (uint8 i = 0; i < 3; i++) {
|
|
gVerticalTunnelHeight = 0;
|
|
CallOriginal(rideType, trackType, direction, trackSequence, height - 8 + i * 8, &mapElement);
|
|
tunnelHeights[i] = gVerticalTunnelHeight;
|
|
}
|
|
|
|
verticalTunnelHeights[direction] = VerticalTunnelCall::GetTunnelHeight(height, tunnelHeights);
|
|
}
|
|
|
|
if (!VerticalTunnelCall::HeightIsConsistent(verticalTunnelHeights)) {
|
|
*error += String::Format(
|
|
"Original vertical tunnel height is inconsistent, skipping test. [trackSequence:%d]\n",
|
|
trackSequence
|
|
);
|
|
return TEST_SUCCESS;
|
|
}
|
|
|
|
uint16 referenceHeight = verticalTunnelHeights[0];
|
|
|
|
for (int direction = 0; direction < 4; direction++) {
|
|
|
|
testpaint_clear_ignore();
|
|
|
|
testPaintVerticalTunnelHeight = 0;
|
|
CallNew(rideType, trackType, direction, trackSequence, height, &mapElement);
|
|
|
|
if (testpaint_is_ignored(direction, trackSequence)) {
|
|
continue;
|
|
}
|
|
|
|
if (testPaintVerticalTunnelHeight != referenceHeight) {
|
|
if (referenceHeight == 0) {
|
|
*error += String::Format(
|
|
"Expected no tunnel. Actual: %s [trackSequence:%d]\n",
|
|
Printer::PrintHeightOffset(testPaintVerticalTunnelHeight, height).c_str(),
|
|
trackSequence
|
|
);
|
|
return TEST_FAILED;
|
|
}
|
|
|
|
*error += String::Format(
|
|
"Expected vertical tunnel height to be `%s`, was `%s`. [trackSequence:%d direction:%d]\n",
|
|
Printer::PrintHeightOffset(referenceHeight, height).c_str(),
|
|
Printer::PrintHeightOffset(testPaintVerticalTunnelHeight, height).c_str(),
|
|
trackSequence,
|
|
direction
|
|
);
|
|
|
|
return TEST_FAILED;
|
|
}
|
|
}
|
|
|
|
return TEST_SUCCESS;
|
|
}
|