1 /*****************************************************************************
2  * Copyright (c) 2014-2020 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 "TrackDesign.h"
11 
12 #include "../Cheats.h"
13 #include "../Context.h"
14 #include "../Game.h"
15 #include "../OpenRCT2.h"
16 #include "../TrackImporter.h"
17 #include "../actions/FootpathPlaceFromTrackAction.h"
18 #include "../actions/FootpathRemoveAction.h"
19 #include "../actions/LargeSceneryPlaceAction.h"
20 #include "../actions/LargeSceneryRemoveAction.h"
21 #include "../actions/MazePlaceTrackAction.h"
22 #include "../actions/RideCreateAction.h"
23 #include "../actions/RideEntranceExitPlaceAction.h"
24 #include "../actions/RideSetNameAction.h"
25 #include "../actions/RideSetSettingAction.h"
26 #include "../actions/RideSetVehicleAction.h"
27 #include "../actions/SmallSceneryPlaceAction.h"
28 #include "../actions/SmallSceneryRemoveAction.h"
29 #include "../actions/TrackPlaceAction.h"
30 #include "../actions/TrackRemoveAction.h"
31 #include "../actions/WallPlaceAction.h"
32 #include "../actions/WallRemoveAction.h"
33 #include "../audio/audio.h"
34 #include "../core/DataSerialiser.h"
35 #include "../core/File.h"
36 #include "../core/Numerics.hpp"
37 #include "../core/String.hpp"
38 #include "../drawing/X8DrawingEngine.h"
39 #include "../localisation/Localisation.h"
40 #include "../localisation/StringIds.h"
41 #include "../management/Finance.h"
42 #include "../network/network.h"
43 #include "../object/FootpathObject.h"
44 #include "../object/FootpathSurfaceObject.h"
45 #include "../object/ObjectList.h"
46 #include "../object/ObjectManager.h"
47 #include "../object/ObjectRepository.h"
48 #include "../rct1/RCT1.h"
49 #include "../rct1/Tables.h"
50 #include "../util/SawyerCoding.h"
51 #include "../util/Util.h"
52 #include "../world/Footpath.h"
53 #include "../world/Park.h"
54 #include "../world/Scenery.h"
55 #include "../world/SmallScenery.h"
56 #include "../world/Surface.h"
57 #include "../world/Wall.h"
58 #include "Ride.h"
59 #include "RideData.h"
60 #include "Track.h"
61 #include "TrackData.h"
62 #include "TrackDesign.h"
63 #include "TrackDesignRepository.h"
64 #include "Vehicle.h"
65 
66 #include <algorithm>
67 #include <iterator>
68 #include <memory>
69 
70 using namespace OpenRCT2;
71 using namespace OpenRCT2::Drawing;
72 using namespace OpenRCT2::TrackMetaData;
73 
74 bool gTrackDesignSceneryToggle;
75 bool _trackDesignDrawingPreview;
76 bool _trackDesignPlaceStateSceneryUnavailable = false;
77 
78 static bool _trackDesignPlaceStateEntranceExitPlaced{};
79 
80 static void TrackDesignPreviewClearMap();
81 
CreateTrackDesign(TrackDesignState & tds,const Ride & ride)82 rct_string_id TrackDesign::CreateTrackDesign(TrackDesignState& tds, const Ride& ride)
83 {
84     type = ride.type;
85 
86     auto object = object_entry_get_object(ObjectType::Ride, ride.subtype);
87     if (object != nullptr)
88     {
89         auto entry = object->GetObjectEntry();
90         if (entry.IsEmpty())
91         {
92             // TODO create a new error message for `JSON objects are unsupported`
93             return STR_UNKNOWN_OBJECT_TYPE;
94         }
95         vehicle_object = ObjectEntryDescriptor(entry);
96     }
97 
98     ride_mode = ride.mode;
99     colour_scheme = ride.colour_scheme_type & 3;
100 
101     for (int32_t i = 0; i < RCT2_MAX_CARS_PER_TRAIN; i++)
102     {
103         vehicle_colours[i].body_colour = ride.vehicle_colours[i].Body;
104         vehicle_colours[i].trim_colour = ride.vehicle_colours[i].Trim;
105         vehicle_additional_colour[i] = ride.vehicle_colours[i].Ternary;
106     }
107 
108     for (int32_t i = 0; i < RCT12_NUM_COLOUR_SCHEMES; i++)
109     {
110         track_spine_colour[i] = ride.track_colour[i].main;
111         track_rail_colour[i] = ride.track_colour[i].additional;
112         track_support_colour[i] = ride.track_colour[i].supports;
113     }
114 
115     depart_flags = ride.depart_flags;
116     number_of_trains = ride.num_vehicles;
117     number_of_cars_per_train = ride.num_cars_per_train;
118     min_waiting_time = ride.min_waiting_time;
119     max_waiting_time = ride.max_waiting_time;
120     operation_setting = ride.operation_option;
121     lift_hill_speed = ride.lift_hill_speed;
122     num_circuits = ride.num_circuits;
123 
124     entrance_style = ride.entrance_style;
125     max_speed = static_cast<int8_t>(ride.max_speed / 65536);
126     average_speed = static_cast<int8_t>(ride.average_speed / 65536);
127     ride_length = ride_get_total_length(&ride) / 65536;
128     max_positive_vertical_g = ride.max_positive_vertical_g / 32;
129     max_negative_vertical_g = ride.max_negative_vertical_g / 32;
130     max_lateral_g = ride.max_lateral_g / 32;
131     inversions = ride.holes & 0x1F;
132     inversions = ride.inversions & 0x1F;
133     inversions |= (ride.sheltered_eighths << 5);
134     drops = ride.drops;
135     highest_drop_height = ride.highest_drop_height;
136 
137     uint16_t totalAirTime = (ride.total_air_time * 123) / 1024;
138     if (totalAirTime > 255)
139     {
140         totalAirTime = 0;
141     }
142     total_air_time = static_cast<uint8_t>(totalAirTime);
143 
144     excitement = ride.ratings.Excitement / 10;
145     intensity = ride.ratings.Intensity / 10;
146     nausea = ride.ratings.Nausea / 10;
147 
148     upkeep_cost = ride.upkeep_cost;
149     flags = 0;
150     flags2 = 0;
151 
152     if (type == RIDE_TYPE_MAZE)
153     {
154         return CreateTrackDesignMaze(tds, ride);
155     }
156 
157     return CreateTrackDesignTrack(tds, ride);
158 }
159 
CreateTrackDesignTrack(TrackDesignState & tds,const Ride & ride)160 rct_string_id TrackDesign::CreateTrackDesignTrack(TrackDesignState& tds, const Ride& ride)
161 {
162     CoordsXYE trackElement;
163     if (!ride_try_get_origin_element(&ride, &trackElement))
164     {
165         return STR_TRACK_TOO_LARGE_OR_TOO_MUCH_SCENERY;
166     }
167 
168     ride_get_start_of_track(&trackElement);
169 
170     int32_t z = trackElement.element->GetBaseZ();
171     auto trackType = trackElement.element->AsTrack()->GetTrackType();
172     uint8_t direction = trackElement.element->GetDirection();
173     _saveDirection = direction;
174     auto newCoords = GetTrackElementOriginAndApplyChanges(
175         { trackElement, z, direction }, trackType, 0, &trackElement.element, 0);
176 
177     if (!newCoords.has_value())
178     {
179         return STR_TRACK_TOO_LARGE_OR_TOO_MUCH_SCENERY;
180     }
181     trackElement.x = newCoords->x;
182     trackElement.y = newCoords->y;
183     z = newCoords->z;
184 
185     const auto& ted = GetTrackElementDescriptor(trackElement.element->AsTrack()->GetTrackType());
186     const rct_track_coordinates* trackCoordinates = &ted.Coordinates;
187     const auto* trackBlock = ted.Block;
188     // Used in the following loop to know when we have
189     // completed all of the elements and are back at the
190     // start.
191     TileElement* initialMap = trackElement.element;
192 
193     CoordsXYZ startPos = { trackElement.x, trackElement.y, z + trackCoordinates->z_begin - trackBlock->z };
194     tds.Origin = startPos;
195 
196     do
197     {
198         TrackDesignTrackElement track{};
199         track.type = trackElement.element->AsTrack()->GetTrackType();
200 
201         uint8_t trackFlags;
202         if (TrackTypeHasSpeedSetting(track.type))
203         {
204             trackFlags = trackElement.element->AsTrack()->GetBrakeBoosterSpeed() >> 1;
205         }
206         else
207         {
208             trackFlags = trackElement.element->AsTrack()->GetSeatRotation();
209         }
210 
211         if (trackElement.element->AsTrack()->HasChain())
212             trackFlags |= RCT12_TRACK_ELEMENT_TYPE_FLAG_CHAIN_LIFT;
213         trackFlags |= trackElement.element->AsTrack()->GetColourScheme() << 4;
214         if (ride.GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_ALTERNATIVE_TRACK_TYPE)
215             && trackElement.element->AsTrack()->IsInverted())
216         {
217             trackFlags |= TD6_TRACK_ELEMENT_FLAG_INVERTED;
218         }
219 
220         track.flags = trackFlags;
221         track_elements.push_back(track);
222 
223         if (!track_block_get_next(&trackElement, &trackElement, nullptr, nullptr))
224         {
225             break;
226         }
227 
228         z = trackElement.element->GetBaseZ();
229         direction = trackElement.element->GetDirection();
230         trackType = trackElement.element->AsTrack()->GetTrackType();
231         newCoords = GetTrackElementOriginAndApplyChanges(
232             { trackElement, z, direction }, trackType, 0, &trackElement.element, 0);
233 
234         if (!newCoords.has_value())
235         {
236             break;
237         }
238         trackElement.x = newCoords->x;
239         trackElement.y = newCoords->y;
240 
241         if (track_elements.size() > TD6MaxTrackElements)
242         {
243             return STR_TRACK_TOO_LARGE_OR_TOO_MUCH_SCENERY;
244         }
245     } while (trackElement.element != initialMap);
246 
247     // First entrances, second exits
248     for (int32_t i = 0; i < 2; i++)
249     {
250         for (StationIndex station_index = 0; station_index < RCT12_MAX_STATIONS_PER_RIDE; station_index++)
251         {
252             z = ride.stations[station_index].GetBaseZ();
253 
254             TileCoordsXYZD location;
255             if (i == 0)
256             {
257                 location = ride_get_entrance_location(&ride, station_index);
258             }
259             else
260             {
261                 location = ride_get_exit_location(&ride, station_index);
262             }
263 
264             if (location.IsNull())
265             {
266                 continue;
267             }
268 
269             CoordsXY mapLocation = location.ToCoordsXY();
270 
271             TileElement* tileElement = map_get_first_element_at(mapLocation);
272             if (tileElement == nullptr)
273                 continue;
274 
275             do
276             {
277                 if (tileElement->GetType() != TILE_ELEMENT_TYPE_ENTRANCE)
278                     continue;
279                 if (tileElement->GetBaseZ() == z)
280                     break;
281             } while (!(tileElement++)->IsLastForTile());
282 
283             // Add something that stops this from walking off the end
284 
285             Direction entranceDirection = tileElement->GetDirection();
286             entranceDirection -= _saveDirection;
287             entranceDirection &= TILE_ELEMENT_DIRECTION_MASK;
288 
289             TrackDesignEntranceElement entrance{};
290             entrance.direction = entranceDirection;
291 
292             mapLocation -= tds.Origin;
293 
294             // Rotate entrance coordinates backwards to the correct direction
295             auto rotatedMapLocation = mapLocation.Rotate(0 - _saveDirection);
296             entrance.x = rotatedMapLocation.x;
297             entrance.y = rotatedMapLocation.y;
298 
299             z -= tds.Origin.z;
300             z /= 8;
301 
302             if (z > 127 || z < -126)
303             {
304                 return STR_TRACK_TOO_LARGE_OR_TOO_MUCH_SCENERY;
305             }
306 
307             entrance.z = z;
308 
309             // If this is the exit version
310             if (i == 1)
311             {
312                 entrance.isExit = true;
313             }
314             entrance_elements.push_back(entrance);
315         }
316     }
317 
318     TrackDesignPreviewDrawOutlines(this, GetOrAllocateRide(PreviewRideId), { 4096, 4096, 0 });
319 
320     // Resave global vars for scenery reasons.
321     tds.Origin = startPos;
322 
323     gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_CONSTRUCT;
324     gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW;
325     gMapSelectFlags &= ~MAP_SELECT_FLAG_GREEN;
326 
327     space_required_x = ((tds.PreviewMax.x - tds.PreviewMin.x) / 32) + 1;
328     space_required_y = ((tds.PreviewMax.y - tds.PreviewMin.y) / 32) + 1;
329     return STR_NONE;
330 }
331 
CreateTrackDesignMaze(TrackDesignState & tds,const Ride & ride)332 rct_string_id TrackDesign::CreateTrackDesignMaze(TrackDesignState& tds, const Ride& ride)
333 {
334     auto startLoc = MazeGetFirstElement(ride);
335 
336     if (startLoc.element == nullptr)
337     {
338         return STR_TRACK_TOO_LARGE_OR_TOO_MUCH_SCENERY;
339     }
340 
341     tds.Origin = { startLoc.x, startLoc.y, startLoc.element->GetBaseZ() };
342 
343     // x is defined here as we can start the search
344     // on tile start_x, start_y but then the next row
345     // must restart on 0
346     for (int32_t y = startLoc.y, x = startLoc.x; y < MAXIMUM_MAP_SIZE_BIG; y += COORDS_XY_STEP)
347     {
348         for (; x < MAXIMUM_MAP_SIZE_BIG; x += COORDS_XY_STEP)
349         {
350             auto tileElement = map_get_first_element_at(CoordsXY{ x, y });
351             do
352             {
353                 if (tileElement == nullptr)
354                     break;
355                 if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK)
356                     continue;
357                 if (tileElement->AsTrack()->GetRideIndex() != ride.id)
358                     continue;
359 
360                 TrackDesignMazeElement maze{};
361 
362                 maze.maze_entry = tileElement->AsTrack()->GetMazeEntry();
363                 maze.x = (x - startLoc.x) / COORDS_XY_STEP;
364                 maze.y = (y - startLoc.y) / COORDS_XY_STEP;
365                 _saveDirection = tileElement->GetDirection();
366                 maze_elements.push_back(maze);
367 
368                 if (maze_elements.size() >= 2000)
369                 {
370                     return STR_TRACK_TOO_LARGE_OR_TOO_MUCH_SCENERY;
371                 }
372             } while (!(tileElement++)->IsLastForTile());
373         }
374         x = 0;
375     }
376 
377     auto location = ride_get_entrance_location(&ride, 0);
378     if (location.IsNull())
379     {
380         return STR_TRACK_TOO_LARGE_OR_TOO_MUCH_SCENERY;
381     }
382 
383     CoordsXY entranceLoc = location.ToCoordsXY();
384     auto tileElement = map_get_first_element_at(entranceLoc);
385     do
386     {
387         if (tileElement == nullptr)
388             return STR_TRACK_TOO_LARGE_OR_TOO_MUCH_SCENERY;
389         if (tileElement->GetType() != TILE_ELEMENT_TYPE_ENTRANCE)
390             continue;
391         if (tileElement->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_RIDE_ENTRANCE)
392             continue;
393         if (tileElement->AsEntrance()->GetRideIndex() == ride.id)
394             break;
395     } while (!(tileElement++)->IsLastForTile());
396     // Add something that stops this from walking off the end
397 
398     uint8_t entranceDirection = tileElement->GetDirection();
399     TrackDesignMazeElement mazeEntrance{};
400     mazeEntrance.direction = entranceDirection;
401     mazeEntrance.type = 8;
402     mazeEntrance.x = static_cast<int8_t>((entranceLoc.x - startLoc.x) / 32);
403     mazeEntrance.y = static_cast<int8_t>((entranceLoc.y - startLoc.y) / 32);
404     maze_elements.push_back(mazeEntrance);
405 
406     location = ride_get_exit_location(&ride, 0);
407     if (location.IsNull())
408     {
409         return STR_TRACK_TOO_LARGE_OR_TOO_MUCH_SCENERY;
410     }
411 
412     CoordsXY exitLoc = location.ToCoordsXY();
413     tileElement = map_get_first_element_at(exitLoc);
414     if (tileElement == nullptr)
415         return STR_TRACK_TOO_LARGE_OR_TOO_MUCH_SCENERY;
416     do
417     {
418         if (tileElement->GetType() != TILE_ELEMENT_TYPE_ENTRANCE)
419             continue;
420         if (tileElement->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_RIDE_EXIT)
421             continue;
422         if (tileElement->AsEntrance()->GetRideIndex() == ride.id)
423             break;
424     } while (!(tileElement++)->IsLastForTile());
425     // Add something that stops this from walking off the end
426 
427     uint8_t exit_direction = tileElement->GetDirection();
428     TrackDesignMazeElement mazeExit{};
429     mazeExit.direction = exit_direction;
430     mazeExit.type = 0x80;
431     mazeExit.x = static_cast<int8_t>((exitLoc.x - startLoc.x) / 32);
432     mazeExit.y = static_cast<int8_t>((exitLoc.y - startLoc.y) / 32);
433     maze_elements.push_back(mazeExit);
434 
435     // Save global vars as they are still used by scenery????
436     int32_t startZ = tds.Origin.z;
437     TrackDesignPreviewDrawOutlines(this, GetOrAllocateRide(PreviewRideId), { 4096, 4096, 0 });
438     tds.Origin = { startLoc.x, startLoc.y, startZ };
439 
440     gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_CONSTRUCT;
441     gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW;
442     gMapSelectFlags &= ~MAP_SELECT_FLAG_GREEN;
443 
444     space_required_x = ((tds.PreviewMax.x - tds.PreviewMin.x) / 32) + 1;
445     space_required_y = ((tds.PreviewMax.y - tds.PreviewMin.y) / 32) + 1;
446     return STR_NONE;
447 }
448 
MazeGetFirstElement(const Ride & ride)449 CoordsXYE TrackDesign::MazeGetFirstElement(const Ride& ride)
450 {
451     CoordsXYE tile{};
452     for (tile.y = 0; tile.y < MAXIMUM_MAP_SIZE_BIG; tile.y += COORDS_XY_STEP)
453     {
454         for (tile.x = 0; tile.x < MAXIMUM_MAP_SIZE_BIG; tile.x += COORDS_XY_STEP)
455         {
456             tile.element = map_get_first_element_at(CoordsXY{ tile.x, tile.y });
457             do
458             {
459                 if (tile.element == nullptr)
460                     break;
461 
462                 if (tile.element->GetType() != TILE_ELEMENT_TYPE_TRACK)
463                     continue;
464                 if (tile.element->AsTrack()->GetRideIndex() == ride.id)
465                 {
466                     return tile;
467                 }
468             } while (!(tile.element++)->IsLastForTile());
469         }
470     }
471     tile.element = nullptr;
472     return tile;
473 }
474 
CreateTrackDesignScenery(TrackDesignState & tds)475 rct_string_id TrackDesign::CreateTrackDesignScenery(TrackDesignState& tds)
476 {
477     scenery_elements = _trackSavedTileElementsDesc;
478     // Run an element loop
479     for (auto& scenery : scenery_elements)
480     {
481         switch (scenery.scenery_object.GetType())
482         {
483             case ObjectType::Paths:
484             {
485                 uint8_t slope = (scenery.flags & 0x60) >> 5;
486                 slope -= _saveDirection;
487 
488                 scenery.flags &= 0x9F;
489                 scenery.flags |= ((slope & 3) << 5);
490 
491                 // Direction of connection on path
492                 uint8_t direction = scenery.flags & 0xF;
493                 // Rotate the direction by the track direction
494                 direction = ((direction << 4) >> _saveDirection);
495 
496                 scenery.flags &= 0xF0;
497                 scenery.flags |= (direction & 0xF) | (direction >> 4);
498                 break;
499             }
500             case ObjectType::Walls:
501             {
502                 uint8_t direction = scenery.flags & 3;
503                 direction -= _saveDirection;
504 
505                 scenery.flags &= 0xFC;
506                 scenery.flags |= (direction & 3);
507                 break;
508             }
509             default:
510             {
511                 uint8_t direction = scenery.flags & 3;
512                 uint8_t quadrant = (scenery.flags & 0x0C) >> 2;
513 
514                 direction -= _saveDirection;
515                 quadrant -= _saveDirection;
516 
517                 scenery.flags &= 0xF0;
518                 scenery.flags |= (direction & 3) | ((quadrant & 3) << 2);
519                 break;
520             }
521         }
522 
523         // Cast the value into a uint8_t as this value is not signed yet.
524         auto sceneryPos = TileCoordsXY(static_cast<uint8_t>(scenery.x), static_cast<uint8_t>(scenery.y)).ToCoordsXY();
525         CoordsXY sceneryMapPos = sceneryPos - tds.Origin;
526         CoordsXY rotatedSceneryMapPos = sceneryMapPos.Rotate(0 - _saveDirection);
527         TileCoordsXY sceneryTilePos{ rotatedSceneryMapPos };
528 
529         if (sceneryTilePos.x > 127 || sceneryTilePos.y > 127 || sceneryTilePos.x < -126 || sceneryTilePos.y < -126)
530         {
531             return STR_TRACK_TOO_LARGE_OR_TOO_MUCH_SCENERY;
532         }
533 
534         scenery.x = static_cast<int8_t>(sceneryTilePos.x);
535         scenery.y = static_cast<int8_t>(sceneryTilePos.y);
536 
537         int32_t z = scenery.z * COORDS_Z_STEP - tds.Origin.z;
538         z /= COORDS_Z_STEP;
539         if (z > 127 || z < -126)
540         {
541             return STR_TRACK_TOO_LARGE_OR_TOO_MUCH_SCENERY;
542         }
543         scenery.z = z;
544     }
545 
546     return STR_NONE;
547 }
548 
Serialise(DataSerialiser & stream)549 void TrackDesign::Serialise(DataSerialiser& stream)
550 {
551     if (stream.IsLogging())
552     {
553         stream << DS_TAG(name);
554         // There is too much information logged.
555         // See sub actions for this information if required.
556         return;
557     }
558     stream << DS_TAG(type);
559     stream << DS_TAG(vehicle_type);
560     stream << DS_TAG(cost);
561     stream << DS_TAG(flags);
562     stream << DS_TAG(ride_mode);
563     stream << DS_TAG(track_flags);
564     stream << DS_TAG(colour_scheme);
565     stream << DS_TAG(vehicle_colours);
566     stream << DS_TAG(entrance_style);
567     stream << DS_TAG(total_air_time);
568     stream << DS_TAG(depart_flags);
569     stream << DS_TAG(number_of_trains);
570     stream << DS_TAG(number_of_cars_per_train);
571     stream << DS_TAG(min_waiting_time);
572     stream << DS_TAG(max_waiting_time);
573     stream << DS_TAG(operation_setting);
574     stream << DS_TAG(max_speed);
575     stream << DS_TAG(average_speed);
576     stream << DS_TAG(ride_length);
577     stream << DS_TAG(max_positive_vertical_g);
578     stream << DS_TAG(max_negative_vertical_g);
579     stream << DS_TAG(max_lateral_g);
580     stream << DS_TAG(inversions);
581     stream << DS_TAG(holes);
582     stream << DS_TAG(drops);
583     stream << DS_TAG(highest_drop_height);
584     stream << DS_TAG(excitement);
585     stream << DS_TAG(intensity);
586     stream << DS_TAG(nausea);
587     stream << DS_TAG(upkeep_cost);
588     stream << DS_TAG(track_spine_colour);
589     stream << DS_TAG(track_rail_colour);
590     stream << DS_TAG(track_support_colour);
591     stream << DS_TAG(flags2);
592     stream << DS_TAG(vehicle_object.Entry);
593     stream << DS_TAG(space_required_x);
594     stream << DS_TAG(space_required_y);
595     stream << DS_TAG(vehicle_additional_colour);
596     stream << DS_TAG(lift_hill_speed);
597     stream << DS_TAG(num_circuits);
598 
599     stream << DS_TAG(maze_elements);
600     stream << DS_TAG(track_elements);
601     stream << DS_TAG(entrance_elements);
602     stream << DS_TAG(scenery_elements);
603 
604     stream << DS_TAG(name);
605 }
606 
TrackDesignImport(const utf8 * path)607 std::unique_ptr<TrackDesign> TrackDesignImport(const utf8* path)
608 {
609     try
610     {
611         auto trackImporter = TrackImporter::Create(path);
612         trackImporter->Load(path);
613         return trackImporter->Import();
614     }
615     catch (const std::exception& e)
616     {
617         log_error("Unable to load track design: %s", e.what());
618     }
619     log_verbose("track_design_open(\"%s\")", path);
620     return nullptr;
621 }
622 
623 /**
624  *
625  *  rct2: 0x006ABDB0
626  */
TrackDesignLoadSceneryObjects(TrackDesign * td6)627 static void TrackDesignLoadSceneryObjects(TrackDesign* td6)
628 {
629     auto& objectManager = OpenRCT2::GetContext()->GetObjectManager();
630     objectManager.UnloadAll();
631 
632     // Load ride object
633     if (td6->vehicle_object.HasValue())
634     {
635         objectManager.LoadObject(td6->vehicle_object);
636     }
637 
638     // Load scenery objects
639     for (const auto& scenery : td6->scenery_elements)
640     {
641         if (scenery.scenery_object.HasValue())
642         {
643             objectManager.LoadObject(scenery.scenery_object);
644         }
645     }
646 }
647 
648 struct TrackSceneryEntry
649 {
650     ObjectType Type = ObjectType::None;
651     ObjectEntryIndex Index = OBJECT_ENTRY_INDEX_NULL;
652     ObjectEntryIndex SecondaryIndex = OBJECT_ENTRY_INDEX_NULL; // For footpath railing
653 };
654 
TrackDesignGetDefaultSurfaceIndex(bool isQueue)655 static ObjectEntryIndex TrackDesignGetDefaultSurfaceIndex(bool isQueue)
656 {
657     for (ObjectEntryIndex i = 0; i < MAX_FOOTPATH_SURFACE_OBJECTS; i++)
658     {
659         auto footpathSurfaceObj = GetPathSurfaceEntry(i);
660         if (footpathSurfaceObj != nullptr)
661         {
662             if (footpathSurfaceObj->Flags & FOOTPATH_ENTRY_FLAG_SHOW_ONLY_IN_SCENARIO_EDITOR)
663             {
664                 continue;
665             }
666 
667             if (isQueue != ((footpathSurfaceObj->Flags & FOOTPATH_ENTRY_FLAG_IS_QUEUE) != 0))
668             {
669                 continue;
670             }
671 
672             return i;
673         }
674     }
675     return OBJECT_ENTRY_INDEX_NULL;
676 }
677 
TrackDesignGetDefaultRailingIndex()678 static ObjectEntryIndex TrackDesignGetDefaultRailingIndex()
679 {
680     for (ObjectEntryIndex i = 0; i < MAX_FOOTPATH_RAILINGS_OBJECTS; i++)
681     {
682         auto footpathRailingsObj = GetPathRailingsEntry(i);
683         if (footpathRailingsObj != nullptr)
684         {
685             return i;
686         }
687     }
688     return OBJECT_ENTRY_INDEX_NULL;
689 }
690 
TrackDesignGetDefaultPathIndex(bool isQueue)691 static ObjectEntryIndex TrackDesignGetDefaultPathIndex(bool isQueue)
692 {
693     for (ObjectEntryIndex i = 0; i < MAX_PATH_OBJECTS; i++)
694     {
695         auto legacyPathEntry = GetLegacyFootpathEntry(i);
696         if (legacyPathEntry != nullptr)
697         {
698             const auto& surfaceDescriptor = isQueue ? legacyPathEntry->GetQueueSurfaceDescriptor()
699                                                     : legacyPathEntry->GetPathSurfaceDescriptor();
700             if (surfaceDescriptor.IsEditorOnly())
701             {
702                 continue;
703             }
704             return i;
705         }
706     }
707     return OBJECT_ENTRY_INDEX_NULL;
708 }
709 
TrackDesignPlaceSceneryElementGetEntry(const TrackDesignSceneryElement & scenery)710 static std::optional<TrackSceneryEntry> TrackDesignPlaceSceneryElementGetEntry(const TrackDesignSceneryElement& scenery)
711 {
712     TrackSceneryEntry result;
713 
714     auto& objectMgr = OpenRCT2::GetContext()->GetObjectManager();
715     if (scenery.scenery_object.GetType() == ObjectType::Paths)
716     {
717         auto footpathMapping = GetFootpathSurfaceId(scenery.scenery_object, true, scenery.IsQueue());
718         if (footpathMapping == nullptr)
719         {
720             // Check if legacy path object is loaded
721             auto obj = objectMgr.GetLoadedObject(scenery.scenery_object);
722             if (obj != nullptr)
723             {
724                 result.Type = obj->GetObjectType();
725                 result.Index = objectMgr.GetLoadedObjectEntryIndex(obj);
726             }
727             else
728             {
729                 result.Type = ObjectType::FootpathSurface;
730             }
731         }
732         else
733         {
734             result.Type = ObjectType::FootpathSurface;
735             result.Index = objectMgr.GetLoadedObjectEntryIndex(
736                 ObjectEntryDescriptor(scenery.IsQueue() ? footpathMapping->QueueSurface : footpathMapping->NormalSurface));
737             result.SecondaryIndex = objectMgr.GetLoadedObjectEntryIndex(ObjectEntryDescriptor(footpathMapping->Railing));
738         }
739 
740         if (result.Index == OBJECT_ENTRY_INDEX_NULL)
741             result.Index = TrackDesignGetDefaultSurfaceIndex(scenery.IsQueue());
742         if (result.SecondaryIndex == OBJECT_ENTRY_INDEX_NULL)
743             result.SecondaryIndex = TrackDesignGetDefaultRailingIndex();
744 
745         // NOTE: This block can be deleted in the NSF branch.
746         if (result.Index == OBJECT_ENTRY_INDEX_NULL)
747         {
748             result.Type = ObjectType::Paths;
749             result.Index = TrackDesignGetDefaultPathIndex(scenery.IsQueue());
750         }
751 
752         if (result.Index == OBJECT_ENTRY_INDEX_NULL)
753         {
754             _trackDesignPlaceStateSceneryUnavailable = true;
755             return {};
756         }
757     }
758     else
759     {
760         auto obj = objectMgr.GetLoadedObject(scenery.scenery_object);
761         if (obj != nullptr)
762         {
763             result.Type = obj->GetObjectType();
764             result.Index = objectMgr.GetLoadedObjectEntryIndex(obj);
765         }
766         else
767         {
768             _trackDesignPlaceStateSceneryUnavailable = true;
769             return {};
770         }
771     }
772     return result;
773 }
774 
775 /**
776  *
777  *  rct2: 0x006D247A
778  */
TrackDesignMirrorScenery(TrackDesign * td6)779 static void TrackDesignMirrorScenery(TrackDesign* td6)
780 {
781     auto& objectMgr = OpenRCT2::GetContext()->GetObjectManager();
782     for (auto& scenery : td6->scenery_elements)
783     {
784         auto entryInfo = TrackDesignPlaceSceneryElementGetEntry(scenery);
785         if (!entryInfo)
786             continue;
787 
788         auto obj = objectMgr.GetLoadedObject(entryInfo->Type, entryInfo->Index);
789         switch (obj->GetObjectType())
790         {
791             case ObjectType::LargeScenery:
792             {
793                 auto* sceneryEntry = reinterpret_cast<const LargeSceneryEntry*>(obj->GetLegacyData());
794                 int16_t x1 = 0, x2 = 0, y1 = 0, y2 = 0;
795                 for (rct_large_scenery_tile* tile = sceneryEntry->tiles; tile->x_offset != -1; tile++)
796                 {
797                     if (x1 > tile->x_offset)
798                     {
799                         x1 = tile->x_offset;
800                     }
801                     if (x2 < tile->x_offset)
802                     {
803                         x2 = tile->x_offset;
804                     }
805                     if (y1 > tile->y_offset)
806                     {
807                         y1 = tile->y_offset;
808                     }
809                     if (y2 < tile->y_offset)
810                     {
811                         y2 = tile->y_offset;
812                     }
813                 }
814 
815                 switch (scenery.flags & 3)
816                 {
817                     case 0:
818                         scenery.y = (-(scenery.y * 32 + y1) - y2) / 32;
819                         break;
820                     case 1:
821                         scenery.x = (scenery.x * 32 + y2 + y1) / 32;
822                         scenery.y = (-(scenery.y * 32)) / 32;
823                         scenery.flags ^= (1 << 1);
824                         break;
825                     case 2:
826                         scenery.y = (-(scenery.y * 32 - y2) + y1) / 32;
827                         break;
828                     case 3:
829                         scenery.x = (scenery.x * 32 - y2 - y1) / 32;
830                         scenery.y = (-(scenery.y * 32)) / 32;
831                         scenery.flags ^= (1 << 1);
832                         break;
833                 }
834                 break;
835             }
836             case ObjectType::SmallScenery:
837             {
838                 auto* sceneryEntry = reinterpret_cast<const SmallSceneryEntry*>(obj->GetLegacyData());
839                 scenery.y = -scenery.y;
840 
841                 if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_DIAGONAL))
842                 {
843                     scenery.flags ^= (1 << 0);
844                     if (!sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_FULL_TILE))
845                     {
846                         scenery.flags ^= (1 << 2);
847                     }
848                     break;
849                 }
850                 if (scenery.flags & (1 << 0))
851                 {
852                     scenery.flags ^= (1 << 1);
853                 }
854 
855                 scenery.flags ^= (1 << 2);
856                 break;
857             }
858             case ObjectType::Walls:
859             {
860                 scenery.y = -scenery.y;
861                 if (scenery.flags & (1 << 0))
862                 {
863                     scenery.flags ^= (1 << 1);
864                 }
865                 break;
866             }
867             case ObjectType::Paths:
868             case ObjectType::FootpathSurface:
869             {
870                 scenery.y = -scenery.y;
871 
872                 if (scenery.flags & (1 << 5))
873                 {
874                     scenery.flags ^= (1 << 6);
875                 }
876 
877                 uint8_t flags = scenery.flags;
878                 flags = ((flags & (1 << 3)) >> 2) | ((flags & (1 << 1)) << 2);
879                 scenery.flags &= 0xF5;
880                 scenery.flags |= flags;
881                 break;
882             }
883             default:
884                 break;
885         }
886     }
887 }
888 
889 /**
890  *
891  *  rct2: 0x006D2443
892  */
TrackDesignMirrorRide(TrackDesign * td6)893 static void TrackDesignMirrorRide(TrackDesign* td6)
894 {
895     for (auto& track : td6->track_elements)
896     {
897         const auto& ted = GetTrackElementDescriptor(track.type);
898         track.type = ted.MirrorElement;
899     }
900 
901     for (auto& entrance : td6->entrance_elements)
902     {
903         entrance.y = -entrance.y;
904         if (entrance.direction & 1)
905         {
906             entrance.direction = direction_reverse(entrance.direction);
907         }
908     }
909 }
910 
911 /** rct2: 0x00993EDC */
912 static constexpr const uint8_t maze_segment_mirror_map[] = {
913     5, 4, 2, 7, 1, 0, 14, 3, 13, 12, 10, 15, 9, 8, 6, 11,
914 };
915 
916 /**
917  *
918  *  rct2: 0x006D25FA
919  */
TrackDesignMirrorMaze(TrackDesign * td6)920 static void TrackDesignMirrorMaze(TrackDesign* td6)
921 {
922     for (auto& maze : td6->maze_elements)
923     {
924         maze.y = -maze.y;
925 
926         if (maze.type == 0x8 || maze.type == 0x80)
927         {
928             if (maze.direction & 1)
929             {
930                 maze.direction = direction_reverse(maze.direction);
931             }
932             continue;
933         }
934 
935         uint16_t maze_entry = maze.maze_entry;
936         uint16_t new_entry = 0;
937         for (uint8_t position = bitscanforward(maze_entry); position != 0xFF; position = bitscanforward(maze_entry))
938         {
939             maze_entry &= ~(1 << position);
940             new_entry |= (1 << maze_segment_mirror_map[position]);
941         }
942         maze.maze_entry = new_entry;
943     }
944 }
945 
946 /**
947  *
948  *  rct2: 0x006D2436
949  */
TrackDesignMirror(TrackDesign * td6)950 void TrackDesignMirror(TrackDesign* td6)
951 {
952     if (td6->type == RIDE_TYPE_MAZE)
953     {
954         TrackDesignMirrorMaze(td6);
955     }
956     else
957     {
958         TrackDesignMirrorRide(td6);
959     }
960     TrackDesignMirrorScenery(td6);
961 }
962 
TrackDesignAddSelectedTile(const CoordsXY & coords)963 static void TrackDesignAddSelectedTile(const CoordsXY& coords)
964 {
965     auto tileIterator = std::find(gMapSelectionTiles.begin(), gMapSelectionTiles.end(), coords);
966     if (tileIterator == gMapSelectionTiles.end())
967     {
968         gMapSelectionTiles.push_back(coords);
969     }
970 }
971 
TrackDesignUpdatePreviewBounds(TrackDesignState & tds,const CoordsXYZ & coords)972 static void TrackDesignUpdatePreviewBounds(TrackDesignState& tds, const CoordsXYZ& coords)
973 {
974     tds.PreviewMin = { std::min(tds.PreviewMin.x, coords.x), std::min(tds.PreviewMin.y, coords.y),
975                        std::min(tds.PreviewMin.z, coords.z) };
976     tds.PreviewMax = { std::max(tds.PreviewMax.x, coords.x), std::max(tds.PreviewMax.y, coords.y),
977                        std::max(tds.PreviewMax.z, coords.z) };
978 }
979 
TrackDesignPlaceSceneryElementRemoveGhost(CoordsXY mapCoord,const TrackDesignSceneryElement & scenery,uint8_t rotation,int32_t originZ)980 static GameActions::Result::Ptr TrackDesignPlaceSceneryElementRemoveGhost(
981     CoordsXY mapCoord, const TrackDesignSceneryElement& scenery, uint8_t rotation, int32_t originZ)
982 {
983     auto entryInfo = TrackDesignPlaceSceneryElementGetEntry(scenery);
984     if (!entryInfo)
985     {
986         return std::make_unique<GameActions::Result>();
987     }
988 
989     if (_trackDesignPlaceStateSceneryUnavailable)
990     {
991         return std::make_unique<GameActions::Result>();
992     }
993 
994     int32_t z = (scenery.z * COORDS_Z_STEP) + originZ;
995     uint8_t sceneryRotation = (rotation + scenery.flags) & TILE_ELEMENT_DIRECTION_MASK;
996     const uint32_t flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND
997         | GAME_COMMAND_FLAG_GHOST;
998     std::unique_ptr<GameAction> ga;
999     switch (entryInfo->Type)
1000     {
1001         case ObjectType::SmallScenery:
1002         {
1003             uint8_t quadrant = (scenery.flags >> 2) + _currentTrackPieceDirection;
1004             quadrant &= 3;
1005 
1006             auto* sceneryEntry = get_small_scenery_entry(entryInfo->Index);
1007             if (!(!sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_FULL_TILE) && sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_DIAGONAL))
1008                 && sceneryEntry->HasFlag(
1009                     SMALL_SCENERY_FLAG_DIAGONAL | SMALL_SCENERY_FLAG_HALF_SPACE | SMALL_SCENERY_FLAG_THREE_QUARTERS))
1010             {
1011                 quadrant = 0;
1012             }
1013 
1014             ga = std::make_unique<SmallSceneryRemoveAction>(CoordsXYZ{ mapCoord.x, mapCoord.y, z }, quadrant, entryInfo->Index);
1015             break;
1016         }
1017         case ObjectType::LargeScenery:
1018             ga = std::make_unique<LargeSceneryRemoveAction>(CoordsXYZD{ mapCoord.x, mapCoord.y, z, sceneryRotation }, 0);
1019             break;
1020         case ObjectType::Walls:
1021             ga = std::make_unique<WallRemoveAction>(CoordsXYZD{ mapCoord.x, mapCoord.y, z, sceneryRotation });
1022             break;
1023         case ObjectType::Paths:
1024         case ObjectType::FootpathSurface:
1025             ga = std::make_unique<FootpathRemoveAction>(CoordsXYZ{ mapCoord.x, mapCoord.y, z });
1026             break;
1027         default:
1028             return std::make_unique<GameActions::Result>();
1029     }
1030 
1031     ga->SetFlags(flags);
1032     return GameActions::ExecuteNested(ga.get());
1033 }
1034 
TrackDesignPlaceSceneryElementGetPlaceZ(TrackDesignState & tds,const TrackDesignSceneryElement & scenery)1035 static bool TrackDesignPlaceSceneryElementGetPlaceZ(TrackDesignState& tds, const TrackDesignSceneryElement& scenery)
1036 {
1037     int32_t z = scenery.z * COORDS_Z_STEP + tds.PlaceZ;
1038     if (z < tds.PlaceSceneryZ)
1039     {
1040         tds.PlaceSceneryZ = z;
1041     }
1042 
1043     TrackDesignPlaceSceneryElementGetEntry(scenery);
1044     return true;
1045 }
1046 
TrackDesignPlaceSceneryElement(TrackDesignState & tds,CoordsXY mapCoord,uint8_t mode,const TrackDesignSceneryElement & scenery,uint8_t rotation,int32_t originZ)1047 static GameActions::Result::Ptr TrackDesignPlaceSceneryElement(
1048     TrackDesignState& tds, CoordsXY mapCoord, uint8_t mode, const TrackDesignSceneryElement& scenery, uint8_t rotation,
1049     int32_t originZ)
1050 {
1051     if (tds.PlaceOperation == PTD_OPERATION_DRAW_OUTLINES && mode == 0)
1052     {
1053         TrackDesignAddSelectedTile(mapCoord);
1054         return std::make_unique<GameActions::Result>();
1055     }
1056 
1057     if (tds.PlaceOperation == PTD_OPERATION_REMOVE_GHOST && mode == 0)
1058     {
1059         return TrackDesignPlaceSceneryElementRemoveGhost(mapCoord, scenery, rotation, originZ);
1060     }
1061 
1062     if (tds.PlaceOperation == PTD_OPERATION_GET_PLACE_Z)
1063     {
1064         TrackDesignPlaceSceneryElementGetPlaceZ(tds, scenery);
1065         return std::make_unique<GameActions::Result>();
1066     }
1067 
1068     money32 cost = 0;
1069 
1070     if (tds.PlaceOperation != PTD_OPERATION_PLACE_QUERY && tds.PlaceOperation != PTD_OPERATION_PLACE
1071         && tds.PlaceOperation != PTD_OPERATION_PLACE_GHOST && tds.PlaceOperation != PTD_OPERATION_PLACE_TRACK_PREVIEW)
1072     {
1073         return std::make_unique<GameActions::Result>();
1074     }
1075 
1076     auto entryInfo = TrackDesignPlaceSceneryElementGetEntry(scenery);
1077     if (!entryInfo)
1078     {
1079         return std::make_unique<GameActions::Result>();
1080     }
1081 
1082     int16_t z;
1083     uint8_t flags;
1084     uint8_t quadrant;
1085 
1086     switch (entryInfo->Type)
1087     {
1088         case ObjectType::SmallScenery:
1089         {
1090             if (mode != 0)
1091             {
1092                 return std::make_unique<GameActions::Result>();
1093             }
1094 
1095             rotation += scenery.flags;
1096             rotation &= 3;
1097             z = scenery.z * COORDS_Z_STEP + originZ;
1098             quadrant = ((scenery.flags >> 2) + _currentTrackPieceDirection) & 3;
1099 
1100             flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_PATH_SCENERY;
1101             if (tds.PlaceOperation == PTD_OPERATION_PLACE_TRACK_PREVIEW)
1102             {
1103                 flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_PATH_SCENERY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED
1104                     | GAME_COMMAND_FLAG_NO_SPEND;
1105             }
1106             else if (tds.PlaceOperation == PTD_OPERATION_PLACE_GHOST)
1107             {
1108                 flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_PATH_SCENERY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED
1109                     | GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_NO_SPEND;
1110             }
1111             else if (tds.PlaceOperation == PTD_OPERATION_PLACE_QUERY)
1112             {
1113                 flags = GAME_COMMAND_FLAG_PATH_SCENERY;
1114             }
1115             if (tds.IsReplay)
1116             {
1117                 flags |= GAME_COMMAND_FLAG_REPLAY;
1118             }
1119 
1120             auto smallSceneryPlace = SmallSceneryPlaceAction(
1121                 { mapCoord.x, mapCoord.y, z, rotation }, quadrant, entryInfo->Index, scenery.primary_colour,
1122                 scenery.secondary_colour);
1123 
1124             smallSceneryPlace.SetFlags(flags);
1125             auto res = flags & GAME_COMMAND_FLAG_APPLY ? GameActions::ExecuteNested(&smallSceneryPlace)
1126                                                        : GameActions::QueryNested(&smallSceneryPlace);
1127 
1128             cost = res->Error == GameActions::Status::Ok ? res->Cost : 0;
1129             break;
1130         }
1131         case ObjectType::LargeScenery:
1132         {
1133             if (mode != 0)
1134             {
1135                 return std::make_unique<GameActions::Result>();
1136             }
1137 
1138             rotation += scenery.flags;
1139             rotation &= 3;
1140 
1141             z = scenery.z * COORDS_Z_STEP + originZ;
1142 
1143             flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_PATH_SCENERY;
1144             if (tds.PlaceOperation == PTD_OPERATION_PLACE_TRACK_PREVIEW)
1145             {
1146                 flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_PATH_SCENERY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED
1147                     | GAME_COMMAND_FLAG_NO_SPEND;
1148             }
1149             else if (tds.PlaceOperation == PTD_OPERATION_PLACE_GHOST)
1150             {
1151                 flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_PATH_SCENERY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED
1152                     | GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_NO_SPEND;
1153             }
1154             else if (tds.PlaceOperation == PTD_OPERATION_PLACE_QUERY)
1155             {
1156                 flags = GAME_COMMAND_FLAG_PATH_SCENERY;
1157             }
1158             if (tds.IsReplay)
1159             {
1160                 flags |= GAME_COMMAND_FLAG_REPLAY;
1161             }
1162             auto sceneryPlaceAction = LargeSceneryPlaceAction(
1163                 { mapCoord.x, mapCoord.y, z, rotation }, entryInfo->Index, scenery.primary_colour, scenery.secondary_colour);
1164             sceneryPlaceAction.SetFlags(flags);
1165             auto res = flags & GAME_COMMAND_FLAG_APPLY ? GameActions::ExecuteNested(&sceneryPlaceAction)
1166                                                        : GameActions::QueryNested(&sceneryPlaceAction);
1167 
1168             cost = res->Cost;
1169             break;
1170         }
1171         case ObjectType::Walls:
1172         {
1173             if (mode != 0)
1174             {
1175                 return std::make_unique<GameActions::Result>();
1176             }
1177 
1178             z = scenery.z * COORDS_Z_STEP + originZ;
1179             rotation += scenery.flags;
1180             rotation &= 3;
1181 
1182             flags = GAME_COMMAND_FLAG_APPLY;
1183             if (tds.PlaceOperation == PTD_OPERATION_PLACE_TRACK_PREVIEW)
1184             {
1185                 flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_PATH_SCENERY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED
1186                     | GAME_COMMAND_FLAG_NO_SPEND;
1187             }
1188             else if (tds.PlaceOperation == PTD_OPERATION_PLACE_GHOST)
1189             {
1190                 flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND
1191                     | GAME_COMMAND_FLAG_GHOST;
1192             }
1193             else if (tds.PlaceOperation == PTD_OPERATION_PLACE_QUERY)
1194             {
1195                 flags = 0;
1196             }
1197             if (tds.IsReplay)
1198             {
1199                 flags |= GAME_COMMAND_FLAG_REPLAY;
1200             }
1201             auto wallPlaceAction = WallPlaceAction(
1202                 entryInfo->Index, { mapCoord.x, mapCoord.y, z }, rotation, scenery.primary_colour, scenery.secondary_colour,
1203                 (scenery.flags & 0xFC) >> 2);
1204             wallPlaceAction.SetFlags(flags);
1205             auto res = flags & GAME_COMMAND_FLAG_APPLY ? GameActions::ExecuteNested(&wallPlaceAction)
1206                                                        : GameActions::QueryNested(&wallPlaceAction);
1207 
1208             cost = res->Cost;
1209             break;
1210         }
1211         case ObjectType::Paths:
1212         case ObjectType::FootpathSurface:
1213             z = (scenery.z * COORDS_Z_STEP + originZ) / COORDS_Z_STEP;
1214             if (mode == 0)
1215             {
1216                 auto isQueue = scenery.IsQueue();
1217 
1218                 uint8_t bh = ((scenery.flags & 0xF) << rotation);
1219                 flags = bh >> 4;
1220                 bh = (bh | flags) & 0xF;
1221                 flags = (((scenery.flags >> 5) + rotation) & 3) << 5;
1222                 bh |= flags;
1223 
1224                 bh |= scenery.flags & 0x90;
1225 
1226                 flags = GAME_COMMAND_FLAG_APPLY;
1227                 if (tds.PlaceOperation == PTD_OPERATION_PLACE_TRACK_PREVIEW)
1228                 {
1229                     flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND;
1230                 }
1231                 if (tds.PlaceOperation == PTD_OPERATION_PLACE_GHOST)
1232                 {
1233                     flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND
1234                         | GAME_COMMAND_FLAG_GHOST;
1235                 }
1236                 if (tds.PlaceOperation == PTD_OPERATION_PLACE_QUERY)
1237                 {
1238                     flags = 0;
1239                 }
1240                 if (tds.IsReplay)
1241                 {
1242                     flags |= GAME_COMMAND_FLAG_REPLAY;
1243                 }
1244                 uint8_t slope = ((bh >> 5) & 0x3) | ((bh >> 2) & 0x4);
1245                 uint8_t edges = bh & 0xF;
1246                 PathConstructFlags constructFlags = 0;
1247                 if (isQueue)
1248                     constructFlags |= PathConstructFlag::IsQueue;
1249                 if (entryInfo->Type == ObjectType::Paths)
1250                     constructFlags |= PathConstructFlag::IsLegacyPathObject;
1251                 auto footpathPlaceAction = FootpathPlaceFromTrackAction(
1252                     { mapCoord.x, mapCoord.y, z * COORDS_Z_STEP }, slope, entryInfo->Index, entryInfo->SecondaryIndex, edges,
1253                     constructFlags);
1254                 footpathPlaceAction.SetFlags(flags);
1255                 auto res = flags & GAME_COMMAND_FLAG_APPLY ? GameActions::ExecuteNested(&footpathPlaceAction)
1256                                                            : GameActions::QueryNested(&footpathPlaceAction);
1257                 // Ignore failures
1258                 cost = res->Error == GameActions::Status::Ok ? res->Cost : 0;
1259             }
1260             else
1261             {
1262                 if (tds.PlaceOperation == PTD_OPERATION_PLACE_QUERY)
1263                 {
1264                     return std::make_unique<GameActions::Result>();
1265                 }
1266 
1267                 auto* pathElement = map_get_path_element_at({ mapCoord.x / 32, mapCoord.y / 32, z });
1268                 if (pathElement == nullptr)
1269                 {
1270                     return std::make_unique<GameActions::Result>();
1271                 }
1272 
1273                 footpath_queue_chain_reset();
1274                 footpath_remove_edges_at(mapCoord, reinterpret_cast<TileElement*>(pathElement));
1275 
1276                 flags = GAME_COMMAND_FLAG_APPLY;
1277                 if (tds.PlaceOperation == PTD_OPERATION_PLACE_TRACK_PREVIEW)
1278                 {
1279                     flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND;
1280                 }
1281                 if (tds.PlaceOperation == PTD_OPERATION_PLACE_GHOST)
1282                 {
1283                     flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND
1284                         | GAME_COMMAND_FLAG_GHOST;
1285                 }
1286                 if (tds.IsReplay)
1287                 {
1288                     flags |= GAME_COMMAND_FLAG_REPLAY;
1289                 }
1290                 footpath_connect_edges(mapCoord, reinterpret_cast<TileElement*>(pathElement), flags);
1291                 footpath_update_queue_chains();
1292                 return std::make_unique<GameActions::Result>();
1293             }
1294             break;
1295         default:
1296             _trackDesignPlaceStateSceneryUnavailable = true;
1297             return std::make_unique<GameActions::Result>();
1298     }
1299 
1300     auto res = std::make_unique<GameActions::Result>();
1301     res->Cost = cost;
1302 
1303     return res;
1304 }
1305 
1306 /**
1307  *
1308  *  rct2: 0x006D0964
1309  */
TrackDesignPlaceAllScenery(TrackDesignState & tds,const std::vector<TrackDesignSceneryElement> & sceneryList)1310 static GameActions::Result::Ptr TrackDesignPlaceAllScenery(
1311     TrackDesignState& tds, const std::vector<TrackDesignSceneryElement>& sceneryList)
1312 {
1313     const auto& origin = tds.Origin;
1314 
1315     money32 cost = 0;
1316 
1317     for (uint8_t mode = 0; mode <= 1; mode++)
1318     {
1319         if (!sceneryList.empty())
1320         {
1321             tds.HasScenery = true;
1322         }
1323 
1324         if (!tds.PlaceScenery)
1325         {
1326             continue;
1327         }
1328 
1329         for (const auto& scenery : sceneryList)
1330         {
1331             uint8_t rotation = _currentTrackPieceDirection;
1332             TileCoordsXY tileCoords = TileCoordsXY(origin);
1333             TileCoordsXY offsets = { scenery.x, scenery.y };
1334             tileCoords += offsets.Rotate(rotation);
1335 
1336             auto mapCoord = CoordsXYZ{ tileCoords.ToCoordsXY(), origin.z };
1337             TrackDesignUpdatePreviewBounds(tds, mapCoord);
1338 
1339             auto placementRes = TrackDesignPlaceSceneryElement(tds, mapCoord, mode, scenery, rotation, origin.z);
1340             if (placementRes->Error != GameActions::Status::Ok)
1341             {
1342                 // Allow operation to fail when its removing ghosts.
1343                 if (tds.PlaceOperation != PTD_OPERATION_REMOVE_GHOST)
1344                 {
1345                     return placementRes;
1346                 }
1347             }
1348             cost += placementRes->Cost;
1349         }
1350     }
1351 
1352     auto res = std::make_unique<GameActions::Result>();
1353     res->Cost = cost;
1354 
1355     return res;
1356 }
1357 
TrackDesignPlaceMaze(TrackDesignState & tds,TrackDesign * td6,const CoordsXYZ & coords,Ride * ride)1358 static GameActions::Result::Ptr TrackDesignPlaceMaze(
1359     TrackDesignState& tds, TrackDesign* td6, const CoordsXYZ& coords, Ride* ride)
1360 {
1361     if (tds.PlaceOperation == PTD_OPERATION_DRAW_OUTLINES)
1362     {
1363         gMapSelectionTiles.clear();
1364         gMapSelectArrowPosition = CoordsXYZ{ coords, tile_element_height(coords) };
1365         gMapSelectArrowDirection = _currentTrackPieceDirection;
1366     }
1367 
1368     tds.PlaceZ = 0;
1369     money32 totalCost = 0;
1370 
1371     for (const auto& maze_element : td6->maze_elements)
1372     {
1373         uint8_t rotation = _currentTrackPieceDirection & 3;
1374         CoordsXY mazeMapPos = TileCoordsXY(maze_element.x, maze_element.y).ToCoordsXY();
1375         auto mapCoord = mazeMapPos.Rotate(rotation);
1376         mapCoord += coords;
1377 
1378         TrackDesignUpdatePreviewBounds(tds, { mapCoord, coords.z });
1379 
1380         if (tds.PlaceOperation == PTD_OPERATION_DRAW_OUTLINES)
1381         {
1382             TrackDesignAddSelectedTile(mapCoord);
1383         }
1384 
1385         if (tds.PlaceOperation == PTD_OPERATION_PLACE_QUERY || tds.PlaceOperation == PTD_OPERATION_PLACE
1386             || tds.PlaceOperation == PTD_OPERATION_PLACE_GHOST || tds.PlaceOperation == PTD_OPERATION_PLACE_TRACK_PREVIEW)
1387         {
1388             uint8_t flags;
1389             money32 cost = 0;
1390             uint16_t maze_entry;
1391             switch (maze_element.type)
1392             {
1393                 case MAZE_ELEMENT_TYPE_ENTRANCE:
1394                     // entrance
1395                     rotation += maze_element.direction;
1396                     rotation &= 3;
1397 
1398                     flags = GAME_COMMAND_FLAG_APPLY;
1399 
1400                     if (tds.PlaceOperation == PTD_OPERATION_PLACE_QUERY)
1401                     {
1402                         auto res = RideEntranceExitPlaceAction::TrackPlaceQuery({ mapCoord, coords.z }, false);
1403                         if (res->Error != GameActions::Status::Ok)
1404                         {
1405                             return res;
1406                         }
1407                         cost = res->Cost;
1408                     }
1409                     else
1410                     {
1411                         if (tds.PlaceOperation == PTD_OPERATION_PLACE_TRACK_PREVIEW)
1412                         {
1413                             flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED
1414                                 | GAME_COMMAND_FLAG_NO_SPEND;
1415                         }
1416                         else if (tds.PlaceOperation == PTD_OPERATION_PLACE_GHOST)
1417                         {
1418                             flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND
1419                                 | GAME_COMMAND_FLAG_GHOST;
1420                         }
1421                         if (tds.IsReplay)
1422                         {
1423                             flags |= GAME_COMMAND_FLAG_REPLAY;
1424                         }
1425                         auto rideEntranceExitPlaceAction = RideEntranceExitPlaceAction(mapCoord, rotation, ride->id, 0, false);
1426                         rideEntranceExitPlaceAction.SetFlags(flags);
1427                         auto res = GameActions::ExecuteNested(&rideEntranceExitPlaceAction);
1428                         if (res->Error != GameActions::Status::Ok)
1429                         {
1430                             return res;
1431                         }
1432                         cost = res->Cost;
1433                     }
1434                     tds.EntranceExitPlaced = true;
1435                     _trackDesignPlaceStateEntranceExitPlaced = true;
1436                     break;
1437                 case MAZE_ELEMENT_TYPE_EXIT:
1438                     // exit
1439                     rotation += maze_element.direction;
1440                     rotation &= 3;
1441 
1442                     flags = GAME_COMMAND_FLAG_APPLY;
1443 
1444                     if (tds.PlaceOperation == PTD_OPERATION_PLACE_QUERY)
1445                     {
1446                         auto res = RideEntranceExitPlaceAction::TrackPlaceQuery({ mapCoord, coords.z }, true);
1447                         if (res->Error != GameActions::Status::Ok)
1448                         {
1449                             return res;
1450                         }
1451                         cost = res->Cost;
1452                     }
1453                     else
1454                     {
1455                         if (tds.PlaceOperation == PTD_OPERATION_PLACE_TRACK_PREVIEW)
1456                         {
1457                             flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED
1458                                 | GAME_COMMAND_FLAG_NO_SPEND;
1459                         }
1460                         else if (tds.PlaceOperation == PTD_OPERATION_PLACE_GHOST)
1461                         {
1462                             flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND
1463                                 | GAME_COMMAND_FLAG_GHOST;
1464                         }
1465                         if (tds.IsReplay)
1466                         {
1467                             flags |= GAME_COMMAND_FLAG_REPLAY;
1468                         }
1469                         auto rideEntranceExitPlaceAction = RideEntranceExitPlaceAction(mapCoord, rotation, ride->id, 0, true);
1470                         rideEntranceExitPlaceAction.SetFlags(flags);
1471                         auto res = GameActions::ExecuteNested(&rideEntranceExitPlaceAction);
1472                         if (res->Error != GameActions::Status::Ok)
1473                         {
1474                             return res;
1475                         }
1476                         cost = res->Cost;
1477                     }
1478                     tds.EntranceExitPlaced = true;
1479                     _trackDesignPlaceStateEntranceExitPlaced = true;
1480                     break;
1481                 default:
1482                     maze_entry = Numerics::rol16(maze_element.maze_entry, rotation * 4);
1483 
1484                     if (tds.PlaceOperation == PTD_OPERATION_PLACE_TRACK_PREVIEW)
1485                     {
1486                         flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND;
1487                     }
1488                     else if (tds.PlaceOperation == PTD_OPERATION_PLACE_GHOST)
1489                     {
1490                         flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND
1491                             | GAME_COMMAND_FLAG_GHOST;
1492                     }
1493                     else if (tds.PlaceOperation == PTD_OPERATION_PLACE_QUERY)
1494                     {
1495                         flags = 0;
1496                     }
1497                     else
1498                     {
1499                         flags = GAME_COMMAND_FLAG_APPLY;
1500                     }
1501                     if (tds.IsReplay)
1502                     {
1503                         flags |= GAME_COMMAND_FLAG_REPLAY;
1504                     }
1505 
1506                     auto mazePlace = MazePlaceTrackAction({ mapCoord, coords.z }, ride->id, maze_entry);
1507                     mazePlace.SetFlags(flags);
1508                     auto res = flags & GAME_COMMAND_FLAG_APPLY ? GameActions::ExecuteNested(&mazePlace)
1509                                                                : GameActions::QueryNested(&mazePlace);
1510                     if (res->Error != GameActions::Status::Ok)
1511                     {
1512                         return res;
1513                     }
1514                     cost = res->Cost;
1515                     break;
1516             }
1517 
1518             totalCost += cost;
1519         }
1520 
1521         if (tds.PlaceOperation == PTD_OPERATION_GET_PLACE_Z)
1522         {
1523             if (!map_is_location_valid(mapCoord))
1524             {
1525                 continue;
1526             }
1527 
1528             auto surfaceElement = map_get_surface_element_at(mapCoord);
1529             if (surfaceElement == nullptr)
1530                 continue;
1531             int16_t surfaceZ = surfaceElement->GetBaseZ();
1532             if (surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP)
1533             {
1534                 surfaceZ += LAND_HEIGHT_STEP;
1535                 if (surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)
1536                 {
1537                     surfaceZ += LAND_HEIGHT_STEP;
1538                 }
1539             }
1540 
1541             int16_t waterZ = surfaceElement->GetWaterHeight();
1542             if (waterZ > 0 && waterZ > surfaceZ)
1543             {
1544                 surfaceZ = waterZ;
1545             }
1546 
1547             int16_t temp_z = coords.z + tds.PlaceZ - surfaceZ;
1548             if (temp_z < 0)
1549             {
1550                 tds.PlaceZ -= temp_z;
1551             }
1552         }
1553     }
1554 
1555     if (tds.PlaceOperation == PTD_OPERATION_REMOVE_GHOST)
1556     {
1557         ride_action_modify(
1558             ride, RIDE_MODIFY_DEMOLISH,
1559             GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND
1560                 | GAME_COMMAND_FLAG_GHOST);
1561     }
1562 
1563     tds.Origin = coords;
1564 
1565     auto res = std::make_unique<GameActions::Result>();
1566     res->Cost = totalCost;
1567 
1568     return res;
1569 }
1570 
TrackDesignPlaceRide(TrackDesignState & tds,TrackDesign * td6,const CoordsXYZ & origin,Ride * ride)1571 static GameActions::Result::Ptr TrackDesignPlaceRide(
1572     TrackDesignState& tds, TrackDesign* td6, const CoordsXYZ& origin, Ride* ride)
1573 {
1574     tds.Origin = origin;
1575     if (tds.PlaceOperation == PTD_OPERATION_DRAW_OUTLINES)
1576     {
1577         gMapSelectionTiles.clear();
1578         gMapSelectArrowPosition = CoordsXYZ{ origin, tile_element_height(origin) };
1579         gMapSelectArrowDirection = _currentTrackPieceDirection;
1580     }
1581 
1582     tds.PlaceZ = 0;
1583     money32 totalCost = 0;
1584     uint8_t rotation = _currentTrackPieceDirection;
1585 
1586     // Track elements
1587     auto newCoords = origin;
1588     for (const auto& track : td6->track_elements)
1589     {
1590         auto trackType = track.type;
1591         const auto& ted = GetTrackElementDescriptor(trackType);
1592 
1593         TrackDesignUpdatePreviewBounds(tds, newCoords);
1594 
1595         switch (tds.PlaceOperation)
1596         {
1597             case PTD_OPERATION_DRAW_OUTLINES:
1598                 for (const rct_preview_track* trackBlock = ted.Block; trackBlock->index != 0xFF; trackBlock++)
1599                 {
1600                     auto tile = CoordsXY{ newCoords } + CoordsXY{ trackBlock->x, trackBlock->y }.Rotate(rotation);
1601                     TrackDesignUpdatePreviewBounds(tds, { tile, newCoords.z });
1602                     TrackDesignAddSelectedTile(tile);
1603                 }
1604                 break;
1605             case PTD_OPERATION_REMOVE_GHOST:
1606             {
1607                 const rct_track_coordinates* trackCoordinates = &ted.Coordinates;
1608                 const rct_preview_track* trackBlock = ted.Block;
1609                 int32_t tempZ = newCoords.z - trackCoordinates->z_begin + trackBlock->z;
1610                 auto trackRemoveAction = TrackRemoveAction(
1611                     trackType, 0, { newCoords, tempZ, static_cast<Direction>(rotation & 3) });
1612                 trackRemoveAction.SetFlags(
1613                     GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND | GAME_COMMAND_FLAG_GHOST);
1614                 GameActions::ExecuteNested(&trackRemoveAction);
1615                 break;
1616             }
1617             case PTD_OPERATION_PLACE_QUERY:
1618             case PTD_OPERATION_PLACE:
1619             case PTD_OPERATION_PLACE_GHOST:
1620             case PTD_OPERATION_PLACE_TRACK_PREVIEW:
1621             {
1622                 const rct_track_coordinates* trackCoordinates = &ted.Coordinates;
1623 
1624                 // di
1625                 int16_t tempZ = newCoords.z - trackCoordinates->z_begin;
1626                 uint32_t trackColour = (track.flags >> 4) & 0x3;
1627                 uint32_t brakeSpeed = (track.flags & 0x0F) * 2;
1628                 uint32_t seatRotation = track.flags & 0x0F;
1629 
1630                 int32_t liftHillAndAlternativeState = 0;
1631                 if (track.flags & RCT12_TRACK_ELEMENT_TYPE_FLAG_CHAIN_LIFT)
1632                 {
1633                     liftHillAndAlternativeState |= 1;
1634                 }
1635                 if (track.flags & TD6_TRACK_ELEMENT_FLAG_INVERTED)
1636                 {
1637                     liftHillAndAlternativeState |= 2;
1638                 }
1639 
1640                 uint8_t flags = GAME_COMMAND_FLAG_APPLY;
1641                 if (tds.PlaceOperation == PTD_OPERATION_PLACE_TRACK_PREVIEW)
1642                 {
1643                     flags |= GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED;
1644                     flags |= GAME_COMMAND_FLAG_NO_SPEND;
1645                 }
1646                 else if (tds.PlaceOperation == PTD_OPERATION_PLACE_GHOST)
1647                 {
1648                     flags |= GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED;
1649                     flags |= GAME_COMMAND_FLAG_NO_SPEND;
1650                     flags |= GAME_COMMAND_FLAG_GHOST;
1651                 }
1652                 else if (tds.PlaceOperation == PTD_OPERATION_PLACE_QUERY)
1653                 {
1654                     flags = GAME_COMMAND_FLAG_NO_SPEND;
1655                 }
1656                 if (tds.IsReplay)
1657                 {
1658                     flags |= GAME_COMMAND_FLAG_REPLAY;
1659                 }
1660 
1661                 auto trackPlaceAction = TrackPlaceAction(
1662                     _currentRideIndex, trackType, { newCoords, tempZ, static_cast<uint8_t>(rotation) }, brakeSpeed, trackColour,
1663                     seatRotation, liftHillAndAlternativeState, true);
1664                 trackPlaceAction.SetFlags(flags);
1665 
1666                 auto res = flags & GAME_COMMAND_FLAG_APPLY ? GameActions::ExecuteNested(&trackPlaceAction)
1667                                                            : GameActions::QueryNested(&trackPlaceAction);
1668                 if (res->Error != GameActions::Status::Ok)
1669                 {
1670                     return res;
1671                 }
1672 
1673                 totalCost += res->Cost;
1674                 break;
1675             }
1676             case PTD_OPERATION_GET_PLACE_Z:
1677             {
1678                 int32_t tempZ = newCoords.z - ted.Coordinates.z_begin;
1679                 for (const rct_preview_track* trackBlock = ted.Block; trackBlock->index != 0xFF; trackBlock++)
1680                 {
1681                     auto tile = CoordsXY{ newCoords } + CoordsXY{ trackBlock->x, trackBlock->y }.Rotate(rotation);
1682                     if (!map_is_location_valid(tile))
1683                     {
1684                         continue;
1685                     }
1686 
1687                     auto surfaceElement = map_get_surface_element_at(tile);
1688                     if (surfaceElement == nullptr)
1689                     {
1690                         return std::make_unique<GameActions::Result>(
1691                             GameActions::Status::InvalidParameters, STR_NONE, STR_NONE);
1692                     }
1693 
1694                     int32_t surfaceZ = surfaceElement->GetBaseZ();
1695                     if (surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP)
1696                     {
1697                         surfaceZ += LAND_HEIGHT_STEP;
1698                         if (surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)
1699                         {
1700                             surfaceZ += LAND_HEIGHT_STEP;
1701                         }
1702                     }
1703 
1704                     auto waterZ = surfaceElement->GetWaterHeight();
1705                     if (waterZ > 0 && waterZ > surfaceZ)
1706                     {
1707                         surfaceZ = waterZ;
1708                     }
1709                     int32_t heightDifference = tempZ + tds.PlaceZ + trackBlock->z - surfaceZ;
1710                     if (heightDifference < 0)
1711                     {
1712                         tds.PlaceZ -= heightDifference;
1713                     }
1714                 }
1715                 break;
1716             }
1717         }
1718 
1719         const rct_track_coordinates& track_coordinates = ted.Coordinates;
1720         auto offsetAndRotatedTrack = CoordsXY{ newCoords }
1721             + CoordsXY{ track_coordinates.x, track_coordinates.y }.Rotate(rotation);
1722 
1723         newCoords = { offsetAndRotatedTrack, newCoords.z - track_coordinates.z_begin + track_coordinates.z_end };
1724         rotation = (rotation + track_coordinates.rotation_end - track_coordinates.rotation_begin) & 3;
1725         if (track_coordinates.rotation_end & (1 << 2))
1726         {
1727             rotation |= (1 << 2);
1728         }
1729         else
1730         {
1731             newCoords += CoordsDirectionDelta[rotation];
1732         }
1733     }
1734 
1735     // Entrance elements
1736     for (const auto& entrance : td6->entrance_elements)
1737     {
1738         rotation = _currentTrackPieceDirection & 3;
1739         CoordsXY entranceMapPos{ entrance.x, entrance.y };
1740         auto rotatedEntranceMapPos = entranceMapPos.Rotate(rotation);
1741         newCoords = { rotatedEntranceMapPos + tds.Origin, newCoords.z };
1742 
1743         TrackDesignUpdatePreviewBounds(tds, newCoords);
1744 
1745         switch (tds.PlaceOperation)
1746         {
1747             case PTD_OPERATION_DRAW_OUTLINES:
1748                 TrackDesignAddSelectedTile(newCoords);
1749                 break;
1750             case PTD_OPERATION_PLACE_QUERY:
1751             case PTD_OPERATION_PLACE:
1752             case PTD_OPERATION_PLACE_GHOST:
1753             case PTD_OPERATION_PLACE_TRACK_PREVIEW:
1754             {
1755                 rotation = (rotation + entrance.direction) & 3;
1756                 if (tds.PlaceOperation != PTD_OPERATION_PLACE_QUERY)
1757                 {
1758                     auto tile = CoordsXY{ newCoords } + CoordsDirectionDelta[rotation];
1759                     TileElement* tile_element = map_get_first_element_at(tile);
1760                     newCoords.z = tds.Origin.z / COORDS_Z_STEP;
1761                     newCoords.z += entrance.z;
1762                     if (tile_element == nullptr)
1763                     {
1764                         return std::make_unique<GameActions::Result>(
1765                             GameActions::Status::InvalidParameters, STR_NONE, STR_NONE);
1766                     }
1767 
1768                     do
1769                     {
1770                         if (tile_element->GetType() != TILE_ELEMENT_TYPE_TRACK)
1771                         {
1772                             continue;
1773                         }
1774                         if (tile_element->base_height != newCoords.z)
1775                         {
1776                             continue;
1777                         }
1778 
1779                         auto stationIndex = tile_element->AsTrack()->GetStationIndex();
1780                         uint8_t flags = GAME_COMMAND_FLAG_APPLY;
1781                         if (tds.PlaceOperation == PTD_OPERATION_PLACE_TRACK_PREVIEW)
1782                         {
1783                             flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED
1784                                 | GAME_COMMAND_FLAG_NO_SPEND;
1785                         }
1786                         if (tds.PlaceOperation == PTD_OPERATION_PLACE_GHOST)
1787                         {
1788                             flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND
1789                                 | GAME_COMMAND_FLAG_GHOST;
1790                         }
1791                         if (tds.PlaceOperation == PTD_OPERATION_PLACE_QUERY)
1792                         {
1793                             flags = 0;
1794                         }
1795                         if (tds.IsReplay)
1796                         {
1797                             flags |= GAME_COMMAND_FLAG_REPLAY;
1798                         }
1799 
1800                         auto rideEntranceExitPlaceAction = RideEntranceExitPlaceAction(
1801                             newCoords, rotation, ride->id, stationIndex, entrance.isExit);
1802                         rideEntranceExitPlaceAction.SetFlags(flags);
1803                         auto res = flags & GAME_COMMAND_FLAG_APPLY ? GameActions::ExecuteNested(&rideEntranceExitPlaceAction)
1804                                                                    : GameActions::QueryNested(&rideEntranceExitPlaceAction);
1805 
1806                         if (res->Error != GameActions::Status::Ok)
1807                         {
1808                             return res;
1809                         }
1810                         totalCost += res->Cost;
1811                         tds.EntranceExitPlaced = true;
1812                         _trackDesignPlaceStateEntranceExitPlaced = true;
1813                         break;
1814                     } while (!(tile_element++)->IsLastForTile());
1815                 }
1816                 else
1817                 {
1818                     newCoords.z = entrance.z * COORDS_Z_STEP;
1819                     newCoords.z += tds.Origin.z;
1820 
1821                     auto res = RideEntranceExitPlaceAction::TrackPlaceQuery(newCoords, false);
1822                     if (res->Error != GameActions::Status::Ok)
1823                     {
1824                         return std::make_unique<GameActions::Result>(
1825                             GameActions::Status::InvalidParameters, STR_NONE, STR_NONE);
1826                     }
1827 
1828                     totalCost += res->Cost;
1829                     tds.EntranceExitPlaced = true;
1830                     _trackDesignPlaceStateEntranceExitPlaced = true;
1831                 }
1832                 break;
1833             }
1834         }
1835     }
1836 
1837     if (tds.PlaceOperation == PTD_OPERATION_REMOVE_GHOST)
1838     {
1839         ride->ValidateStations();
1840         ride->Delete();
1841     }
1842 
1843     auto res = std::make_unique<GameActions::Result>(GameActions::Status::Ok, STR_NONE, STR_NONE);
1844     res->Cost = totalCost;
1845 
1846     return res;
1847 }
1848 
1849 /**
1850  * Places a virtual track. This can involve highlighting the surface tiles and showing the track layout. It is also used by
1851  * the track preview window to place the whole track.
1852  * Depending on the value of bl it modifies the function.
1853  * bl == 0, Draw outlines on the ground
1854  * bl == 1,
1855  * bl == 2,
1856  * bl == 3, Returns the z value of a successful placement. Only lower 16 bits are the value, the rest may be garbage?
1857  * bl == 4,
1858  * bl == 5, Returns cost to create the track. All 32 bits are used. Places the track. (used by the preview)
1859  * bl == 6, Clear white outlined track.
1860  *  rct2: 0x006D01B3
1861  */
TrackDesignPlaceVirtual(TrackDesignState & tds,TrackDesign * td6,uint8_t ptdOperation,bool placeScenery,Ride * ride,const CoordsXYZ & coords)1862 static GameActions::Result::Ptr TrackDesignPlaceVirtual(
1863     TrackDesignState& tds, TrackDesign* td6, uint8_t ptdOperation, bool placeScenery, Ride* ride, const CoordsXYZ& coords)
1864 {
1865     _trackDesignPlaceStateSceneryUnavailable = false;
1866     _trackDesignPlaceStateEntranceExitPlaced = false;
1867 
1868     tds.PlaceScenery = placeScenery;
1869     tds.EntranceExitPlaced = false;
1870     tds.HasScenery = false;
1871 
1872     tds.IsReplay = ptdOperation & PTD_OPERATION_FLAG_IS_REPLAY;
1873     ptdOperation &= ~PTD_OPERATION_FLAG_IS_REPLAY;
1874     tds.PlaceOperation = ptdOperation;
1875 
1876     tds.PreviewMin = coords;
1877     tds.PreviewMax = coords;
1878     tds.PlaceSceneryZ = 0;
1879 
1880     if (gTrackDesignSceneryToggle)
1881     {
1882         tds.PlaceScenery = false;
1883     }
1884 
1885     _currentRideIndex = ride->id;
1886 
1887     GameActions::Result::Ptr trackPlaceRes;
1888     if (td6->type == RIDE_TYPE_MAZE)
1889     {
1890         trackPlaceRes = TrackDesignPlaceMaze(tds, td6, coords, ride);
1891     }
1892     else
1893     {
1894         trackPlaceRes = TrackDesignPlaceRide(tds, td6, coords, ride);
1895     }
1896 
1897     if (trackPlaceRes->Error != GameActions::Status::Ok)
1898     {
1899         return trackPlaceRes;
1900     }
1901 
1902     // Scenery elements
1903     auto sceneryPlaceRes = TrackDesignPlaceAllScenery(tds, td6->scenery_elements);
1904     if (sceneryPlaceRes->Error != GameActions::Status::Ok)
1905     {
1906         return sceneryPlaceRes;
1907     }
1908 
1909     // 0x6D0FE6
1910     if (tds.PlaceOperation == PTD_OPERATION_DRAW_OUTLINES)
1911     {
1912         gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE_CONSTRUCT;
1913         gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE_ARROW;
1914         gMapSelectFlags &= ~MAP_SELECT_FLAG_GREEN;
1915         map_invalidate_map_selection_tiles();
1916     }
1917 
1918     auto res = std::make_unique<GameActions::Result>();
1919     res->Cost = trackPlaceRes->Cost + sceneryPlaceRes->Cost;
1920 
1921     return res;
1922 }
1923 
TrackDesignPlace(TrackDesign * td6,uint32_t flags,bool placeScenery,Ride * ride,const CoordsXYZ & coords)1924 GameActions::Result::Ptr TrackDesignPlace(
1925     TrackDesign* td6, uint32_t flags, bool placeScenery, Ride* ride, const CoordsXYZ& coords)
1926 {
1927     uint32_t ptdOperation = (flags & GAME_COMMAND_FLAG_APPLY) != 0 ? PTD_OPERATION_PLACE : PTD_OPERATION_PLACE_QUERY;
1928     if ((flags & GAME_COMMAND_FLAG_APPLY) != 0 && (flags & GAME_COMMAND_FLAG_GHOST) != 0)
1929     {
1930         ptdOperation = PTD_OPERATION_PLACE_GHOST;
1931     }
1932     if (flags & GAME_COMMAND_FLAG_REPLAY)
1933         ptdOperation |= PTD_OPERATION_FLAG_IS_REPLAY;
1934 
1935     TrackDesignState tds{};
1936     return TrackDesignPlaceVirtual(tds, td6, ptdOperation, placeScenery, ride, coords);
1937 }
1938 
TrackDesignPreviewRemoveGhosts(TrackDesign * td6,Ride * ride,const CoordsXYZ & coords)1939 void TrackDesignPreviewRemoveGhosts(TrackDesign* td6, Ride* ride, const CoordsXYZ& coords)
1940 {
1941     TrackDesignState tds{};
1942     TrackDesignPlaceVirtual(tds, td6, PTD_OPERATION_REMOVE_GHOST, true, ride, coords);
1943 }
1944 
TrackDesignPreviewDrawOutlines(TrackDesign * td6,Ride * ride,const CoordsXYZ & coords)1945 void TrackDesignPreviewDrawOutlines(TrackDesign* td6, Ride* ride, const CoordsXYZ& coords)
1946 {
1947     TrackDesignState tds{};
1948     TrackDesignPlaceVirtual(tds, td6, PTD_OPERATION_DRAW_OUTLINES, true, ride, coords);
1949 }
1950 
TrackDesignGetZPlacement(TrackDesignState & tds,TrackDesign * td6,Ride * ride,const CoordsXYZ & coords)1951 static int32_t TrackDesignGetZPlacement(TrackDesignState& tds, TrackDesign* td6, Ride* ride, const CoordsXYZ& coords)
1952 {
1953     TrackDesignPlaceVirtual(tds, td6, PTD_OPERATION_GET_PLACE_Z, true, ride, coords);
1954 
1955     // Change from vanilla: originally, _trackDesignPlaceSceneryZ was not subtracted
1956     // from _trackDesignPlaceZ, causing bug #259.
1957     return tds.PlaceZ - tds.PlaceSceneryZ;
1958 }
1959 
TrackDesignGetZPlacement(TrackDesign * td6,Ride * ride,const CoordsXYZ & coords)1960 int32_t TrackDesignGetZPlacement(TrackDesign* td6, Ride* ride, const CoordsXYZ& coords)
1961 {
1962     TrackDesignState tds{};
1963     return TrackDesignGetZPlacement(tds, td6, ride, coords);
1964 }
1965 
TrackDesignCreateRide(int32_t type,int32_t subType,int32_t flags,ride_id_t * outRideIndex)1966 static money32 TrackDesignCreateRide(int32_t type, int32_t subType, int32_t flags, ride_id_t* outRideIndex)
1967 {
1968     // Don't set colours as will be set correctly later.
1969     auto gameAction = RideCreateAction(type, subType, 0, 0);
1970     gameAction.SetFlags(flags);
1971 
1972     auto res = GameActions::ExecuteNested(&gameAction);
1973 
1974     // Callee's of this function expect MONEY32_UNDEFINED in case of failure.
1975     if (res->Error != GameActions::Status::Ok)
1976     {
1977         return MONEY32_UNDEFINED;
1978     }
1979 
1980     *outRideIndex = res->GetData<ride_id_t>();
1981 
1982     return res->Cost;
1983 }
1984 
1985 /**
1986  *
1987  *  rct2: 0x006D2189
1988  * ebx = ride_id
1989  * cost = edi
1990  */
TrackDesignPlacePreview(TrackDesignState & tds,TrackDesign * td6,money32 * cost,Ride ** outRide,uint8_t * flags)1991 static bool TrackDesignPlacePreview(TrackDesignState& tds, TrackDesign* td6, money32* cost, Ride** outRide, uint8_t* flags)
1992 {
1993     *outRide = nullptr;
1994     *flags = 0;
1995 
1996     auto& objManager = GetContext()->GetObjectManager();
1997     auto entry_index = objManager.GetLoadedObjectEntryIndex(td6->vehicle_object);
1998 
1999     ride_id_t rideIndex;
2000     uint8_t rideCreateFlags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND;
2001     if (TrackDesignCreateRide(td6->type, entry_index, rideCreateFlags, &rideIndex) == MONEY32_UNDEFINED)
2002     {
2003         return false;
2004     }
2005 
2006     auto ride = get_ride(rideIndex);
2007     if (ride == nullptr)
2008         return false;
2009 
2010     ride->custom_name = {};
2011 
2012     auto stationIdentifier = GetStationIdentifierFromStyle(td6->entrance_style);
2013     ride->entrance_style = objManager.GetLoadedObjectEntryIndex(stationIdentifier);
2014     if (ride->entrance_style == OBJECT_ENTRY_INDEX_NULL)
2015     {
2016         ride->entrance_style = gLastEntranceStyle;
2017     }
2018 
2019     for (int32_t i = 0; i < RCT12_NUM_COLOUR_SCHEMES; i++)
2020     {
2021         ride->track_colour[i].main = td6->track_spine_colour[i];
2022         ride->track_colour[i].additional = td6->track_rail_colour[i];
2023         ride->track_colour[i].supports = td6->track_support_colour[i];
2024     }
2025 
2026     // Flat rides need their vehicle colours loaded for display
2027     // in the preview window
2028     if (!GetRideTypeDescriptor(td6->type).HasFlag(RIDE_TYPE_FLAG_HAS_TRACK))
2029     {
2030         for (int32_t i = 0; i < RCT12_MAX_VEHICLE_COLOURS; i++)
2031         {
2032             ride->vehicle_colours[i].Body = td6->vehicle_colours[i].body_colour;
2033             ride->vehicle_colours[i].Trim = td6->vehicle_colours[i].trim_colour;
2034             ride->vehicle_colours[i].Ternary = td6->vehicle_additional_colour[i];
2035         }
2036     }
2037 
2038     _trackDesignDrawingPreview = true;
2039     uint8_t backup_rotation = _currentTrackPieceDirection;
2040     uint32_t backup_park_flags = gParkFlags;
2041     gParkFlags &= ~PARK_FLAGS_FORBID_HIGH_CONSTRUCTION;
2042     int32_t mapSize = gMapSize << 4;
2043 
2044     _currentTrackPieceDirection = 0;
2045     int32_t z = TrackDesignGetZPlacement(tds, td6, GetOrAllocateRide(PreviewRideId), { mapSize, mapSize, 16 });
2046 
2047     if (tds.HasScenery)
2048     {
2049         *flags |= TRACK_DESIGN_FLAG_HAS_SCENERY;
2050     }
2051 
2052     z += 16 - tds.PlaceSceneryZ;
2053 
2054     bool placeScenery = true;
2055     if (_trackDesignPlaceStateSceneryUnavailable)
2056     {
2057         placeScenery = false;
2058         *flags |= TRACK_DESIGN_FLAG_SCENERY_UNAVAILABLE;
2059     }
2060 
2061     auto res = TrackDesignPlaceVirtual(
2062         tds, td6, PTD_OPERATION_PLACE_TRACK_PREVIEW, placeScenery, ride, { mapSize, mapSize, z });
2063     gParkFlags = backup_park_flags;
2064 
2065     if (res->Error == GameActions::Status::Ok)
2066     {
2067         if (entry_index == OBJECT_ENTRY_INDEX_NULL)
2068         {
2069             *flags |= TRACK_DESIGN_FLAG_VEHICLE_UNAVAILABLE;
2070         }
2071         else if (!ride_entry_is_invented(entry_index) && !gCheatsIgnoreResearchStatus)
2072         {
2073             *flags |= TRACK_DESIGN_FLAG_VEHICLE_UNAVAILABLE;
2074         }
2075 
2076         _currentTrackPieceDirection = backup_rotation;
2077         _trackDesignDrawingPreview = false;
2078         *cost = res->Cost;
2079         *outRide = ride;
2080         return true;
2081     }
2082 
2083     _currentTrackPieceDirection = backup_rotation;
2084     ride->Delete();
2085     _trackDesignDrawingPreview = false;
2086     return false;
2087 }
2088 
2089 #pragma region Track Design Preview
2090 
2091 /**
2092  *
2093  *  rct2: 0x006D1EF0
2094  */
TrackDesignDrawPreview(TrackDesign * td6,uint8_t * pixels)2095 void TrackDesignDrawPreview(TrackDesign* td6, uint8_t* pixels)
2096 {
2097     StashMap();
2098     TrackDesignPreviewClearMap();
2099 
2100     if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER)
2101     {
2102         TrackDesignLoadSceneryObjects(td6);
2103     }
2104 
2105     TrackDesignState tds{};
2106 
2107     money32 cost;
2108     Ride* ride;
2109     uint8_t flags;
2110     if (!TrackDesignPlacePreview(tds, td6, &cost, &ride, &flags))
2111     {
2112         std::fill_n(pixels, TRACK_PREVIEW_IMAGE_SIZE * 4, 0x00);
2113         UnstashMap();
2114         return;
2115     }
2116     td6->cost = cost;
2117     td6->track_flags = flags & 7;
2118 
2119     CoordsXYZ centre = { (tds.PreviewMin.x + tds.PreviewMax.x) / 2 + 16, (tds.PreviewMin.y + tds.PreviewMax.y) / 2 + 16,
2120                          (tds.PreviewMin.z + tds.PreviewMax.z) / 2 };
2121 
2122     int32_t size_x = tds.PreviewMax.x - tds.PreviewMin.x;
2123     int32_t size_y = tds.PreviewMax.y - tds.PreviewMin.y;
2124     int32_t size_z = tds.PreviewMax.z - tds.PreviewMin.z;
2125 
2126     // Special case for flat rides - Z-axis info is irrelevant
2127     // and must be zeroed out lest the preview be off-centre
2128     if (!GetRideTypeDescriptor(td6->type).HasFlag(RIDE_TYPE_FLAG_HAS_TRACK))
2129     {
2130         centre.z = 0;
2131         size_z = 0;
2132     }
2133 
2134     int32_t zoom_level = 1;
2135 
2136     if (size_x < size_y)
2137     {
2138         size_x = size_y;
2139     }
2140 
2141     if (size_x > 1000 || size_z > 280)
2142     {
2143         zoom_level = 2;
2144     }
2145 
2146     if (size_x > 1600 || size_z > 1000)
2147     {
2148         zoom_level = 3;
2149     }
2150 
2151     size_x = 370 << zoom_level;
2152     size_y = 217 << zoom_level;
2153 
2154     rct_viewport view;
2155     view.width = 370;
2156     view.height = 217;
2157     view.view_width = size_x;
2158     view.view_height = size_y;
2159     view.pos = { 0, 0 };
2160     view.zoom = zoom_level;
2161     view.flags = VIEWPORT_FLAG_HIDE_BASE | VIEWPORT_FLAG_INVISIBLE_SPRITES;
2162 
2163     rct_drawpixelinfo dpi;
2164     dpi.zoom_level = zoom_level;
2165     dpi.x = 0;
2166     dpi.y = 0;
2167     dpi.width = 370;
2168     dpi.height = 217;
2169     dpi.pitch = 0;
2170     dpi.bits = pixels;
2171 
2172     auto drawingEngine = std::make_unique<X8DrawingEngine>(GetContext()->GetUiContext());
2173     dpi.DrawingEngine = drawingEngine.get();
2174 
2175     const ScreenCoordsXY offset = { size_x / 2, size_y / 2 };
2176     for (uint8_t i = 0; i < 4; i++)
2177     {
2178         gCurrentRotation = i;
2179 
2180         view.viewPos = translate_3d_to_2d_with_z(i, centre) - offset;
2181         viewport_paint(&view, &dpi, { view.viewPos, view.viewPos + ScreenCoordsXY{ size_x, size_y } });
2182 
2183         dpi.bits += TRACK_PREVIEW_IMAGE_SIZE;
2184     }
2185 
2186     ride->Delete();
2187     UnstashMap();
2188 }
2189 
2190 /**
2191  * Resets all the map elements to surface tiles for track preview.
2192  *  rct2: 0x006D1D9A
2193  */
TrackDesignPreviewClearMap()2194 static void TrackDesignPreviewClearMap()
2195 {
2196     auto numTiles = MAXIMUM_MAP_SIZE_TECHNICAL * MAXIMUM_MAP_SIZE_TECHNICAL;
2197 
2198     gMapSize = 256;
2199 
2200     // Reserve ~8 elements per tile
2201     std::vector<TileElement> tileElements;
2202     tileElements.reserve(numTiles * 8);
2203 
2204     for (int32_t i = 0; i < numTiles; i++)
2205     {
2206         auto* element = &tileElements.emplace_back();
2207         element->ClearAs(TILE_ELEMENT_TYPE_SURFACE);
2208         element->SetLastForTile(true);
2209         element->AsSurface()->SetSlope(TILE_ELEMENT_SLOPE_FLAT);
2210         element->AsSurface()->SetWaterHeight(0);
2211         element->AsSurface()->SetSurfaceStyle(0);
2212         element->AsSurface()->SetEdgeStyle(0);
2213         element->AsSurface()->SetGrassLength(GRASS_LENGTH_CLEAR_0);
2214         element->AsSurface()->SetOwnership(OWNERSHIP_OWNED);
2215         element->AsSurface()->SetParkFences(0);
2216     }
2217     SetTileElements(std::move(tileElements));
2218 }
2219 
track_design_are_entrance_and_exit_placed()2220 bool track_design_are_entrance_and_exit_placed()
2221 {
2222     return _trackDesignPlaceStateEntranceExitPlaced;
2223 }
2224 
2225 #pragma endregion
2226