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 "Data.h"
11 #include "FunctionCall.hpp"
12 #include "PaintIntercept.hpp"
13 #include "SegmentSupportHeightCall.hpp"
14 #include "SideTunnelCall.hpp"
15 #include "String.hpp"
16 #include "Utils.hpp"
17 
18 #include <algorithm>
19 #include <cstdarg>
20 #include <cstring>
21 #include <openrct2/interface/Viewport.h>
22 #include <openrct2/rct2/RCT2.h>
23 #include <openrct2/ride/Ride.h>
24 #include <openrct2/ride/RideData.h>
25 #include <openrct2/ride/Track.h>
26 #include <openrct2/ride/TrackData.h>
27 #include <string>
28 #include <vector>
29 
30 class PaintCodeGenerator
31 {
32 public:
Generate(uint8_t rideType)33     int Generate(uint8_t rideType)
34     {
35         auto filename = "paint_" + std::to_string(rideType) + ".c";
36         FILE* file = fopen(filename.c_str(), "w");
37         if (file == nullptr)
38         {
39             fprintf(stderr, "Unable to save to ./%s\n", filename.c_str());
40             return 1;
41         }
42 
43         _file = file;
44         _rideType = rideType;
45         _rideName = std::string(RideCodeNames[rideType]);
46         Generate();
47 
48         fclose(file);
49         return 0;
50     }
51 
52 private:
53     std::string _rideName;
54     uint8_t _rideType;
55     FILE* _file;
56 
57     bool _conditionalSupports;
58     bool _invertedTrack;
59 
Generate()60     void Generate()
61     {
62         GenerateCopyrightHeader();
63         GenerateIncludes();
64         GenerateTrackFunctions();
65         GenerateMainFunction();
66     }
67 
GenerateCopyrightHeader()68     void GenerateCopyrightHeader()
69     {
70         const char* copyrights[] = {
71             "/*****************************************************************************",
72             " * Copyright (c) 2014-2018 OpenRCT2 developers",
73             " *",
74             " * For a complete list of all authors, please refer to contributors.md",
75             " * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2",
76             " *",
77             " * OpenRCT2 is licensed under the GNU General Public License version 3.",
78             " *****************************************************************************/",
79         };
80 
81         for (const auto copyright : copyrights)
82         {
83             WriteLine(0, copyright);
84         }
85         WriteLine();
86     }
87 
GenerateIncludes()88     void GenerateIncludes()
89     {
90         const char* includes[] = {
91             "../../drawing/Drawing.h",
92             "../../paint/supports.h",
93             "../../interface/Viewport.h",
94             "../../paint/tile_element/tile_element.h",
95             "../../paint/paint.h",
96             "../../sprites.h",
97             "../../world/Map.h",
98             "../../world/Sprite.h",
99             "../ride_data.h",
100             "../TrackData.h",
101             "../track_paint.h",
102         };
103         for (auto include : includes)
104         {
105             WriteLine(0, "#include \"%s\"", include);
106         }
107         WriteLine();
108     }
109 
GenerateTrackFunctions()110     void GenerateTrackFunctions()
111     {
112         for (int trackType = 0; trackType < 256; trackType++)
113         {
114             if (IsTrackTypeSupported(trackType))
115             {
116                 GenerateTrackFunction(trackType);
117                 WriteLine();
118             }
119 
120             if (trackType == TrackElemType::EndStation)
121             {
122                 const uint32_t* paintFunctionList = RideTypeTrackPaintFunctionsOld[_rideType];
123                 WriteLine(
124                     0, "/** rct2: 0x%08X, 0x%08X, 0x%08X */", paintFunctionList[TrackElemType::EndStation],
125                     paintFunctionList[TrackElemType::BeginStation], paintFunctionList[TrackElemType::MiddleStation]);
126                 WriteLine(
127                     0,
128                     "static void " + _rideName
129                         + "_track_station(uint8_t rideIndex, uint8_t trackSequence, uint8_t direction, int height, "
130                           "TileElement * tileElement)");
131                 WriteLine(0, "{");
132                 WriteLine(0, "}");
133                 WriteLine();
134             }
135         }
136     }
137 
GenerateTrackFunction(int trackType)138     void GenerateTrackFunction(int trackType)
139     {
140         WriteLine(0, "/** rct2: 0x%08X */", RideTypeTrackPaintFunctionsOld[_rideType][trackType]);
141         WriteLine(
142             0,
143             "static void " + GetTrackFunctionName(trackType)
144                 + "(uint8_t rideIndex, uint8_t trackSequence, uint8_t direction, int height, TileElement * tileElement)");
145         WriteLine(0, "{");
146         if (!GenerateMirrorCall(1, trackType))
147         {
148             if (_rideType == RIDE_TYPE_MULTI_DIMENSION_ROLLER_COASTER || _rideType == RIDE_TYPE_FLYING_ROLLER_COASTER
149                 || _rideType == RIDE_TYPE_LAY_DOWN_ROLLER_COASTER)
150             {
151                 WriteLine(1, "if (!tileElement->AsTrack()->IsInverted()) {");
152                 _invertedTrack = false;
153                 GenerateTrackFunctionBody(2, trackType);
154                 WriteLine(1, "} else {");
155                 _invertedTrack = true;
156                 GenerateTrackFunctionBody(2, trackType);
157                 WriteLine(1, "}");
158             }
159             else
160             {
161                 _invertedTrack = false;
162                 GenerateTrackFunctionBody(1, trackType);
163             }
164         }
165         WriteLine(0, "}");
166     }
167 
GenerateTrackFunctionBody(int tabs,int trackType)168     void GenerateTrackFunctionBody(int tabs, int trackType)
169     {
170         int numSequences = Utils::getTrackSequenceCount(_rideType, trackType);
171         if (numSequences > 1)
172         {
173             WriteLine(tabs, "switch (trackSequence) {");
174             for (int trackSequence = 0; trackSequence < numSequences; trackSequence++)
175             {
176                 WriteLine(tabs, "case %d:", trackSequence);
177                 GenerateTrackSequence(tabs + 1, trackType, trackSequence);
178                 WriteLine(tabs + 1, "break;");
179             }
180             WriteLine(tabs, "}");
181         }
182         else
183         {
184             GenerateTrackSequence(tabs, trackType, 0);
185         }
186     }
187 
GenerateMirrorCall(int tabs,int trackType)188     bool GenerateMirrorCall(int tabs, int trackType)
189     {
190         uint8_t mirrorTable[][3] = {
191             { 0, TrackElemType::Down25, TrackElemType::Up25 },
192             { 0, TrackElemType::Down60, TrackElemType::Up60 },
193             { 0, TrackElemType::FlatToDown25, TrackElemType::Up25ToFlat },
194             { 0, TrackElemType::Down25ToDown60, TrackElemType::Up60ToUp25 },
195             { 0, TrackElemType::Down60ToDown25, TrackElemType::Up25ToUp60 },
196             { 0, TrackElemType::Down25ToFlat, TrackElemType::FlatToUp25 },
197             { 0, TrackElemType::LeftBankToDown25, TrackElemType::Up25ToRightBank },
198             { 0, TrackElemType::RightBankToDown25, TrackElemType::Up25ToLeftBank },
199             { 0, TrackElemType::Down25ToLeftBank, TrackElemType::RightBankToUp25 },
200             { 0, TrackElemType::Down25ToRightBank, TrackElemType::LeftBankToUp25 },
201             { 0, TrackElemType::RightBank, TrackElemType::LeftBank },
202             { 0, TrackElemType::Down60ToFlat, TrackElemType::FlatToUp60 },
203             { 0, TrackElemType::FlatToDown60, TrackElemType::Up60ToFlat },
204             { 0, TrackElemType::Down25Covered, TrackElemType::Up25Covered },
205             { 0, TrackElemType::Down60Covered, TrackElemType::Up60Covered },
206             { 0, TrackElemType::FlatToDown25Covered, TrackElemType::Up25ToFlatCovered },
207             { 0, TrackElemType::Down25ToDown60Covered, TrackElemType::Up60ToUp25Covered },
208             { 0, TrackElemType::Down60ToDown25Covered, TrackElemType::Up25ToUp60Covered },
209             { 0, TrackElemType::Down25ToFlatCovered, TrackElemType::FlatToUp25Covered },
210             { 0, TrackElemType::Down25LeftBanked, TrackElemType::Up25RightBanked },
211             { 0, TrackElemType::Down25RightBanked, TrackElemType::Up25LeftBanked },
212             { 0, TrackElemType::Down90, TrackElemType::Up90 },
213             { 0, TrackElemType::Down90ToDown60, TrackElemType::Up60ToUp90 },
214             // { 0, TrackElemType::Down60ToDown90, TrackElemType::Up90ToUp60 },
215             { 0, TrackElemType::RightBankedDown25ToDown25, TrackElemType::Up25ToLeftBankedUp25 },
216             { 0, TrackElemType::LeftBankedDown25ToDown25, TrackElemType::Up25ToRightBankedUp25 },
217             { 0, TrackElemType::Down25ToRightBankedDown25, TrackElemType::LeftBankedUp25ToUp25 },
218             { 0, TrackElemType::Down25ToLeftBankedDown25, TrackElemType::RightBankedUp25ToUp25 },
219             { 0, TrackElemType::RightBankedDown25ToRightBankedFlat, TrackElemType::LeftBankedFlatToLeftBankedUp25 },
220             { 0, TrackElemType::LeftBankedDown25ToLeftBankedFlat, TrackElemType::RightBankedFlatToRightBankedUp25 },
221             { 0, TrackElemType::RightBankedFlatToRightBankedDown25, TrackElemType::LeftBankedUp25ToLeftBankedFlat },
222             { 0, TrackElemType::LeftBankedFlatToLeftBankedDown25, TrackElemType::RightBankedUp25ToRightBankedFlat },
223             { 0, TrackElemType::RightBankedDown25ToFlat, TrackElemType::FlatToLeftBankedUp25 },
224             { 0, TrackElemType::LeftBankedDown25ToFlat, TrackElemType::FlatToRightBankedUp25 },
225             { 0, TrackElemType::FlatToRightBankedDown25, TrackElemType::LeftBankedUp25ToFlat },
226             { 0, TrackElemType::FlatToLeftBankedDown25, TrackElemType::RightBankedUp25ToFlat },
227 
228             { 1, TrackElemType::RightQuarterTurn5Tiles, TrackElemType::LeftQuarterTurn5Tiles },
229             { 1, TrackElemType::BankedRightQuarterTurn5Tiles, TrackElemType::BankedLeftQuarterTurn5Tiles },
230             { 1, TrackElemType::RightQuarterTurn5TilesDown25, TrackElemType::LeftQuarterTurn5TilesUp25 },
231             { 1, TrackElemType::RightQuarterTurn5TilesCovered, TrackElemType::LeftQuarterTurn5TilesCovered },
232             { 1, TrackElemType::RightBankedQuarterTurn5TileDown25, TrackElemType::LeftBankedQuarterTurn5TileUp25 },
233             { 2, TrackElemType::LeftQuarterTurn5TilesDown25, TrackElemType::RightQuarterTurn5TilesUp25 },
234             { 2, TrackElemType::LeftBankedQuarterTurn5TileDown25, TrackElemType::RightBankedQuarterTurn5TileUp25 },
235 
236             { 3, TrackElemType::RightQuarterTurn3Tiles, TrackElemType::LeftQuarterTurn3Tiles },
237             { 3, TrackElemType::RightBankedQuarterTurn3Tiles, TrackElemType::LeftBankedQuarterTurn3Tiles },
238             { 3, TrackElemType::RightQuarterTurn3TilesDown25, TrackElemType::LeftQuarterTurn3TilesUp25 },
239             { 3, TrackElemType::RightQuarterTurn3TilesCovered, TrackElemType::LeftQuarterTurn3TilesCovered },
240             { 3, TrackElemType::RightBankedQuarterTurn3TileDown25, TrackElemType::LeftBankedQuarterTurn3TileUp25 },
241             { 4, TrackElemType::LeftQuarterTurn3TilesDown25, TrackElemType::RightQuarterTurn3TilesUp25 },
242             { 4, TrackElemType::LeftBankedQuarterTurn3TileDown25, TrackElemType::RightBankedQuarterTurn3TileUp25 },
243 
244             { 5, TrackElemType::RightQuarterTurn1Tile, TrackElemType::LeftQuarterTurn1Tile },
245             { 5, TrackElemType::RightQuarterTurn1TileDown60, TrackElemType::LeftQuarterTurn1TileUp60 },
246             { 5, TrackElemType::RightQuarterTurn1TileDown90, TrackElemType::LeftQuarterTurn1TileUp90 },
247             { 6, TrackElemType::LeftQuarterTurn1TileDown60, TrackElemType::RightQuarterTurn1TileUp60 },
248             { 6, TrackElemType::LeftQuarterTurn1TileDown90, TrackElemType::RightQuarterTurn1TileUp90 },
249 
250             { 7, TrackElemType::RightEighthToOrthogonal, TrackElemType::LeftEighthToDiag },
251             { 7, TrackElemType::RightEighthBankToOrthogonal, TrackElemType::LeftEighthBankToDiag },
252             { 8, TrackElemType::LeftEighthToOrthogonal, TrackElemType::RightEighthToDiag },
253             { 8, TrackElemType::LeftEighthBankToOrthogonal, TrackElemType::RightEighthBankToDiag },
254 
255             { 9, TrackElemType::RightHalfBankedHelixDownSmall, TrackElemType::LeftHalfBankedHelixUpSmall },
256             { 10, TrackElemType::LeftHalfBankedHelixDownSmall, TrackElemType::RightHalfBankedHelixUpSmall },
257             { 11, TrackElemType::RightHalfBankedHelixDownLarge, TrackElemType::LeftHalfBankedHelixUpLarge },
258             { 12, TrackElemType::LeftHalfBankedHelixDownLarge, TrackElemType::RightHalfBankedHelixUpLarge },
259 
260             { 13, TrackElemType::Down60ToFlatLongBase, TrackElemType::FlatToUp60LongBase },
261             { 13, TrackElemType::FlatToDown60LongBase, TrackElemType::Up60ToFlatLongBase },
262 
263             { 14, TrackElemType::RightCorkscrewDown, TrackElemType::LeftCorkscrewUp },
264             { 15, TrackElemType::LeftCorkscrewDown, TrackElemType::RightCorkscrewUp },
265 
266             { 16, TrackElemType::HalfLoopDown, TrackElemType::HalfLoopUp },
267 
268             { 17, TrackElemType::InvertedFlatToDown90QuarterLoop, TrackElemType::Up90ToInvertedFlatQuarterLoop },
269 
270             { 18, TrackElemType::LeftBarrelRollDownToUp, TrackElemType::LeftBarrelRollUpToDown },
271             { 18, TrackElemType::RightBarrelRollDownToUp, TrackElemType::RightBarrelRollUpToDown },
272 
273             { 19, TrackElemType::LeftLargeHalfLoopDown, TrackElemType::LeftLargeHalfLoopUp },
274             { 19, TrackElemType::RightLargeHalfLoopDown, TrackElemType::RightLargeHalfLoopUp },
275         };
276 
277         for (size_t i = 0; i < (sizeof(mirrorTable) / sizeof(mirrorTable[0])); i++)
278         {
279             if (mirrorTable[i][1] == trackType)
280             {
281                 std::string destFuncName = GetTrackFunctionName(mirrorTable[i][2]);
282                 switch (mirrorTable[i][0])
283                 {
284                     case 0:
285                         WriteLine(
286                             tabs, "%s(rideIndex, trackSequence, (direction + 2) & 3, height, tileElement);",
287                             destFuncName.c_str());
288                         break;
289                     case 1:
290                         WriteLine(tabs, "trackSequence = mapLeftQuarterTurn5TilesToRightQuarterTurn5Tiles[trackSequence];");
291                         WriteLine(
292                             tabs, "%s(rideIndex, trackSequence, (direction - 1) & 3, height, tileElement);",
293                             destFuncName.c_str());
294                         break;
295                     case 2:
296                         WriteLine(tabs, "trackSequence = mapLeftQuarterTurn5TilesToRightQuarterTurn5Tiles[trackSequence];");
297                         WriteLine(
298                             tabs, "%s(rideIndex, trackSequence, (direction + 1) & 3, height, tileElement);",
299                             destFuncName.c_str());
300                         break;
301                     case 3:
302                         WriteLine(tabs, "trackSequence = mapLeftQuarterTurn3TilesToRightQuarterTurn3Tiles[trackSequence];");
303                         WriteLine(
304                             tabs, "%s(rideIndex, trackSequence, (direction - 1) & 3, height, tileElement);",
305                             destFuncName.c_str());
306                         break;
307                     case 4:
308                         WriteLine(tabs, "trackSequence = mapLeftQuarterTurn3TilesToRightQuarterTurn3Tiles[trackSequence];");
309                         WriteLine(
310                             tabs, "%s(rideIndex, trackSequence, (direction + 1) & 3, height, tileElement);",
311                             destFuncName.c_str());
312                         break;
313                     case 5:
314                         WriteLine(
315                             tabs, "%s(rideIndex, trackSequence, (direction - 1) & 3, height, tileElement);",
316                             destFuncName.c_str());
317                         break;
318                     case 6:
319                         WriteLine(
320                             tabs, "%s(rideIndex, trackSequence, (direction + 1) & 3, height, tileElement);",
321                             destFuncName.c_str());
322                         break;
323                     case 7:
324                         WriteLine(tabs, "trackSequence = mapLeftEighthTurnToOrthogonal[trackSequence];");
325                         WriteLine(
326                             tabs, "%s(rideIndex, trackSequence, (direction + 3) & 3, height, tileElement);",
327                             destFuncName.c_str());
328                         break;
329                     case 8:
330                         WriteLine(tabs, "trackSequence = mapLeftEighthTurnToOrthogonal[trackSequence];");
331                         WriteLine(
332                             tabs, "%s(rideIndex, trackSequence, (direction + 2) & 3, height, tileElement);",
333                             destFuncName.c_str());
334                         break;
335                     case 9:
336                         WriteLine(tabs, "if (trackSequence >= 4) {");
337                         WriteLine(tabs + 1, "trackSequence -= 4;");
338                         WriteLine(tabs + 1, "direction = (direction + 1) & 3;");
339                         WriteLine(tabs, "}");
340                         WriteLine(tabs, "trackSequence = mapLeftQuarterTurn3TilesToRightQuarterTurn3Tiles[trackSequence];");
341                         WriteLine(
342                             tabs, "%s(rideIndex, trackSequence, (direction - 1) & 3, height, tileElement);",
343                             destFuncName.c_str());
344                         break;
345                     case 10:
346                         WriteLine(tabs, "if (trackSequence >= 4) {");
347                         WriteLine(tabs + 1, "trackSequence -= 4;");
348                         WriteLine(tabs + 1, "direction = (direction - 1) & 3;");
349                         WriteLine(tabs, "}");
350                         WriteLine(tabs, "trackSequence = mapLeftQuarterTurn3TilesToRightQuarterTurn3Tiles[trackSequence];");
351                         WriteLine(
352                             tabs, "%s(rideIndex, trackSequence, (direction + 1) & 3, height, tileElement);",
353                             destFuncName.c_str());
354                         break;
355                     case 11:
356                         WriteLine(tabs, "if (trackSequence >= 7) {");
357                         WriteLine(tabs + 1, "trackSequence -= 7;");
358                         WriteLine(tabs + 1, "direction = (direction + 1) & 3;");
359                         WriteLine(tabs, "}");
360                         WriteLine(tabs, "trackSequence = mapLeftQuarterTurn5TilesToRightQuarterTurn5Tiles[trackSequence];");
361                         WriteLine(
362                             tabs, "%s(rideIndex, trackSequence, (direction - 1) & 3, height, tileElement);",
363                             destFuncName.c_str());
364                         break;
365                     case 12:
366                         WriteLine(tabs, "if (trackSequence >= 7) {");
367                         WriteLine(tabs + 1, "trackSequence -= 7;");
368                         WriteLine(tabs + 1, "direction = (direction - 1) & 3;");
369                         WriteLine(tabs, "}");
370                         WriteLine(tabs, "trackSequence = mapLeftQuarterTurn5TilesToRightQuarterTurn5Tiles[trackSequence];");
371                         WriteLine(
372                             tabs, "%s(rideIndex, trackSequence, (direction + 1) & 3, height, tileElement);",
373                             destFuncName.c_str());
374                         break;
375                     case 13:
376                         WriteLine(
377                             tabs, "%s(rideIndex, 3 - trackSequence, (direction + 2) & 3, height, tileElement);",
378                             destFuncName.c_str());
379                         break;
380                     case 14:
381                         WriteLine(
382                             tabs, "%s(rideIndex, 2 - trackSequence, (direction - 1) & 3, height, tileElement);",
383                             destFuncName.c_str());
384                         break;
385                     case 15:
386                         WriteLine(
387                             tabs, "%s(rideIndex, 2 - trackSequence, (direction + 1) & 3, height, tileElement);",
388                             destFuncName.c_str());
389                         break;
390                     case 16:
391                         WriteLine(
392                             tabs, "%s(rideIndex, 3 - trackSequence, direction, height, tileElement);", destFuncName.c_str());
393                         break;
394                     case 17:
395                         WriteLine(
396                             tabs, "%s(rideIndex, 2 - trackSequence, direction, height, tileElement);", destFuncName.c_str());
397                         break;
398                     case 18:
399                         WriteLine(
400                             tabs, "%s(rideIndex, 2 - trackSequence, (direction + 2) & 3, height, tileElement);",
401                             destFuncName.c_str());
402                         break;
403                     case 19:
404                         WriteLine(
405                             tabs, "%s(rideIndex, 6 - trackSequence, direction, height, tileElement);", destFuncName.c_str());
406                         break;
407                 }
408                 return true;
409             }
410         }
411         return false;
412     }
413 
ExtractMetalSupportCalls(std::vector<function_call> calls[4],std::vector<function_call> output[4])414     void ExtractMetalSupportCalls(std::vector<function_call> calls[4], std::vector<function_call> output[4])
415     {
416         for (int direction = 0; direction < 4; direction++)
417         {
418             auto cutPoint = std::find_if(calls[direction].begin(), calls[direction].end(), [](function_call call) {
419                 return (call.function == SUPPORTS_METAL_A || call.function == SUPPORTS_METAL_B);
420             });
421             output[direction].insert(output[direction].begin(), cutPoint, calls[direction].end());
422             calls[direction].erase(cutPoint, calls[direction].end());
423         }
424     }
425 
GenerateTrackSequence(int tabs,int trackType,int trackSequence)426     void GenerateTrackSequence(int tabs, int trackType, int trackSequence)
427     {
428         int height = 48;
429         _conditionalSupports = false;
430         bool blockSegmentsBeforeSupports = false;
431 
432         std::vector<function_call> calls[4], chainLiftCalls[4], cableLiftCalls[4];
433         TunnelCall tileTunnelCalls[4][4];
434         int16_t verticalTunnelHeights[4];
435         std::vector<SegmentSupportCall> segmentSupportCalls[4];
436         support_height generalSupports[4] = {};
437         for (int direction = 0; direction < 4; direction++)
438         {
439             TileElement tileElement = {};
440             tileElement.SetType(TILE_ELEMENT_TYPE_TRACK);
441             tileElement.SetLastForTile(true);
442             tileElement.AsTrack()->SetTrackType(trackType);
443             tileElement.base_height = 3;
444             if (_invertedTrack)
445             {
446                 tileElement.AsTrack()->SetInverted(true);
447             }
448             g_currently_drawn_item = &tileElement;
449 
450             // Set position
451             RCT2_GLOBAL(0x009DE56A, int16_t) = 64;
452             RCT2_GLOBAL(0x009DE56E, int16_t) = 64;
453 
454             function_call callBuffer[256] = {};
455             PaintIntercept::ClearCalls();
456             CallOriginal(trackType, direction, trackSequence, height, &tileElement);
457             int numCalls = PaintIntercept::GetCalls(callBuffer);
458             calls[direction].insert(calls[direction].begin(), callBuffer, callBuffer + numCalls);
459 
460             for (auto&& call : calls[direction])
461             {
462                 if (call.function == SET_SEGMENT_HEIGHT)
463                 {
464                     blockSegmentsBeforeSupports = true;
465                     break;
466                 }
467             }
468 
469             segmentSupportCalls[direction] = SegmentSupportHeightCall::getSegmentCalls(gSupportSegments, direction);
470             generalSupports[direction] = gSupport;
471             if (gSupport.slope != 0xFF && gSupport.height != 0)
472             {
473                 generalSupports[direction].height -= height;
474             }
475 
476             // Get chain lift calls
477             tileElement.AsTrack()->SetHasChain(true);
478             PaintIntercept::ClearCalls();
479             CallOriginal(trackType, direction, trackSequence, height, &tileElement);
480             numCalls = PaintIntercept::GetCalls(callBuffer);
481             chainLiftCalls[direction].insert(chainLiftCalls[direction].begin(), callBuffer, callBuffer + numCalls);
482 
483             // Get cable lift calls (giga coaster only)
484             if (_rideType == RIDE_TYPE_GIGA_COASTER)
485             {
486                 tileElement.type = 0;
487                 tileElement.AsTrack()->SetHasCableLift(true);
488                 PaintIntercept::ClearCalls();
489                 CallOriginal(trackType, direction, trackSequence, height, &tileElement);
490                 numCalls = PaintIntercept::GetCalls(callBuffer);
491                 cableLiftCalls[direction].insert(cableLiftCalls[direction].begin(), callBuffer, callBuffer + numCalls);
492             }
493 
494             // Check a different position for direction 0 to see if supports are different
495             if (direction == 0)
496             {
497                 RCT2_GLOBAL(0x009DE56A, int16_t) = 64 + 32;
498                 RCT2_GLOBAL(0x009DE56E, int16_t) = 64;
499                 tileElement.type = 0;
500                 tileElement.AsTrack()->SetHasCableLift(false);
501                 PaintIntercept::ClearCalls();
502                 CallOriginal(trackType, direction, trackSequence, height, &tileElement);
503                 numCalls = PaintIntercept::GetCalls(callBuffer);
504                 std::vector<function_call> checkCalls = std::vector<function_call>(callBuffer, callBuffer + numCalls);
505                 if (!CompareFunctionCalls(checkCalls, calls[direction]))
506                 {
507                     _conditionalSupports = true;
508                 }
509             }
510 
511             GetTunnelCalls(trackType, direction, trackSequence, height, &tileElement, tileTunnelCalls, verticalTunnelHeights);
512         }
513 
514         std::vector<function_call> supportCalls[4], chainLiftSupportCalls[4], cableLiftSupportCalls[4];
515         if (blockSegmentsBeforeSupports)
516         {
517             ExtractMetalSupportCalls(calls, supportCalls);
518             ExtractMetalSupportCalls(cableLiftCalls, cableLiftSupportCalls);
519             ExtractMetalSupportCalls(chainLiftCalls, chainLiftSupportCalls);
520         }
521 
522         if (_rideType == RIDE_TYPE_GIGA_COASTER && !CompareFunctionCalls(calls, cableLiftCalls))
523         {
524             WriteLine(tabs, "if (tileElement->AsTrack()->HasCableLift()) {");
525             GenerateCalls(tabs + 1, cableLiftCalls, height);
526 
527             if (!CompareFunctionCalls(calls, chainLiftCalls))
528             {
529                 WriteLine(tabs, "} else if (tileElement->AsTrack()->HasChain()) {");
530                 GenerateCalls(tabs + 1, chainLiftCalls, height);
531             }
532 
533             WriteLine(tabs, "} else {");
534             GenerateCalls(tabs + 1, calls, height);
535             WriteLine(tabs, "}");
536         }
537         else if (!CompareFunctionCalls(calls, chainLiftCalls))
538         {
539             WriteLine(tabs, "if (tileElement->AsTrack()->HasChain()) {");
540             GenerateCalls(tabs + 1, chainLiftCalls, height);
541             WriteLine(tabs, "} else {");
542             GenerateCalls(tabs + 1, calls, height);
543             WriteLine(tabs, "}");
544         }
545         else
546         {
547             GenerateCalls(tabs, calls, height);
548         }
549 
550         if (blockSegmentsBeforeSupports)
551         {
552             if (_rideType == RIDE_TYPE_GIGA_COASTER && !CompareFunctionCalls(supportCalls, cableLiftSupportCalls))
553             {
554                 printf("Error: Supports differ for cable lift.\n");
555             }
556             else if (!CompareFunctionCalls(supportCalls, chainLiftSupportCalls))
557             {
558                 printf("Error: Supports differ for chain lift\n");
559             }
560             WriteLine();
561             GenerateSegmentSupportCall(tabs, segmentSupportCalls);
562 
563             bool conditionalSupports = _conditionalSupports;
564             _conditionalSupports = false;
565             if (conditionalSupports)
566             {
567                 WriteLine(tabs, "if (track_paint_util_should_paint_supports(gPaintMapPosition)) {");
568                 tabs++;
569             }
570             GenerateCalls(tabs, supportCalls, height);
571             if (conditionalSupports)
572             {
573                 tabs--;
574                 WriteLine(tabs, "}");
575             }
576             WriteLine();
577         }
578 
579         GenerateTunnelCall(tabs, tileTunnelCalls, verticalTunnelHeights);
580         if (!blockSegmentsBeforeSupports)
581         {
582             GenerateSegmentSupportCall(tabs, segmentSupportCalls);
583         }
584         GenerateGeneralSupportCall(tabs, generalSupports);
585     }
586 
GenerateCalls(int tabs,std::vector<function_call> calls[4],int height)587     void GenerateCalls(int tabs, std::vector<function_call> calls[4], int height)
588     {
589         std::vector<function_call> commonCalls = TrimCommonCallsEnd(calls);
590 
591         int totalCalls = 0;
592         for (int direction = 0; direction < 4; direction++)
593         {
594             totalCalls += calls[direction].size();
595         }
596         if (totalCalls != 0)
597         {
598             WriteLine(tabs, "switch (direction) {");
599             for (int direction = 0; direction < 4; direction++)
600             {
601                 if (calls[direction].empty())
602                     continue;
603 
604                 WriteLine(tabs, "case %d:", direction);
605                 for (int d2 = direction + 1; d2 < 4; d2++)
606                 {
607                     if (CompareFunctionCalls(calls[direction], calls[d2]))
608                     {
609                         // Clear identical other direction calls and add case for it
610                         calls[d2].clear();
611                         WriteLine(tabs, "case %d:", d2);
612                     }
613                 }
614 
615                 for (auto call : calls[direction])
616                 {
617                     GenerateCalls(tabs + 1, call, height, direction);
618                 }
619                 WriteLine(tabs + 1, "break;");
620             }
621             WriteLine(tabs, "}");
622         }
623 
624         for (auto call : commonCalls)
625         {
626             GenerateCalls(tabs, call, height, 0);
627         }
628     }
629 
GenerateCalls(int tabs,const function_call & call,int height,int direction)630     void GenerateCalls(int tabs, const function_call& call, int height, int direction)
631     {
632         switch (call.function)
633         {
634             case PAINT_98196C:
635             case PAINT_98197C:
636             case PAINT_98198C:
637             case PAINT_98199C:
638                 GeneratePaintCall(tabs, call, height, direction);
639                 break;
640             case SUPPORTS_METAL_A:
641             case SUPPORTS_METAL_B:
642             {
643                 int callTabs = tabs;
644                 if (_conditionalSupports)
645                 {
646                     WriteLine(tabs, "if (track_paint_util_should_paint_supports(gPaintMapPosition)) {");
647                     callTabs++;
648                 }
649 
650                 WriteLine(
651                     callTabs, "%s(%d, %d, %d, height%s, %s);", GetFunctionCallName(call.function), call.supports.type,
652                     call.supports.segment, call.supports.special,
653                     GetOffsetExpressionString(call.supports.height - height).c_str(),
654                     GetImageIdString(call.supports.colour_flags).c_str());
655 
656                 if (_conditionalSupports)
657                 {
658                     WriteLine(tabs, "}");
659                 }
660                 break;
661             }
662             case SUPPORTS_WOOD_A:
663             case SUPPORTS_WOOD_B:
664                 WriteLine(
665                     tabs, "%s(%d, %d, height%s, %s, NULL);", GetFunctionCallName(call.function), call.supports.type,
666                     call.supports.special, GetOffsetExpressionString(call.supports.height - height).c_str(),
667                     GetImageIdString(call.supports.colour_flags).c_str());
668                 break;
669         }
670     }
671 
GeneratePaintCall(int tabs,const function_call & call,int height,int direction)672     void GeneratePaintCall(int tabs, const function_call& call, int height, int direction)
673     {
674         const char* funcName = GetFunctionCallName(call.function);
675         std::string imageId = GetImageIdString(call.paint.image_id);
676         std::string s = String::Format("%s_rotated(direction, %s, ", funcName, imageId.c_str());
677         s += FormatXYSwap(call.paint.offset.x, call.paint.offset.y, direction);
678         s += ", ";
679         s += FormatXYSwap(call.paint.bound_box_length.x, call.paint.bound_box_length.y, direction);
680         s += String::Format(
681             ", %d, height%s", call.paint.bound_box_length.z, GetOffsetExpressionString(call.paint.z_offset - height).c_str());
682 
683         if (call.function != PAINT_98196C)
684         {
685             s += ", ";
686             s += FormatXYSwap(call.paint.bound_box_offset.x, call.paint.bound_box_offset.y, direction);
687             s += String::Format(", height%s", GetOffsetExpressionString(call.paint.bound_box_offset.z - height).c_str());
688         }
689 
690         s += ");";
691         WriteLine(tabs, s);
692     }
693 
FormatXYSwap(int16_t x,int16_t y,int direction)694     std::string FormatXYSwap(int16_t x, int16_t y, int direction)
695     {
696         if (direction & 1)
697         {
698             return String::Format("%d, %d", y, x);
699         }
700         else
701         {
702             return String::Format("%d, %d", x, y);
703         }
704     }
705 
TrimCommonCallsEnd(std::vector<function_call> calls[4])706     std::vector<function_call> TrimCommonCallsEnd(std::vector<function_call> calls[4])
707     {
708         std::vector<function_call> commonCalls;
709 
710         while (calls[0].size() != 0)
711         {
712             function_call lastCall = calls[0].back();
713             for (int i = 0; i < 4; i++)
714             {
715                 if (calls[i].empty() || !CompareFunctionCall(calls[i].back(), lastCall))
716                 {
717                     goto finished;
718                 }
719             }
720             for (int i = 0; i < 4; i++)
721             {
722                 calls[i].pop_back();
723             }
724             commonCalls.push_back(lastCall);
725         }
726 
727     finished:
728         return commonCalls;
729     }
730 
CompareFunctionCalls(const std::vector<function_call> a[4],const std::vector<function_call> b[4])731     bool CompareFunctionCalls(const std::vector<function_call> a[4], const std::vector<function_call> b[4])
732     {
733         for (size_t i = 0; i < 4; i++)
734         {
735             if (!CompareFunctionCalls(a[i], b[i]))
736             {
737                 return false;
738             }
739         }
740         return true;
741     }
742 
CompareFunctionCalls(const std::vector<function_call> & a,const std::vector<function_call> & b)743     bool CompareFunctionCalls(const std::vector<function_call>& a, const std::vector<function_call>& b)
744     {
745         if (a.size() != b.size())
746             return false;
747         for (size_t i = 0; i < a.size(); i++)
748         {
749             if (!CompareFunctionCall(a[i], b[i]))
750             {
751                 return false;
752             }
753         }
754         return true;
755     }
756 
CompareFunctionCall(const function_call a,const function_call & b)757     bool CompareFunctionCall(const function_call a, const function_call& b)
758     {
759         return FunctionCall::AssertsEquals(a, b);
760     }
761 
GetFunctionCallName(int function)762     const char* GetFunctionCallName(int function)
763     {
764         const char* functionNames[] = {
765             "sub_98196C",
766             "sub_98197C",
767             "sub_98198C",
768             "sub_98199C",
769             "metal_a_supports_paint_setup",
770             "metal_b_supports_paint_setup",
771             "wooden_a_supports_paint_setup",
772             "wooden_b_supports_paint_setup",
773         };
774         return functionNames[function];
775     }
776 
GetTunnelCalls(int trackType,int direction,int trackSequence,int height,TileElement * tileElement,TunnelCall tileTunnelCalls[4][4],int16_t verticalTunnelHeights[4])777     bool GetTunnelCalls(
778         int trackType, int direction, int trackSequence, int height, TileElement* tileElement, TunnelCall tileTunnelCalls[4][4],
779         int16_t verticalTunnelHeights[4])
780     {
781         TestPaint::ResetTunnels();
782 
783         for (int offset = -8; offset <= 8; offset += 8)
784         {
785             CallOriginal(trackType, direction, trackSequence, height + offset, tileElement);
786         }
787 
788         uint8_t rightIndex = (4 - direction) % 4;
789         uint8_t leftIndex = (rightIndex + 1) % 4;
790 
791         for (int i = 0; i < 4; ++i)
792         {
793             tileTunnelCalls[direction][i].call = TUNNELCALL_SKIPPED;
794         }
795         if (gRightTunnelCount == 0)
796         {
797             tileTunnelCalls[direction][rightIndex].call = TUNNELCALL_NONE;
798         }
799         else if (gRightTunnelCount == 3)
800         {
801             tileTunnelCalls[direction][rightIndex].call = TUNNELCALL_CALL;
802             tileTunnelCalls[direction][rightIndex].offset = SideTunnelCall::GetTunnelOffset(height, gRightTunnels);
803             tileTunnelCalls[direction][rightIndex].type = gRightTunnels[0].type;
804         }
805         else
806         {
807             printf("Multiple tunnels on one side aren't supported.\n");
808             return false;
809         }
810 
811         if (gLeftTunnelCount == 0)
812         {
813             tileTunnelCalls[direction][leftIndex].call = TUNNELCALL_NONE;
814         }
815         else if (gLeftTunnelCount == 3)
816         {
817             tileTunnelCalls[direction][leftIndex].call = TUNNELCALL_CALL;
818             tileTunnelCalls[direction][leftIndex].offset = SideTunnelCall::GetTunnelOffset(height, gLeftTunnels);
819             tileTunnelCalls[direction][leftIndex].type = gLeftTunnels[0].type;
820         }
821         else
822         {
823             printf("Multiple tunnels on one side aren't supported.\n");
824             return false;
825         }
826 
827         // Vertical tunnel
828         gVerticalTunnelHeight = 0;
829         CallOriginal(trackType, direction, trackSequence, height, tileElement);
830 
831         int verticalTunnelHeight = gVerticalTunnelHeight;
832         if (verticalTunnelHeight != 0)
833         {
834             verticalTunnelHeight = (verticalTunnelHeight * 16) - height;
835         }
836         verticalTunnelHeights[direction] = verticalTunnelHeight;
837         return true;
838     }
839 
GenerateTunnelCall(int tabs,TunnelCall tileTunnelCalls[4][4],int16_t verticalTunnelHeights[4])840     void GenerateTunnelCall(int tabs, TunnelCall tileTunnelCalls[4][4], int16_t verticalTunnelHeights[4])
841     {
842         constexpr uint8_t TunnelLeft = 0;
843         constexpr uint8_t TunnelRight = 1;
844         constexpr uint8_t TunnelNA = 255;
845         static const uint8_t dsToWay[4][4] = {
846             { TunnelRight, TunnelLeft, TunnelNA, TunnelNA },
847             { TunnelLeft, TunnelNA, TunnelNA, TunnelRight },
848             { TunnelNA, TunnelNA, TunnelRight, TunnelLeft },
849             { TunnelNA, TunnelRight, TunnelLeft, TunnelNA },
850         };
851 
852         int16_t tunnelOffset[4] = { 0 };
853         uint8_t tunnelType[4] = { 0xFF, 0xFF, 0xFF, 0xFF };
854         for (int direction = 0; direction < 4; direction++)
855         {
856             for (int side = 0; side < 4; side++)
857             {
858                 auto tunnel = tileTunnelCalls[direction][side];
859                 if (tunnel.call == TUNNELCALL_CALL)
860                 {
861                     tunnelOffset[direction] = tunnel.offset;
862                     tunnelType[direction] = tunnel.type;
863                     break;
864                 }
865             }
866         }
867 
868         if (AllMatch(tunnelOffset, 4) && AllMatch(tunnelType, 4))
869         {
870             if (tunnelType[0] != 0xFF)
871             {
872                 GenerateTunnelCall(tabs, tunnelOffset[0], tunnelType[0]);
873             }
874         }
875         else if (
876             tunnelOffset[0] == tunnelOffset[3] && tunnelType[0] == tunnelType[3] && tunnelOffset[1] == tunnelOffset[2]
877             && tunnelType[1] == tunnelType[2] && tunnelType[0] != 0xFF)
878         {
879             if (tunnelType[0] != 0xFF)
880             {
881                 WriteLine(tabs, "if (direction == 0 || direction == 3) {");
882                 GenerateTunnelCall(tabs + 1, tunnelOffset[0], tunnelType[0]);
883                 if (tunnelType[1] != 0xFF)
884                 {
885                     WriteLine(tabs, "} else {");
886                     GenerateTunnelCall(tabs + 1, tunnelOffset[1], tunnelType[1]);
887                 }
888                 WriteLine(tabs, "}");
889             }
890             else
891             {
892                 WriteLine(tabs, "if (direction == 1 || direction == 2) {");
893                 GenerateTunnelCall(tabs + 1, tunnelOffset[1], tunnelType[1]);
894                 WriteLine(tabs, "}");
895             }
896         }
897         else
898         {
899             WriteLine(tabs, "switch (direction) {");
900             for (int i = 0; i < 4; i++)
901             {
902                 if (tunnelType[i] != 0xFF)
903                 {
904                     WriteLine(tabs, "case %d:", i);
905                     for (int side = 0; side < 4; side++)
906                     {
907                         if (tileTunnelCalls[i][side].call == TUNNELCALL_CALL)
908                         {
909                             GenerateTunnelCall(
910                                 tabs + 1, tileTunnelCalls[i][side].offset, tileTunnelCalls[i][side].type, dsToWay[i][side]);
911                         }
912                     }
913                     WriteLine(tabs + 1, "break;");
914                 }
915             }
916             WriteLine(tabs, "}");
917         }
918 
919         if (AllMatch(verticalTunnelHeights, 4))
920         {
921             int tunnelHeight = verticalTunnelHeights[0];
922             if (tunnelHeight != 0)
923             {
924                 WriteLine(
925                     tabs, "paint_util_set_vertical_tunnel(session, height%s);",
926                     GetOffsetExpressionString(tunnelHeight).c_str());
927             }
928         }
929     }
930 
GenerateTunnelCall(int tabs,int offset,int type,int way)931     void GenerateTunnelCall(int tabs, int offset, int type, int way)
932     {
933         switch (way)
934         {
935             case 0:
936                 WriteLine(
937                     tabs, "paint_util_push_tunnel_left(session, height%s, TUNNEL_%d);",
938                     GetOffsetExpressionString(offset).c_str(), type);
939                 break;
940             case 1:
941                 WriteLine(
942                     tabs, "paint_util_push_tunnel_right(session, height%s, TUNNEL_%d);",
943                     GetOffsetExpressionString(offset).c_str(), type);
944                 break;
945         }
946     }
947 
GenerateTunnelCall(int tabs,int offset,int type)948     void GenerateTunnelCall(int tabs, int offset, int type)
949     {
950         WriteLine(
951             tabs, "paint_util_push_tunnel_rotated(session, direction, height%s, TUNNEL_%d);",
952             GetOffsetExpressionString(offset).c_str(), type);
953     }
954 
GenerateSegmentSupportCall(int tabs,std::vector<SegmentSupportCall> segmentSupportCalls[4])955     void GenerateSegmentSupportCall(int tabs, std::vector<SegmentSupportCall> segmentSupportCalls[4])
956     {
957         for (size_t i = 0; i < segmentSupportCalls[0].size(); i++)
958         {
959             auto ssh = segmentSupportCalls[0][i];
960             std::string szCall = "paint_util_set_segment_support_height(session, ";
961             if (ssh.segments == SEGMENTS_ALL)
962             {
963                 szCall += "SEGMENTS_ALL";
964             }
965             else
966             {
967                 szCall += "paint_util_rotate_segments(";
968                 szCall += GetORedSegments(ssh.segments);
969                 szCall += ", direction)";
970             }
971             szCall += ", ";
972             if (ssh.height == 0xFFFF)
973             {
974                 szCall += "0xFFFF";
975                 szCall += String::Format(", 0);", ssh.slope);
976             }
977             else
978             {
979                 szCall += std::to_string(ssh.height);
980                 szCall += String::Format(", 0x%02X);", ssh.slope);
981             }
982             WriteLine(tabs, szCall);
983         }
984     }
985 
GenerateGeneralSupportCall(int tabs,support_height generalSupports[4])986     void GenerateGeneralSupportCall(int tabs, support_height generalSupports[4])
987     {
988         if (generalSupports[0].height == 0 && generalSupports[0].slope == 0xFF)
989         {
990             return;
991         }
992 
993         WriteLine(
994             tabs, "paint_util_set_general_support_height(session, height%s, 0x%02X);",
995             GetOffsetExpressionString((int16_t)generalSupports[0].height).c_str(), generalSupports[0].slope);
996         if (!AllMatch(generalSupports, 4))
997         {
998             // WriteLine(tabs, "#error Unsupported: different directional general supports");
999         }
1000     }
1001 
GetImageIdString(uint32_t imageId)1002     std::string GetImageIdString(uint32_t imageId)
1003     {
1004         std::string result;
1005 
1006         uint32_t image = imageId & 0x7FFFF;
1007         uint32_t palette = imageId & ~0x7FFFF;
1008 
1009         std::string paletteName;
1010         if (palette == TestPaint::DEFAULT_SCHEME_TRACK)
1011             paletteName = "gTrackColours[SCHEME_TRACK]";
1012         else if (palette == TestPaint::DEFAULT_SCHEME_SUPPORTS)
1013             paletteName = "gTrackColours[SCHEME_SUPPORTS]";
1014         else if (palette == TestPaint::DEFAULT_SCHEME_MISC)
1015             paletteName = "gTrackColours[SCHEME_MISC]";
1016         else if (palette == TestPaint::DEFAULT_SCHEME_3)
1017             paletteName = "gTrackColours[SCHEME_3]";
1018         else
1019         {
1020             paletteName = String::Format("0x%08X", palette);
1021         }
1022 
1023         if (image == 0)
1024         {
1025             result = paletteName;
1026         }
1027         else if (image & 0x70000)
1028         {
1029             result = String::Format("%s | vehicle.base_image_id + %d", paletteName.c_str(), image & ~0x70000);
1030         }
1031         else
1032         {
1033             result = String::Format("%s | %d", paletteName.c_str(), image);
1034         }
1035         return result;
1036     }
1037 
GetOffsetExpressionString(int offset)1038     std::string GetOffsetExpressionString(int offset)
1039     {
1040         if (offset < 0)
1041             return std::string(" - ") + std::to_string(-offset);
1042         if (offset > 0)
1043             return std::string(" + ") + std::to_string(offset);
1044         return std::string();
1045     }
1046 
GetORedSegments(int segments)1047     std::string GetORedSegments(int segments)
1048     {
1049         std::string s;
1050         int segmentsPrinted = 0;
1051         for (int i = 0; i < 9; i++)
1052         {
1053             if (segments & segment_offsets[i])
1054             {
1055                 if (segmentsPrinted > 0)
1056                 {
1057                     s += " | ";
1058                 }
1059                 s += String::Format("SEGMENT_%02X", 0xB4 + 4 * i);
1060                 segmentsPrinted++;
1061             }
1062         }
1063         return s;
1064     }
1065 
AllMatch(T * arr,size_t count)1066     template<typename T> bool AllMatch(T* arr, size_t count)
1067     {
1068         for (size_t i = 1; i < count; i++)
1069         {
1070             if (memcmp((const void*)&arr[i], (const void*)&arr[0], sizeof(T)) != 0)
1071             {
1072                 return false;
1073             }
1074         }
1075         return true;
1076     }
1077 
CallOriginal(int trackType,int direction,int trackSequence,int height,TileElement * tileElement)1078     void CallOriginal(int trackType, int direction, int trackSequence, int height, TileElement* tileElement)
1079     {
1080         TestPaint::ResetEnvironment();
1081         TestPaint::ResetSupportHeights();
1082 
1083         uint32_t* trackDirectionList = (uint32_t*)RideTypeTrackPaintFunctionsOld[_rideType][trackType];
1084         // Have to call from this point as it pushes esi and expects callee to pop it
1085         RCT2_CALLPROC_X(
1086             0x006C4934, _rideType, (int)trackDirectionList, direction, height, (int)tileElement, 0 * sizeof(Ride),
1087             trackSequence);
1088     }
1089 
GenerateMainFunction()1090     void GenerateMainFunction()
1091     {
1092         WriteLine(0, "TRACK_PAINT_FUNCTION get_track_paint_function_" + _rideName + "(int trackType, int direction)");
1093         WriteLine(0, "{");
1094         WriteLine(1, "switch (trackType) {");
1095         for (int trackType = 0; trackType < 256; trackType++)
1096         {
1097             if (trackType == TrackElemType::EndStation)
1098             {
1099                 WriteLine(1, "case " + std::string(TrackElemNames[TrackElemType::EndStation]) + ":");
1100                 WriteLine(1, "case " + std::string(TrackElemNames[TrackElemType::BeginStation]) + ":");
1101                 WriteLine(1, "case " + std::string(TrackElemNames[TrackElemType::MiddleStation]) + ":");
1102                 WriteLine(2, "return %s_track_station;", _rideName.c_str());
1103                 continue;
1104             }
1105 
1106             if (IsTrackTypeSupported(trackType))
1107             {
1108                 WriteLine(1, "case " + std::string(TrackElemNames[trackType]) + ":");
1109                 WriteLine(2, "return %s;", GetTrackFunctionName(trackType).c_str());
1110             }
1111         }
1112         WriteLine(1, "}");
1113         WriteLine(1, "return nullptr;");
1114         WriteLine(0, "}");
1115     }
1116 
GetTrackFunctionName(int trackType)1117     std::string GetTrackFunctionName(int trackType)
1118     {
1119         std::string trackName = TrackCodeNames[trackType];
1120         return _rideName + "_track_" + trackName;
1121     }
1122 
IsTrackTypeSupported(int trackType)1123     bool IsTrackTypeSupported(int trackType)
1124     {
1125         if (trackType == TrackElemType::BeginStation || trackType == TrackElemType::MiddleStation
1126             || trackType == TrackElemType::EndStation)
1127         {
1128             return false;
1129         }
1130         if (RideTypeTrackPaintFunctionsOld[_rideType][trackType] != 0)
1131         {
1132             return true;
1133         }
1134         return false;
1135     }
1136 
WriteLine()1137     void WriteLine()
1138     {
1139         WriteLine(0, "");
1140     }
1141 
WriteLine(int tabs,const char * format,...)1142     void WriteLine(int tabs, const char* format, ...)
1143     {
1144         va_list args;
1145         char buffer[512];
1146 
1147         va_start(args, format);
1148         vsnprintf(buffer, sizeof(buffer), format, args);
1149         va_end(args);
1150 
1151         WriteLine(tabs, std::string(buffer));
1152     }
1153 
WriteLine(int tabs,std::string s)1154     void WriteLine(int tabs, std::string s)
1155     {
1156         for (int i = 0; i < tabs; i++)
1157         {
1158             fprintf(_file, "\t");
1159         }
1160         fprintf(_file, "%s\n", s.c_str());
1161     }
1162 };
1163 
generatePaintCode(uint8_t rideType)1164 int generatePaintCode(uint8_t rideType)
1165 {
1166     if (GetRideTypeDescriptor(rideType).HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE))
1167     {
1168         fprintf(stderr, "Flat rides not supported.\n");
1169     }
1170 
1171     auto pcg = PaintCodeGenerator();
1172     return pcg.Generate(rideType);
1173 }
1174