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