1 /*****************************************************************************
2 * Copyright (c) 2014-2018 OpenRCT2 developers
3 *
4 * For a complete list of all authors, please refer to contributors.md
5 * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6 *
7 * OpenRCT2 is licensed under the GNU General Public License version 3.
8 *****************************************************************************/
9
10 #include "TestTrack.hpp"
11
12 #include "../../src/openrct2/ride/RideData.h"
13 #include "Data.h"
14 #include "FunctionCall.hpp"
15 #include "GeneralSupportHeightCall.hpp"
16 #include "PaintIntercept.hpp"
17 #include "Printer.hpp"
18 #include "SegmentSupportHeightCall.hpp"
19 #include "SideTunnelCall.hpp"
20 #include "String.hpp"
21 #include "TestPaint.hpp"
22 #include "Utils.hpp"
23 #include "VerticalTunnelCall.hpp"
24
25 #include <openrct2/paint/Supports.h>
26 #include <openrct2/paint/tile_element/Paint.TileElement.h>
27 #include <openrct2/ride/Ride.h>
28 #include <openrct2/ride/Track.h>
29 #include <openrct2/ride/TrackData.h>
30 #include <string>
31 #include <vector>
32
33 struct ITestTrackFilter
34 {
35 public:
~ITestTrackFilterITestTrackFilter36 virtual ~ITestTrackFilter()
37 {
38 }
39
40 virtual bool AppliesTo(uint8_t rideType, uint8_t trackType) abstract;
41
42 virtual int Variations(uint8_t rideType, uint8_t trackType) abstract;
43
44 virtual std::string VariantName(uint8_t rideType, uint8_t trackType, int variant) abstract;
45
46 virtual void ApplyTo(
47 uint8_t rideType, uint8_t trackType, int variant, TileElement* tileElement, TileElement* surfaceElement, Ride* ride,
48 rct_ride_entry* rideEntry) abstract;
49 };
50
51 class CableLiftFilter : public ITestTrackFilter
52 {
53 public:
AppliesTo(uint8_t rideType,uint8_t trackType)54 bool AppliesTo(uint8_t rideType, uint8_t trackType) override
55 {
56 return rideType == RIDE_TYPE_GIGA_COASTER;
57 }
58
Variations(uint8_t rideType,uint8_t trackType)59 int Variations(uint8_t rideType, uint8_t trackType) override
60 {
61 return 2;
62 }
63
VariantName(uint8_t rideType,uint8_t trackType,int variant)64 std::string VariantName(uint8_t rideType, uint8_t trackType, int variant) override
65 {
66 return String::Format("cableLift:%d", variant);
67 }
68
ApplyTo(uint8_t rideType,uint8_t trackType,int variant,TileElement * tileElement,TileElement * surfaceElement,Ride * ride,rct_ride_entry * rideEntry)69 virtual void ApplyTo(
70 uint8_t rideType, uint8_t trackType, int variant, TileElement* tileElement, TileElement* surfaceElement, Ride* ride,
71 rct_ride_entry* rideEntry) override
72 {
73 if (variant == 0)
74 {
75 tileElement->AsTrack()->SetHasCableLift(false);
76 }
77 else
78 {
79 tileElement->AsTrack()->SetHasCableLift(true);
80 }
81 }
82 };
83
84 class ChainLiftFilter : public ITestTrackFilter
85 {
86 public:
AppliesTo(uint8_t rideType,uint8_t trackType)87 bool AppliesTo(uint8_t rideType, uint8_t trackType) override
88 {
89 return !GetRideTypeDescriptor(rideType).HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE);
90 }
91
Variations(uint8_t rideType,uint8_t trackType)92 int Variations(uint8_t rideType, uint8_t trackType) override
93 {
94 return 2;
95 }
96
VariantName(uint8_t rideType,uint8_t trackType,int variant)97 std::string VariantName(uint8_t rideType, uint8_t trackType, int variant) override
98 {
99 return String::Format("chainLift:%d", variant);
100 }
101
ApplyTo(uint8_t rideType,uint8_t trackType,int variant,TileElement * tileElement,TileElement * surfaceElement,Ride * ride,rct_ride_entry * rideEntry)102 virtual void ApplyTo(
103 uint8_t rideType, uint8_t trackType, int variant, TileElement* tileElement, TileElement* surfaceElement, Ride* ride,
104 rct_ride_entry* rideEntry) override
105 {
106 tileElement->AsTrack()->SetHasChain(variant != 0);
107 }
108 };
109
110 class InvertedFilter : public ITestTrackFilter
111 {
112 public:
AppliesTo(uint8_t rideType,uint8_t trackType)113 bool AppliesTo(uint8_t rideType, uint8_t trackType) override
114 {
115 if (rideType == RIDE_TYPE_MULTI_DIMENSION_ROLLER_COASTER || rideType == RIDE_TYPE_FLYING_ROLLER_COASTER
116 || rideType == RIDE_TYPE_LAY_DOWN_ROLLER_COASTER)
117 {
118 return true;
119 }
120
121 return false;
122 }
123
Variations(uint8_t rideType,uint8_t trackType)124 int Variations(uint8_t rideType, uint8_t trackType) override
125 {
126 return 2;
127 }
128
VariantName(uint8_t rideType,uint8_t trackType,int variant)129 std::string VariantName(uint8_t rideType, uint8_t trackType, int variant) override
130 {
131 return String::Format("inverted:%d", variant);
132 }
133
ApplyTo(uint8_t rideType,uint8_t trackType,int variant,TileElement * tileElement,TileElement * surfaceElement,Ride * ride,rct_ride_entry * rideEntry)134 virtual void ApplyTo(
135 uint8_t rideType, uint8_t trackType, int variant, TileElement* tileElement, TileElement* surfaceElement, Ride* ride,
136 rct_ride_entry* rideEntry) override
137 {
138 if (variant == 0)
139 {
140 tileElement->AsTrack()->SetHasCableLift(false);
141 }
142 else
143 {
144 tileElement->AsTrack()->SetHasCableLift(true);
145 }
146 }
147 };
148
149 class EntranceStyleFilter : public ITestTrackFilter
150 {
151 public:
AppliesTo(uint8_t rideType,uint8_t trackType)152 bool AppliesTo(uint8_t rideType, uint8_t trackType) override
153 {
154 if (track_type_is_station(trackType))
155 {
156 return true;
157 }
158
159 return false;
160 }
161
Variations(uint8_t rideType,uint8_t trackType)162 int Variations(uint8_t rideType, uint8_t trackType) override
163 {
164 return 12;
165 }
166
VariantName(uint8_t rideType,uint8_t trackType,int variant)167 std::string VariantName(uint8_t rideType, uint8_t trackType, int variant) override
168 {
169 return String::Format("entranceStyle:%d", variant);
170 }
171
ApplyTo(uint8_t rideType,uint8_t trackType,int variant,TileElement * tileElement,TileElement * surfaceElement,Ride * ride,rct_ride_entry * rideEntry)172 virtual void ApplyTo(
173 uint8_t rideType, uint8_t trackType, int variant, TileElement* tileElement, TileElement* surfaceElement, Ride* ride,
174 rct_ride_entry* rideEntry) override
175 {
176 ride->entrance_style = variant;
177 RCT2_Rides[0].entrance_style = variant;
178 }
179 };
180
CallOriginal(uint8_t rideType,uint8_t trackType,uint8_t direction,uint8_t trackSequence,uint16_t height,TileElement * tileElement)181 static void CallOriginal(
182 uint8_t rideType, uint8_t trackType, uint8_t direction, uint8_t trackSequence, uint16_t height, TileElement* tileElement)
183 {
184 uint32_t* trackDirectionList = (uint32_t*)RideTypeTrackPaintFunctionsOld[rideType][trackType];
185 const uint8_t rideIndex = 0;
186
187 // Have to call from this point as it pushes esi and expects callee to pop it
188 RCT2_CALLPROC_X(
189 0x006C4934, rideType, (int)trackDirectionList, direction, height, (int)tileElement, rideIndex * sizeof(Ride),
190 trackSequence);
191 }
192
CallNew(uint8_t rideType,uint8_t trackType,uint8_t direction,uint8_t trackSequence,uint16_t height,TileElement * tileElement)193 static void CallNew(
194 uint8_t rideType, uint8_t trackType, uint8_t direction, uint8_t trackSequence, uint16_t height, TileElement* tileElement)
195 {
196 TRACK_PAINT_FUNCTION_GETTER newPaintFunctionGetter = GetRideTypeDescriptor(rideType).TrackPaintFunction;
197 TRACK_PAINT_FUNCTION newPaintFunction = newPaintFunctionGetter(trackType);
198
199 newPaintFunction(&gPaintSession, 0, trackSequence, direction, height, tileElement);
200 }
201
202 using TestFunction = uint8_t (*)(uint8_t, uint8_t, uint8_t, std::string*);
203
204 static uint8_t TestTrackElementPaintCalls(uint8_t rideType, uint8_t trackType, uint8_t trackSequence, std::string* error);
205
206 static uint8_t TestTrackElementSegmentSupportHeight(
207 uint8_t rideType, uint8_t trackType, uint8_t trackSequence, std::string* error);
208
209 static uint8_t TestTrackElementGeneralSupportHeight(
210 uint8_t rideType, uint8_t trackType, uint8_t trackSequence, std::string* error);
211
212 static uint8_t TestTrackElementSideTunnels(uint8_t rideType, uint8_t trackType, uint8_t trackSequence, std::string* error);
213
214 static uint8_t TestTrackElementVerticalTunnels(uint8_t rideType, uint8_t trackType, uint8_t trackSequence, std::string* error);
215
TestPaintTrackElement(uint8_t rideType,uint8_t trackType,std::string * out)216 uint8_t TestTrack::TestPaintTrackElement(uint8_t rideType, uint8_t trackType, std::string* out)
217 {
218 if (!Utils::rideSupportsTrackType(rideType, trackType))
219 {
220 return TEST_FAILED;
221 }
222
223 if (rideType == RIDE_TYPE_CHAIRLIFT)
224 {
225 if (track_type_is_station(trackType))
226 {
227 // These rides check neighbouring tiles for tracks
228 return TEST_SKIPPED;
229 }
230 }
231
232 int sequenceCount = Utils::getTrackSequenceCount(rideType, trackType);
233 std::string error = String::Format("rct2: 0x%08X\n", RideTypeTrackPaintFunctionsOld[rideType][trackType]);
234
235 uint8_t retVal = TEST_SUCCESS;
236
237 static TestFunction functions[] = {
238 TestTrackElementPaintCalls, TestTrackElementSegmentSupportHeight, TestTrackElementGeneralSupportHeight,
239 TestTrackElementSideTunnels, TestTrackElementVerticalTunnels,
240 };
241
242 for (int trackSequence = 0; trackSequence < sequenceCount; trackSequence++)
243 {
244 for (auto&& function : functions)
245 {
246 retVal = function(rideType, trackType, trackSequence, &error);
247
248 if (retVal != TEST_SUCCESS)
249 {
250 *out += error + "\n";
251 return retVal;
252 }
253 }
254 }
255
256 return retVal;
257 }
258
TestTrackElementPaintCalls(uint8_t rideType,uint8_t trackType,uint8_t trackSequence,std::string * error)259 static uint8_t TestTrackElementPaintCalls(uint8_t rideType, uint8_t trackType, uint8_t trackSequence, std::string* error)
260 {
261 uint16_t height = 3 * 16;
262
263 TileElement tileElement = {};
264 tileElement.SetType(TILE_ELEMENT_TYPE_TRACK);
265 tileElement.SetLastForTile(true);
266 tileElement.AsTrack()->SetTrackType(trackType);
267 tileElement.base_height = height / 16;
268 g_currently_drawn_item = &tileElement;
269
270 TileElement surfaceElement = {};
271 surfaceElement.SetType(TILE_ELEMENT_TYPE_SURFACE);
272 surfaceElement.base_height = MINIMUM_LAND_HEIGHT;
273 gSurfaceElement = &surfaceElement;
274 gDidPassSurface = true;
275
276 gPaintSession.CurrentlyDrawnItem = &tileElement;
277 gPaintSession.SurfaceElement = &surfaceElement;
278 gPaintSession.DidPassSurface = true;
279
280 TestPaint::ResetEnvironment();
281 TestPaint::ResetTunnels();
282
283 function_call callBuffer[256] = {};
284 int callCount = 0;
285
286 // TODO: test supports
287 // TODO: test flat rides
288 // TODO: test mazes
289 // TODO: test underground (Wooden RC)
290 // TODO: test station fences
291
292 std::vector<ITestTrackFilter*> filters;
293 filters.push_back(new CableLiftFilter());
294 filters.push_back(new ChainLiftFilter());
295 filters.push_back(new InvertedFilter());
296 filters.push_back(new EntranceStyleFilter());
297
298 std::vector<ITestTrackFilter*> activeFilters;
299
300 for (auto&& filter : filters)
301 {
302 if (filter->AppliesTo(rideType, trackType))
303 {
304 activeFilters.push_back(filter);
305 }
306 }
307
308 // Add an element so there's always something to add to
309 std::vector<uint8_t> filler;
310 filler.push_back(0);
311
312 std::vector<std::vector<uint8_t>> argumentPermutations;
313 argumentPermutations.push_back(filler);
314 for (size_t filterIndex = 0; filterIndex < activeFilters.size(); ++filterIndex)
315 {
316 ITestTrackFilter* filter = activeFilters[filterIndex];
317 uint8_t variantCount = filter->Variations(rideType, trackType);
318
319 std::vector<std::vector<uint8_t>> newArgumentPermutations;
320 for (int variant = 0; variant < variantCount; variant++)
321 {
322 for (auto&& oldPermutation : argumentPermutations)
323 {
324 std::vector<uint8_t> permutation;
325 permutation.insert(permutation.begin(), oldPermutation.begin(), oldPermutation.end());
326 permutation.push_back(variant);
327 newArgumentPermutations.push_back(permutation);
328 }
329 }
330
331 argumentPermutations.clear();
332 argumentPermutations.insert(
333 argumentPermutations.begin(), newArgumentPermutations.begin(), newArgumentPermutations.end());
334 }
335
336 for (auto&& arguments : argumentPermutations)
337 {
338 std::string baseCaseName = "[";
339
340 for (size_t filterIndex = 0; filterIndex < activeFilters.size(); ++filterIndex)
341 {
342 uint8_t& variant = arguments[1 + filterIndex];
343 baseCaseName += activeFilters[filterIndex]->VariantName(rideType, trackType, variant);
344 baseCaseName += " ";
345
346 activeFilters[filterIndex]->ApplyTo(
347 rideType, trackType, variant, &tileElement, &surfaceElement, &(gRideList[0]), gRideEntries[0]);
348 }
349
350 for (int currentRotation = 0; currentRotation < 4; currentRotation++)
351 {
352 gCurrentRotation = currentRotation;
353 RCT2_CurrentRotation = currentRotation;
354 gPaintSession.CurrentRotation = currentRotation;
355 for (int direction = 0; direction < 4; direction++)
356 {
357 RCT2_GLOBAL(0x009DE56A, int16_t) = 64; // x
358 RCT2_GLOBAL(0x009DE56E, int16_t) = 64; // y
359
360 std::string caseName = String::Format(
361 "%srotation:%d direction:%d trackSequence:%d]", baseCaseName.c_str(), currentRotation, direction,
362 trackSequence);
363
364 PaintIntercept::ClearCalls();
365 TestPaint::ResetSupportHeights();
366 gWoodenSupportsPrependTo = nullptr;
367
368 CallOriginal(rideType, trackType, direction, trackSequence, height, &tileElement);
369
370 callCount = PaintIntercept::GetCalls(callBuffer);
371 std::vector<function_call> oldCalls;
372 oldCalls.insert(oldCalls.begin(), callBuffer, callBuffer + callCount);
373
374 PaintIntercept::ClearCalls();
375 TestPaint::testClearIgnore();
376 TestPaint::ResetSupportHeights();
377 gPaintSession.WoodenSupportsPrependTo = nullptr;
378
379 CallNew(rideType, trackType, direction, trackSequence, height, &tileElement);
380
381 if (TestPaint::testIsIgnored(direction, trackSequence))
382 {
383 *error += String::Format("[ IGNORED ] %s\n", caseName.c_str());
384 continue;
385 }
386
387 callCount = PaintIntercept::GetCalls(callBuffer);
388 std::vector<function_call> newCalls;
389 newCalls.insert(newCalls.begin(), callBuffer, callBuffer + callCount);
390
391 bool success = true;
392 if (oldCalls.size() != newCalls.size())
393 {
394 *error += String::Format(
395 "Call counts don't match (was %d, expected %d). %s\n", newCalls.size(), oldCalls.size(),
396 caseName.c_str());
397 success = false;
398 }
399 else if (!FunctionCall::AssertsEquals(oldCalls, newCalls))
400 {
401 *error += String::Format("Calls don't match. %s\n", caseName.c_str());
402 success = false;
403 }
404
405 if (!success)
406 {
407 *error += " Expected:\n";
408 *error += Printer::PrintFunctionCalls(oldCalls, height);
409 *error += " Actual:\n";
410 *error += Printer::PrintFunctionCalls(newCalls, height);
411
412 return TEST_FAILED;
413 }
414 }
415 }
416 }
417
418 return TEST_SUCCESS;
419 }
420
TestTrackElementSegmentSupportHeight(uint8_t rideType,uint8_t trackType,uint8_t trackSequence,std::string * error)421 static uint8_t TestTrackElementSegmentSupportHeight(
422 uint8_t rideType, uint8_t trackType, uint8_t trackSequence, std::string* error)
423 {
424 uint16_t height = 3 * 16;
425
426 TileElement tileElement = {};
427 tileElement.SetType(TILE_ELEMENT_TYPE_TRACK);
428 tileElement.SetLastForTile(true);
429 tileElement.AsTrack()->SetTrackType(trackType);
430 tileElement.base_height = height / 16;
431 g_currently_drawn_item = &tileElement;
432
433 TileElement surfaceElement = {};
434 surfaceElement.SetType(TILE_ELEMENT_TYPE_SURFACE);
435 surfaceElement.base_height = MINIMUM_LAND_HEIGHT;
436 gSurfaceElement = &surfaceElement;
437 gDidPassSurface = true;
438
439 gPaintSession.CurrentlyDrawnItem = &tileElement;
440 gPaintSession.SurfaceElement = &surfaceElement;
441 gPaintSession.DidPassSurface = true;
442
443 TestPaint::ResetEnvironment();
444 TestPaint::ResetTunnels();
445
446 // TODO: Test Chainlift
447 // TODO: Test Maze
448 // TODO: Allow skip
449
450 std::string state = String::Format("[trackSequence:%d chainLift:%d]", trackSequence, 0);
451
452 std::vector<SegmentSupportCall> tileSegmentSupportCalls[4];
453
454 for (int direction = 0; direction < 4; direction++)
455 {
456 TestPaint::ResetSupportHeights();
457
458 CallOriginal(rideType, trackType, direction, trackSequence, height, &tileElement);
459
460 tileSegmentSupportCalls[direction] = SegmentSupportHeightCall::getSegmentCalls(gSupportSegments, direction);
461 }
462
463 std::vector<SegmentSupportCall> referenceCalls = tileSegmentSupportCalls[0];
464
465 if (!SegmentSupportHeightCall::CallsMatch(tileSegmentSupportCalls))
466 {
467 bool success = SegmentSupportHeightCall::FindMostCommonSupportCall(tileSegmentSupportCalls, &referenceCalls);
468 if (!success)
469 {
470 *error += String::Format("Original segment calls didn't match. %s\n", state.c_str());
471 for (int direction = 0; direction < 4; direction++)
472 {
473 *error += String::Format("# %d\n", direction);
474 *error += Printer::PrintSegmentSupportHeightCalls(tileSegmentSupportCalls[direction]);
475 }
476 return TEST_FAILED;
477 }
478 }
479
480 for (int direction = 0; direction < 4; direction++)
481 {
482 TestPaint::ResetSupportHeights();
483
484 TestPaint::testClearIgnore();
485 CallNew(rideType, trackType, direction, trackSequence, height, &tileElement);
486 if (TestPaint::testIsIgnored(direction, trackSequence))
487 {
488 continue;
489 }
490
491 std::vector<SegmentSupportCall> newCalls = SegmentSupportHeightCall::getSegmentCalls(
492 gPaintSession.SupportSegments, direction);
493 if (!SegmentSupportHeightCall::CallsEqual(referenceCalls, newCalls))
494 {
495 *error += String::Format("Segment support heights didn't match. [direction:%d] %s\n", direction, state.c_str());
496 *error += " Expected:\n";
497 *error += Printer::PrintSegmentSupportHeightCalls(referenceCalls);
498 *error += " Actual:\n";
499 *error += Printer::PrintSegmentSupportHeightCalls(newCalls);
500
501 return TEST_FAILED;
502 }
503 }
504
505 return TEST_SUCCESS;
506 }
507
TestTrackElementGeneralSupportHeight(uint8_t rideType,uint8_t trackType,uint8_t trackSequence,std::string * error)508 static uint8_t TestTrackElementGeneralSupportHeight(
509 uint8_t rideType, uint8_t trackType, uint8_t trackSequence, std::string* error)
510 {
511 uint16_t height = 3 * 16;
512
513 TileElement tileElement = {};
514 tileElement.SetType(TILE_ELEMENT_TYPE_TRACK);
515 tileElement.SetLastForTile(true);
516 tileElement.AsTrack()->SetTrackType(trackType);
517 tileElement.base_height = height / 16;
518 g_currently_drawn_item = &tileElement;
519
520 TileElement surfaceElement = {};
521 surfaceElement.SetType(TILE_ELEMENT_TYPE_SURFACE);
522 surfaceElement.base_height = MINIMUM_LAND_HEIGHT;
523 gSurfaceElement = &surfaceElement;
524 gDidPassSurface = true;
525
526 gPaintSession.CurrentlyDrawnItem = &tileElement;
527 gPaintSession.SurfaceElement = &surfaceElement;
528 gPaintSession.DidPassSurface = true;
529
530 TestPaint::ResetEnvironment();
531 TestPaint::ResetTunnels();
532
533 // TODO: Test Chainlift
534 // TODO: Test Maze
535 // TODO: Allow skip
536
537 std::string state = String::Format("[trackSequence:%d chainLift:%d]", trackSequence, 0);
538
539 SupportCall tileGeneralSupportCalls[4];
540 for (int direction = 0; direction < 4; direction++)
541 {
542 TestPaint::ResetSupportHeights();
543
544 CallOriginal(rideType, trackType, direction, trackSequence, height, &tileElement);
545
546 tileGeneralSupportCalls[direction].height = -1;
547 tileGeneralSupportCalls[direction].slope = -1;
548 if (gSupport.height != 0)
549 {
550 tileGeneralSupportCalls[direction].height = gSupport.height;
551 }
552 if (gSupport.slope != 0xFF)
553 {
554 tileGeneralSupportCalls[direction].slope = gSupport.slope;
555 }
556 }
557
558 SupportCall referenceCall = tileGeneralSupportCalls[0];
559 if (!GeneralSupportHeightCall::CallsMatch(tileGeneralSupportCalls))
560 {
561 bool success = GeneralSupportHeightCall::FindMostCommonSupportCall(tileGeneralSupportCalls, &referenceCall);
562 if (!success)
563 {
564 *error += String::Format("Original support calls didn't match. %s\n", state.c_str());
565 for (int i = 0; i < 4; ++i)
566 {
567 *error += String::Format("[%d, 0x%02X] ", tileGeneralSupportCalls[i].height, tileGeneralSupportCalls[i].slope);
568 }
569 *error += "\n";
570 return TEST_FAILED;
571 }
572 }
573
574 for (int direction = 0; direction < 4; direction++)
575 {
576 TestPaint::ResetSupportHeights();
577
578 TestPaint::testClearIgnore();
579 CallNew(rideType, trackType, direction, trackSequence, height, &tileElement);
580 if (TestPaint::testIsIgnored(direction, trackSequence))
581 {
582 continue;
583 }
584
585 if (referenceCall.height != -1)
586 {
587 if (gPaintSession.Support.height != referenceCall.height)
588 {
589 *error += String::Format(
590 "General support heights didn't match. (expected height + %d, actual: height + %d) [direction:%d] %s\n",
591 referenceCall.height - height, gPaintSession.Support.height - height, direction, state.c_str());
592 return TEST_FAILED;
593 }
594 }
595 if (referenceCall.slope != -1)
596 {
597 if (gPaintSession.Support.slope != referenceCall.slope)
598 {
599 *error += String::Format(
600 "General support slopes didn't match. (expected 0x%02X, actual: 0x%02X) [direction:%d] %s\n",
601 referenceCall.slope, gPaintSession.Support.slope, direction, state.c_str());
602 return TEST_FAILED;
603 }
604 }
605 }
606
607 return TEST_SUCCESS;
608 }
609
TestTrackElementSideTunnels(uint8_t rideType,uint8_t trackType,uint8_t trackSequence,std::string * error)610 static uint8_t TestTrackElementSideTunnels(uint8_t rideType, uint8_t trackType, uint8_t trackSequence, std::string* error)
611 {
612 uint16_t height = 3 * 16;
613
614 TileElement tileElement = {};
615 tileElement.SetType(TILE_ELEMENT_TYPE_TRACK);
616 tileElement.SetLastForTile(true);
617 tileElement.AsTrack()->SetTrackType(trackType);
618 tileElement.base_height = height / 16;
619 g_currently_drawn_item = &tileElement;
620
621 TileElement surfaceElement = {};
622 surfaceElement.SetType(TILE_ELEMENT_TYPE_SURFACE);
623 surfaceElement.base_height = MINIMUM_LAND_HEIGHT;
624 gSurfaceElement = &surfaceElement;
625 gDidPassSurface = true;
626
627 gPaintSession.CurrentlyDrawnItem = &tileElement;
628 gPaintSession.SurfaceElement = &surfaceElement;
629 gPaintSession.DidPassSurface = true;
630
631 TestPaint::ResetEnvironment();
632 TestPaint::ResetTunnels();
633
634 TunnelCall tileTunnelCalls[4][4];
635
636 // TODO: test inverted tracks
637
638 for (int direction = 0; direction < 4; direction++)
639 {
640 TestPaint::ResetTunnels();
641
642 for (int8_t offset = -8; offset <= 8; offset += 8)
643 {
644 CallOriginal(rideType, trackType, direction, trackSequence, height + offset, &tileElement);
645 }
646
647 uint8_t rightIndex = (direction + 1) % 4;
648 uint8_t leftIndex = direction;
649
650 for (int i = 0; i < 4; ++i)
651 {
652 tileTunnelCalls[direction][i].call = TUNNELCALL_SKIPPED;
653 }
654
655 bool err = false;
656 tileTunnelCalls[direction][rightIndex] = SideTunnelCall::ExtractTunnelCalls(
657 gRightTunnels, gRightTunnelCount, height, &err);
658
659 tileTunnelCalls[direction][leftIndex] = SideTunnelCall::ExtractTunnelCalls(
660 gLeftTunnels, gLeftTunnelCount, height, &err);
661
662 if (err)
663 {
664 *error += "Multiple tunnels on one side aren't supported.\n";
665 return TEST_FAILED;
666 }
667 }
668
669 TunnelCall newTileTunnelCalls[4][4];
670 for (int direction = 0; direction < 4; direction++)
671 {
672 TestPaint::ResetTunnels();
673
674 TestPaint::testClearIgnore();
675
676 for (int8_t offset = -8; offset <= 8; offset += 8)
677 {
678 // TODO: move tunnel pushing to interface so we don't have to check the output 3 times
679 CallNew(rideType, trackType, direction, trackSequence, height + offset, &tileElement);
680 }
681
682 uint8_t rightIndex = (direction + 1) % 4;
683 uint8_t leftIndex = direction;
684
685 for (int i = 0; i < 4; ++i)
686 {
687 newTileTunnelCalls[direction][i].call = TUNNELCALL_SKIPPED;
688 }
689
690 bool err = false;
691 newTileTunnelCalls[direction][rightIndex] = SideTunnelCall::ExtractTunnelCalls(
692 gPaintSession.RightTunnels, gPaintSession.RightTunnelCount, height, &err);
693 newTileTunnelCalls[direction][leftIndex] = SideTunnelCall::ExtractTunnelCalls(
694 gPaintSession.LeftTunnels, gPaintSession.LeftTunnelCount, height, &err);
695 if (err)
696 {
697 *error += "Multiple tunnels on one side aren't supported.\n";
698 return TEST_FAILED;
699 }
700 }
701
702 if (!SideTunnelCall::TunnelCallsLineUp(tileTunnelCalls))
703 {
704 // TODO: Check that new pattern uses the same tunnel group (round, big round, etc.)
705 *error += String::Format(
706 "Original tunnel calls don\'t line up. Skipping tunnel validation [trackSequence:%d].\n", trackSequence);
707 *error += Printer::PrintSideTunnelCalls(tileTunnelCalls);
708
709 if (!SideTunnelCall::TunnelCallsLineUp(newTileTunnelCalls))
710 {
711 *error += String::Format("Decompiled tunnel calls don\'t line up. [trackSequence:%d].\n", trackSequence);
712 *error += Printer::PrintSideTunnelCalls(newTileTunnelCalls);
713 return TEST_FAILED;
714 }
715
716 return TEST_SUCCESS;
717 }
718
719 TunnelCall referencePattern[4];
720 SideTunnelCall::GetTunnelCallReferencePattern(tileTunnelCalls, &referencePattern);
721
722 TunnelCall actualPattern[4];
723 SideTunnelCall::GetTunnelCallReferencePattern(newTileTunnelCalls, &actualPattern);
724
725 if (!SideTunnelCall::TunnelPatternsMatch(referencePattern, actualPattern))
726 {
727 *error += String::Format("Tunnel calls don't match expected pattern. [trackSequence:%d]\n", trackSequence);
728 *error += " Expected:\n";
729 *error += Printer::PrintSideTunnelCalls(tileTunnelCalls);
730 *error += " Actual:\n";
731 *error += Printer::PrintSideTunnelCalls(newTileTunnelCalls);
732 return TEST_FAILED;
733 }
734
735 return TEST_SUCCESS;
736 }
737
TestTrackElementVerticalTunnels(uint8_t rideType,uint8_t trackType,uint8_t trackSequence,std::string * error)738 static uint8_t TestTrackElementVerticalTunnels(uint8_t rideType, uint8_t trackType, uint8_t trackSequence, std::string* error)
739 {
740 uint16_t height = 3 * 16;
741
742 TileElement tileElement = {};
743 tileElement.SetType(TILE_ELEMENT_TYPE_TRACK);
744 tileElement.SetLastForTile(true);
745 tileElement.AsTrack()->SetTrackType(trackType);
746 tileElement.base_height = height / 16;
747 g_currently_drawn_item = &tileElement;
748
749 TileElement surfaceElement = {};
750 surfaceElement.SetType(TILE_ELEMENT_TYPE_SURFACE);
751 surfaceElement.base_height = MINIMUM_LAND_HEIGHT;
752 gSurfaceElement = &surfaceElement;
753 gDidPassSurface = true;
754
755 gPaintSession.CurrentlyDrawnItem = &tileElement;
756 gPaintSession.SurfaceElement = &surfaceElement;
757 gPaintSession.DidPassSurface = true;
758
759 TestPaint::ResetEnvironment();
760 TestPaint::ResetTunnels();
761
762 uint16_t verticalTunnelHeights[4];
763
764 for (int direction = 0; direction < 4; direction++)
765 {
766 uint8_t tunnelHeights[3] = { 0 };
767
768 for (uint8_t i = 0; i < 3; i++)
769 {
770 gVerticalTunnelHeight = 0;
771 CallOriginal(rideType, trackType, direction, trackSequence, height - 8 + i * 8, &tileElement);
772 tunnelHeights[i] = gVerticalTunnelHeight;
773 }
774
775 verticalTunnelHeights[direction] = VerticalTunnelCall::GetTunnelHeight(height, tunnelHeights);
776 }
777
778 if (!VerticalTunnelCall::HeightIsConsistent(verticalTunnelHeights))
779 {
780 *error += String::Format(
781 "Original vertical tunnel height is inconsistent, skipping test. [trackSequence:%d]\n", trackSequence);
782 return TEST_SUCCESS;
783 }
784
785 uint16_t referenceHeight = verticalTunnelHeights[0];
786
787 for (int direction = 0; direction < 4; direction++)
788 {
789 TestPaint::testClearIgnore();
790
791 testPaintVerticalTunnelHeight = 0;
792 CallNew(rideType, trackType, direction, trackSequence, height, &tileElement);
793
794 if (TestPaint::testIsIgnored(direction, trackSequence))
795 {
796 continue;
797 }
798
799 if (testPaintVerticalTunnelHeight != referenceHeight)
800 {
801 if (referenceHeight == 0)
802 {
803 *error += String::Format(
804 "Expected no tunnel. Actual: %s [trackSequence:%d]\n",
805 Printer::PrintHeightOffset(testPaintVerticalTunnelHeight, height).c_str(), trackSequence);
806 return TEST_FAILED;
807 }
808
809 *error += String::Format(
810 "Expected vertical tunnel height to be `%s`, was `%s`. [trackSequence:%d direction:%d]\n",
811 Printer::PrintHeightOffset(referenceHeight, height).c_str(),
812 Printer::PrintHeightOffset(testPaintVerticalTunnelHeight, height).c_str(), trackSequence, direction);
813
814 return TEST_FAILED;
815 }
816 }
817
818 return TEST_SUCCESS;
819 }
820