1 /*****************************************************************************
2  * Copyright (c) 2014-2021 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 "../Context.h"
11 #include "../Input.h"
12 #include "../actions/RideEntranceExitRemoveAction.h"
13 #include "../actions/RideSetSettingAction.h"
14 #include "../actions/RideSetStatusAction.h"
15 #include "../actions/RideSetVehicleAction.h"
16 #include "../actions/TrackRemoveAction.h"
17 #include "../common.h"
18 #include "../interface/Window.h"
19 #include "../localisation/Date.h"
20 #include "../localisation/Localisation.h"
21 #include "../network/network.h"
22 #include "../paint/VirtualFloor.h"
23 #include "../peep/Staff.h"
24 #include "../ui/UiContext.h"
25 #include "../ui/WindowManager.h"
26 #include "../util/Util.h"
27 #include "../windows/Intent.h"
28 #include "../world/Banner.h"
29 #include "../world/Climate.h"
30 #include "../world/Entity.h"
31 #include "../world/EntityList.h"
32 #include "../world/Entrance.h"
33 #include "../world/Footpath.h"
34 #include "../world/Location.hpp"
35 #include "../world/Map.h"
36 #include "../world/MapAnimation.h"
37 #include "../world/Park.h"
38 #include "../world/Scenery.h"
39 #include "../world/Sprite.h"
40 #include "Ride.h"
41 #include "RideData.h"
42 #include "Track.h"
43 #include "TrackData.h"
44 #include "TrainManager.h"
45 #include "Vehicle.h"
46 
47 using namespace OpenRCT2::TrackMetaData;
48 bool gGotoStartPlacementMode = false;
49 
50 money16 gTotalRideValueForMoney;
51 
52 money32 _currentTrackPrice;
53 
54 uint16_t _numCurrentPossibleRideConfigurations;
55 uint16_t _numCurrentPossibleSpecialTrackPieces;
56 
57 uint32_t _currentTrackCurve;
58 RideConstructionState _rideConstructionState;
59 ride_id_t _currentRideIndex;
60 
61 CoordsXYZ _currentTrackBegin;
62 
63 uint8_t _currentTrackPieceDirection;
64 track_type_t _currentTrackPieceType;
65 uint8_t _currentTrackSelectionFlags;
66 uint32_t _rideConstructionNextArrowPulse = 0;
67 uint8_t _currentTrackSlopeEnd;
68 uint8_t _currentTrackBankEnd;
69 uint8_t _currentTrackLiftHill;
70 uint8_t _currentTrackAlternative;
71 track_type_t _selectedTrackType;
72 
73 uint8_t _previousTrackBankEnd;
74 uint8_t _previousTrackSlopeEnd;
75 
76 CoordsXYZ _previousTrackPiece;
77 
78 uint8_t _currentBrakeSpeed2;
79 uint8_t _currentSeatRotationAngle;
80 
81 CoordsXYZD _unkF440C5;
82 
83 ObjectEntryIndex gLastEntranceStyle;
84 
85 uint8_t gRideEntranceExitPlaceType;
86 ride_id_t gRideEntranceExitPlaceRideIndex;
87 StationIndex gRideEntranceExitPlaceStationIndex;
88 RideConstructionState gRideEntranceExitPlacePreviousRideConstructionState;
89 Direction gRideEntranceExitPlaceDirection;
90 
91 using namespace OpenRCT2;
92 using namespace OpenRCT2::TrackMetaData;
93 
ride_check_if_construction_allowed(Ride * ride)94 static int32_t ride_check_if_construction_allowed(Ride* ride)
95 {
96     Formatter ft;
97     rct_ride_entry* rideEntry = ride->GetRideEntry();
98     if (rideEntry == nullptr)
99     {
100         context_show_error(STR_INVALID_RIDE_TYPE, STR_CANT_EDIT_INVALID_RIDE_TYPE, ft);
101         return 0;
102     }
103     if (ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN)
104     {
105         ft.Increment(6);
106         ride->FormatNameTo(ft);
107         context_show_error(STR_CANT_START_CONSTRUCTION_ON, STR_HAS_BROKEN_DOWN_AND_REQUIRES_FIXING, ft);
108         return 0;
109     }
110 
111     if (ride->status != RideStatus::Closed && ride->status != RideStatus::Simulating)
112     {
113         ft.Increment(6);
114         ride->FormatNameTo(ft);
115         context_show_error(STR_CANT_START_CONSTRUCTION_ON, STR_MUST_BE_CLOSED_FIRST, ft);
116         return 0;
117     }
118 
119     return 1;
120 }
121 
ride_create_or_find_construction_window(ride_id_t rideIndex)122 static rct_window* ride_create_or_find_construction_window(ride_id_t rideIndex)
123 {
124     auto windowManager = GetContext()->GetUiContext()->GetWindowManager();
125     auto intent = Intent(INTENT_ACTION_RIDE_CONSTRUCTION_FOCUS);
126     intent.putExtra(INTENT_EXTRA_RIDE_ID, EnumValue(rideIndex));
127     windowManager->BroadcastIntent(intent);
128     return window_find_by_class(WC_RIDE_CONSTRUCTION);
129 }
130 
131 /**
132  *
133  *  rct2: 0x006B4857
134  */
ride_construct(Ride * ride)135 void ride_construct(Ride* ride)
136 {
137     CoordsXYE trackElement;
138     if (ride_try_get_origin_element(ride, &trackElement))
139     {
140         ride_find_track_gap(ride, &trackElement, &trackElement);
141 
142         rct_window* w = window_get_main();
143         if (w != nullptr && ride_modify(&trackElement))
144             window_scroll_to_location(w, { trackElement, trackElement.element->GetBaseZ() });
145     }
146     else
147     {
148         ride_initialise_construction_window(ride);
149     }
150 }
151 
152 /**
153  *
154  *  rct2: 0x006DD4D5
155  */
ride_remove_cable_lift(Ride * ride)156 static void ride_remove_cable_lift(Ride* ride)
157 {
158     if (ride->lifecycle_flags & RIDE_LIFECYCLE_CABLE_LIFT)
159     {
160         ride->lifecycle_flags &= ~RIDE_LIFECYCLE_CABLE_LIFT;
161         uint16_t spriteIndex = ride->cable_lift;
162         do
163         {
164             Vehicle* vehicle = GetEntity<Vehicle>(spriteIndex);
165             if (vehicle == nullptr)
166             {
167                 return;
168             }
169             vehicle->Invalidate();
170             spriteIndex = vehicle->next_vehicle_on_train;
171             sprite_remove(vehicle);
172         } while (spriteIndex != SPRITE_INDEX_NULL);
173     }
174 }
175 
176 /**
177  *
178  *  rct2: 0x006DD506
179  */
RemoveVehicles()180 void Ride::RemoveVehicles()
181 {
182     if (lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK)
183     {
184         lifecycle_flags &= ~RIDE_LIFECYCLE_ON_TRACK;
185         lifecycle_flags &= ~(RIDE_LIFECYCLE_TEST_IN_PROGRESS | RIDE_LIFECYCLE_HAS_STALLED_VEHICLE);
186 
187         for (size_t i = 0; i <= MAX_VEHICLES_PER_RIDE; i++)
188         {
189             uint16_t spriteIndex = vehicles[i];
190             while (spriteIndex != SPRITE_INDEX_NULL)
191             {
192                 Vehicle* vehicle = GetEntity<Vehicle>(spriteIndex);
193                 if (vehicle == nullptr)
194                 {
195                     break;
196                 }
197                 vehicle->Invalidate();
198                 spriteIndex = vehicle->next_vehicle_on_train;
199                 sprite_remove(vehicle);
200             }
201 
202             vehicles[i] = SPRITE_INDEX_NULL;
203         }
204 
205         for (size_t i = 0; i < MAX_STATIONS; i++)
206             stations[i].TrainAtStation = RideStation::NO_TRAIN;
207 
208         // Also clean up orphaned vehicles for good measure.
209         for (auto* vehicle : TrainManager::View())
210         {
211             if (vehicle->ride == id)
212             {
213                 vehicle->Invalidate();
214                 sprite_remove(vehicle);
215             }
216         }
217     }
218 }
219 
220 /**
221  *
222  *  rct2: 0x006DD4AC
223  */
ride_clear_for_construction(Ride * ride)224 void ride_clear_for_construction(Ride* ride)
225 {
226     ride->measurement = {};
227 
228     ride->lifecycle_flags &= ~(RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN);
229     ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAIN | RIDE_INVALIDATE_RIDE_LIST;
230 
231     // Open circuit rides will go directly into building mode (creating ghosts) where it would normally clear the stats,
232     // however this causes desyncs since it's directly run from the window and other clients would not get it.
233     // To prevent these problems, unconditionally invalidate the test results on all clients in multiplayer games.
234     if (network_get_mode() != NETWORK_MODE_NONE)
235     {
236         invalidate_test_results(ride);
237     }
238 
239     ride_remove_cable_lift(ride);
240     ride->RemoveVehicles();
241     ride_clear_blocked_tiles(ride);
242 
243     auto w = window_find_by_number(WC_RIDE, EnumValue(ride->id));
244     if (w != nullptr)
245         window_event_resize_call(w);
246 }
247 
248 /**
249  *
250  *  rct2: 0x006664DF
251  */
ride_remove_peeps(Ride * ride)252 void ride_remove_peeps(Ride* ride)
253 {
254     // Find first station
255     auto stationIndex = ride_get_first_valid_station_start(ride);
256 
257     // Get exit position and direction
258     auto exitPosition = CoordsXYZD{ 0, 0, 0, INVALID_DIRECTION };
259     if (stationIndex != STATION_INDEX_NULL)
260     {
261         auto location = ride_get_exit_location(ride, stationIndex).ToCoordsXYZD();
262         if (!location.IsNull())
263         {
264             auto direction = direction_reverse(location.direction);
265             exitPosition = location;
266             exitPosition.x += (DirectionOffsets[direction].x * 20) + COORDS_XY_HALF_TILE;
267             exitPosition.y += (DirectionOffsets[direction].y * 20) + COORDS_XY_HALF_TILE;
268             exitPosition.z += 2;
269 
270             // Reverse direction
271             exitPosition.direction = direction_reverse(exitPosition.direction);
272 
273             exitPosition.direction *= 8;
274         }
275     }
276 
277     // Place all the guests at exit
278     for (auto peep : EntityList<Guest>())
279     {
280         if (peep->State == PeepState::QueuingFront || peep->State == PeepState::EnteringRide
281             || peep->State == PeepState::LeavingRide || peep->State == PeepState::OnRide)
282         {
283             if (peep->CurrentRide != ride->id)
284                 continue;
285 
286             peep_decrement_num_riders(peep);
287             if (peep->State == PeepState::QueuingFront && peep->RideSubState == PeepRideSubState::AtEntrance)
288                 peep->RemoveFromQueue();
289 
290             if (exitPosition.direction == INVALID_DIRECTION)
291             {
292                 CoordsXYZ newLoc = { peep->NextLoc.ToTileCentre(), peep->NextLoc.z };
293                 if (peep->GetNextIsSloped())
294                     newLoc.z += COORDS_Z_STEP;
295                 newLoc.z++;
296                 peep->MoveTo(newLoc);
297             }
298             else
299             {
300                 peep->MoveTo(exitPosition);
301                 peep->sprite_direction = exitPosition.direction;
302             }
303 
304             peep->State = PeepState::Falling;
305             peep->SwitchToSpecialSprite(0);
306 
307             peep->Happiness = std::min(peep->Happiness, peep->HappinessTarget) / 2;
308             peep->HappinessTarget = peep->Happiness;
309             peep->WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_STATS;
310         }
311     }
312     // Place all the staff at exit
313     for (auto peep : EntityList<Staff>())
314     {
315         if (peep->State == PeepState::Fixing || peep->State == PeepState::Inspecting)
316         {
317             if (peep->CurrentRide != ride->id)
318                 continue;
319 
320             if (exitPosition.direction == INVALID_DIRECTION)
321             {
322                 CoordsXYZ newLoc = { peep->NextLoc.ToTileCentre(), peep->NextLoc.z };
323                 if (peep->GetNextIsSloped())
324                     newLoc.z += COORDS_Z_STEP;
325                 newLoc.z++;
326                 peep->MoveTo(newLoc);
327             }
328             else
329             {
330                 peep->MoveTo(exitPosition);
331                 peep->sprite_direction = exitPosition.direction;
332             }
333 
334             peep->State = PeepState::Falling;
335             peep->SwitchToSpecialSprite(0);
336 
337             peep->WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_STATS;
338         }
339     }
340     ride->num_riders = 0;
341     ride->slide_in_use = 0;
342     ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAIN;
343 }
344 
ride_clear_blocked_tiles(Ride * ride)345 void ride_clear_blocked_tiles(Ride* ride)
346 {
347     for (int32_t y = 0; y < MAXIMUM_MAP_SIZE_TECHNICAL; y++)
348     {
349         for (int32_t x = 0; x < MAXIMUM_MAP_SIZE_TECHNICAL; x++)
350         {
351             auto element = map_get_first_element_at(TileCoordsXY{ x, y });
352             if (element != nullptr)
353             {
354                 do
355                 {
356                     if (element->GetType() == TILE_ELEMENT_TYPE_TRACK && element->AsTrack()->GetRideIndex() == ride->id)
357                     {
358                         // Unblock footpath element that is at same position
359                         auto footpathElement = map_get_footpath_element(
360                             TileCoordsXYZ{ x, y, element->base_height }.ToCoordsXYZ());
361                         if (footpathElement != nullptr)
362                         {
363                             footpathElement->AsPath()->SetIsBlockedByVehicle(false);
364                         }
365                     }
366                 } while (!(element++)->IsLastForTile());
367             }
368         }
369     }
370 }
371 
372 /**
373  * Gets the origin track element (sequence 0). Seems to do more than that though and even invalidates track.
374  *  rct2: 0x006C683D
375  * ax : x
376  * bx : direction << 8, type
377  * cx : y
378  * dx : z
379  * si : extra_params
380  * di : output_element
381  * bp : flags
382  */
GetTrackElementOriginAndApplyChanges(const CoordsXYZD & location,track_type_t type,uint16_t extra_params,TileElement ** output_element,uint16_t flags)383 std::optional<CoordsXYZ> GetTrackElementOriginAndApplyChanges(
384     const CoordsXYZD& location, track_type_t type, uint16_t extra_params, TileElement** output_element, uint16_t flags)
385 {
386     // Find the relevant track piece, prefer sequence 0 (this ensures correct behaviour for diagonal track pieces)
387     auto trackElement = map_get_track_element_at_of_type_seq(location, type, 0);
388     if (trackElement == nullptr)
389     {
390         trackElement = map_get_track_element_at_of_type(location, type);
391         if (trackElement == nullptr)
392         {
393             return std::nullopt;
394         }
395     }
396 
397     // Possibly z should be & 0xF8
398     const auto& ted = GetTrackElementDescriptor(type);
399     const auto* trackBlock = ted.Block;
400     if (trackBlock == nullptr)
401         return std::nullopt;
402 
403     // Now find all the elements that belong to this track piece
404     int32_t sequence = trackElement->GetSequenceIndex();
405     uint8_t mapDirection = trackElement->GetDirection();
406 
407     CoordsXY offsets = { trackBlock[sequence].x, trackBlock[sequence].y };
408     CoordsXY newCoords = location;
409     newCoords += offsets.Rotate(direction_reverse(mapDirection));
410 
411     auto retCoordsXYZ = CoordsXYZ{ newCoords.x, newCoords.y, location.z - trackBlock[sequence].z };
412 
413     int32_t start_z = retCoordsXYZ.z;
414     retCoordsXYZ.z += trackBlock[0].z;
415     for (int32_t i = 0; trackBlock[i].index != 0xFF; ++i)
416     {
417         CoordsXY cur = { retCoordsXYZ };
418         offsets = { trackBlock[i].x, trackBlock[i].y };
419         cur += offsets.Rotate(mapDirection);
420         int32_t cur_z = start_z + trackBlock[i].z;
421 
422         map_invalidate_tile_full(cur);
423 
424         trackElement = map_get_track_element_at_of_type_seq(
425             { cur, cur_z, static_cast<Direction>(location.direction) }, type, trackBlock[i].index);
426         if (trackElement == nullptr)
427         {
428             return std::nullopt;
429         }
430         if (i == 0 && output_element != nullptr)
431         {
432             *output_element = reinterpret_cast<TileElement*>(trackElement);
433         }
434         if (flags & TRACK_ELEMENT_SET_HIGHLIGHT_FALSE)
435         {
436             trackElement->SetHighlight(false);
437         }
438         if (flags & TRACK_ELEMENT_SET_HIGHLIGHT_TRUE)
439         {
440             trackElement->SetHighlight(true);
441         }
442         if (flags & TRACK_ELEMENT_SET_COLOUR_SCHEME)
443         {
444             trackElement->SetColourScheme(static_cast<uint8_t>(extra_params & 0xFF));
445         }
446         if (flags & TRACK_ELEMENT_SET_SEAT_ROTATION)
447         {
448             trackElement->SetSeatRotation(static_cast<uint8_t>(extra_params & 0xFF));
449         }
450         if (flags & TRACK_ELEMENT_SET_HAS_CABLE_LIFT_TRUE)
451         {
452             trackElement->SetHasCableLift(true);
453         }
454         if (flags & TRACK_ELEMENT_SET_HAS_CABLE_LIFT_FALSE)
455         {
456             trackElement->SetHasCableLift(false);
457         }
458     }
459     return retCoordsXYZ;
460 }
461 
ride_restore_provisional_track_piece()462 void ride_restore_provisional_track_piece()
463 {
464     if (_currentTrackSelectionFlags & TRACK_SELECTION_FLAG_TRACK)
465     {
466         ride_id_t rideIndex;
467         int32_t direction, type, liftHillAndAlternativeState;
468         CoordsXYZ trackPos;
469         if (window_ride_construction_update_state(
470                 &type, &direction, &rideIndex, &liftHillAndAlternativeState, &trackPos, nullptr))
471         {
472             ride_construction_remove_ghosts();
473         }
474         else
475         {
476             _currentTrackPrice = place_provisional_track_piece(
477                 rideIndex, type, direction, liftHillAndAlternativeState, trackPos);
478             window_ride_construction_update_active_elements();
479         }
480     }
481 }
482 
ride_remove_provisional_track_piece()483 void ride_remove_provisional_track_piece()
484 {
485     auto rideIndex = _currentRideIndex;
486     auto ride = get_ride(rideIndex);
487     if (ride == nullptr || !(_currentTrackSelectionFlags & TRACK_SELECTION_FLAG_TRACK))
488     {
489         return;
490     }
491 
492     int32_t x = _unkF440C5.x;
493     int32_t y = _unkF440C5.y;
494     int32_t z = _unkF440C5.z;
495     if (ride->type == RIDE_TYPE_MAZE)
496     {
497         int32_t flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND
498             | GAME_COMMAND_FLAG_GHOST;
499         maze_set_track(x, y, z, flags, false, 0, rideIndex, GC_SET_MAZE_TRACK_FILL);
500         maze_set_track(x, y + 16, z, flags, false, 1, rideIndex, GC_SET_MAZE_TRACK_FILL);
501         maze_set_track(x + 16, y + 16, z, flags, false, 2, rideIndex, GC_SET_MAZE_TRACK_FILL);
502         maze_set_track(x + 16, y, z, flags, false, 3, rideIndex, GC_SET_MAZE_TRACK_FILL);
503     }
504     else
505     {
506         int32_t direction = _unkF440C5.direction;
507         if (!(direction & 4))
508         {
509             x -= CoordsDirectionDelta[direction].x;
510             y -= CoordsDirectionDelta[direction].y;
511         }
512         CoordsXYE next_track;
513         if (track_block_get_next_from_zero({ x, y, z }, ride, direction, &next_track, &z, &direction, true))
514         {
515             auto trackType = next_track.element->AsTrack()->GetTrackType();
516             int32_t trackSequence = next_track.element->AsTrack()->GetSequenceIndex();
517             auto trackRemoveAction = TrackRemoveAction{ trackType,
518                                                         trackSequence,
519                                                         { next_track.x, next_track.y, z, static_cast<Direction>(direction) } };
520             trackRemoveAction.SetFlags(
521                 GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND | GAME_COMMAND_FLAG_GHOST);
522             GameActions::Execute(&trackRemoveAction);
523         }
524     }
525 }
526 
527 /**
528  *
529  *  rct2: 0x006C96C0
530  */
ride_construction_remove_ghosts()531 void ride_construction_remove_ghosts()
532 {
533     if (_currentTrackSelectionFlags & TRACK_SELECTION_FLAG_ENTRANCE_OR_EXIT)
534     {
535         ride_entrance_exit_remove_ghost();
536         _currentTrackSelectionFlags &= ~TRACK_SELECTION_FLAG_ENTRANCE_OR_EXIT;
537     }
538     if (_currentTrackSelectionFlags & TRACK_SELECTION_FLAG_TRACK)
539     {
540         ride_remove_provisional_track_piece();
541         _currentTrackSelectionFlags &= ~TRACK_SELECTION_FLAG_TRACK;
542     }
543 }
544 
545 /*
546  *  rct2: 0x006C9627
547  */
ride_construction_invalidate_current_track()548 void ride_construction_invalidate_current_track()
549 {
550     switch (_rideConstructionState)
551     {
552         case RideConstructionState::Selected:
553             GetTrackElementOriginAndApplyChanges(
554                 { _currentTrackBegin, static_cast<Direction>(_currentTrackPieceDirection & 3) }, _currentTrackPieceType, 0,
555                 nullptr, TRACK_ELEMENT_SET_HIGHLIGHT_FALSE);
556             break;
557         case RideConstructionState::MazeBuild:
558         case RideConstructionState::MazeMove:
559         case RideConstructionState::MazeFill:
560         case RideConstructionState::Front:
561         case RideConstructionState::Back:
562             if (_currentTrackSelectionFlags & TRACK_SELECTION_FLAG_ARROW)
563             {
564                 map_invalidate_tile_full(_currentTrackBegin.ToTileStart());
565             }
566             ride_construction_remove_ghosts();
567             break;
568         case RideConstructionState::Place:
569         case RideConstructionState::EntranceExit:
570         default:
571             if (_currentTrackSelectionFlags & TRACK_SELECTION_FLAG_ARROW)
572             {
573                 _currentTrackSelectionFlags &= ~TRACK_SELECTION_FLAG_ARROW;
574                 gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW;
575                 map_invalidate_tile_full(_currentTrackBegin);
576             }
577             ride_construction_remove_ghosts();
578             break;
579     }
580 }
581 
582 /**
583  *
584  *  rct2: 0x006C9B19
585  */
ride_construction_reset_current_piece()586 static void ride_construction_reset_current_piece()
587 {
588     auto ride = get_ride(_currentRideIndex);
589     if (ride == nullptr)
590         return;
591 
592     const auto& rtd = ride->GetRideTypeDescriptor();
593 
594     if (!rtd.HasFlag(RIDE_TYPE_FLAG_HAS_NO_TRACK) || ride->num_stations == 0)
595     {
596         _currentTrackCurve = rtd.StartTrackPiece | RideConstructionSpecialPieceSelected;
597         _currentTrackSlopeEnd = 0;
598         _currentTrackBankEnd = 0;
599         _currentTrackLiftHill = 0;
600         _currentTrackAlternative = RIDE_TYPE_NO_ALTERNATIVES;
601         if (rtd.HasFlag(RIDE_TYPE_FLAG_START_CONSTRUCTION_INVERTED))
602         {
603             _currentTrackAlternative |= RIDE_TYPE_ALTERNATIVE_TRACK_TYPE;
604         }
605         _previousTrackSlopeEnd = 0;
606         _previousTrackBankEnd = 0;
607     }
608     else
609     {
610         _currentTrackCurve = TrackElemType::None;
611         _rideConstructionState = RideConstructionState::State0;
612     }
613 }
614 
615 /**
616  *
617  *  rct2: 0x006C9800
618  */
ride_construction_set_default_next_piece()619 void ride_construction_set_default_next_piece()
620 {
621     auto rideIndex = _currentRideIndex;
622     auto ride = get_ride(rideIndex);
623     if (ride == nullptr)
624         return;
625 
626     const auto& rtd = ride->GetRideTypeDescriptor();
627 
628     int32_t z, direction, trackType, curve, bank, slope;
629     track_begin_end trackBeginEnd;
630     CoordsXYE xyElement;
631     TileElement* tileElement;
632     _currentTrackPrice = MONEY32_UNDEFINED;
633 
634     const TrackElementDescriptor* ted;
635     switch (_rideConstructionState)
636     {
637         case RideConstructionState::Front:
638             direction = _currentTrackPieceDirection;
639             if (!track_block_get_previous_from_zero(_currentTrackBegin, ride, direction, &trackBeginEnd))
640             {
641                 ride_construction_reset_current_piece();
642                 return;
643             }
644             tileElement = trackBeginEnd.begin_element;
645             trackType = tileElement->AsTrack()->GetTrackType();
646 
647             if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_NO_TRACK))
648             {
649                 ride_construction_reset_current_piece();
650                 return;
651             }
652 
653             // Set whether track is covered
654             _currentTrackAlternative &= ~RIDE_TYPE_ALTERNATIVE_TRACK_TYPE;
655             if (rtd.HasFlag(RIDE_TYPE_FLAG_HAS_ALTERNATIVE_TRACK_TYPE))
656             {
657                 if (tileElement->AsTrack()->IsInverted())
658                 {
659                     _currentTrackAlternative |= RIDE_TYPE_ALTERNATIVE_TRACK_TYPE;
660                 }
661             }
662 
663             ted = &GetTrackElementDescriptor(trackType);
664             curve = ted->CurveChain.next;
665             bank = ted->Definition.bank_end;
666             slope = ted->Definition.vangle_end;
667 
668             // Set track curve
669             _currentTrackCurve = curve;
670 
671             // Set track banking
672             if (rtd.HasFlag(RIDE_TYPE_FLAG_HAS_ALTERNATIVE_TRACK_TYPE))
673             {
674                 if (bank == TRACK_BANK_UPSIDE_DOWN)
675                 {
676                     bank = TRACK_BANK_NONE;
677                     _currentTrackAlternative ^= RIDE_TYPE_ALTERNATIVE_TRACK_TYPE;
678                 }
679             }
680             _currentTrackBankEnd = bank;
681             _previousTrackBankEnd = bank;
682 
683             // Set track slope and lift hill
684             _currentTrackSlopeEnd = slope;
685             _previousTrackSlopeEnd = slope;
686             _currentTrackLiftHill = tileElement->AsTrack()->HasChain() && slope != TRACK_SLOPE_DOWN_25
687                 && slope != TRACK_SLOPE_DOWN_60;
688             break;
689         case RideConstructionState::Back:
690             direction = direction_reverse(_currentTrackPieceDirection);
691             if (!track_block_get_next_from_zero(_currentTrackBegin, ride, direction, &xyElement, &z, &direction, false))
692             {
693                 ride_construction_reset_current_piece();
694                 return;
695             }
696             tileElement = xyElement.element;
697             trackType = tileElement->AsTrack()->GetTrackType();
698 
699             // Set whether track is covered
700             _currentTrackAlternative &= ~RIDE_TYPE_ALTERNATIVE_TRACK_TYPE;
701             if (rtd.HasFlag(RIDE_TYPE_FLAG_HAS_ALTERNATIVE_TRACK_TYPE))
702             {
703                 if (tileElement->AsTrack()->IsInverted())
704                 {
705                     _currentTrackAlternative |= RIDE_TYPE_ALTERNATIVE_TRACK_TYPE;
706                 }
707             }
708 
709             ted = &GetTrackElementDescriptor(trackType);
710             curve = ted->CurveChain.previous;
711             bank = ted->Definition.bank_start;
712             slope = ted->Definition.vangle_start;
713 
714             // Set track curve
715             _currentTrackCurve = curve;
716 
717             // Set track banking
718             if (rtd.HasFlag(RIDE_TYPE_FLAG_HAS_ALTERNATIVE_TRACK_TYPE))
719             {
720                 if (bank == TRACK_BANK_UPSIDE_DOWN)
721                 {
722                     bank = TRACK_BANK_NONE;
723                     _currentTrackAlternative ^= RIDE_TYPE_ALTERNATIVE_TRACK_TYPE;
724                 }
725             }
726             _currentTrackBankEnd = bank;
727             _previousTrackBankEnd = bank;
728 
729             // Set track slope and lift hill
730             _currentTrackSlopeEnd = slope;
731             _previousTrackSlopeEnd = slope;
732             if (!gCheatsEnableChainLiftOnAllTrack)
733             {
734                 _currentTrackLiftHill = tileElement->AsTrack()->HasChain();
735             }
736             break;
737         default:
738             break;
739     }
740 }
741 
742 /**
743  *
744  *  rct2: 0x006C9296
745  */
ride_select_next_section()746 void ride_select_next_section()
747 {
748     if (_rideConstructionState == RideConstructionState::Selected)
749     {
750         ride_construction_invalidate_current_track();
751         int32_t direction = _currentTrackPieceDirection;
752         int32_t type = _currentTrackPieceType;
753         TileElement* tileElement;
754         auto newCoords = GetTrackElementOriginAndApplyChanges(
755             { _currentTrackBegin, static_cast<Direction>(direction & 3) }, type, 0, &tileElement, 0);
756         if (!newCoords.has_value())
757         {
758             _rideConstructionState = RideConstructionState::State0;
759             window_ride_construction_update_active_elements();
760             return;
761         }
762 
763         // Invalidate previous track piece (we may not be changing height!)
764         virtual_floor_invalidate();
765 
766         CoordsXYE inputElement, outputElement;
767         inputElement.x = newCoords->x;
768         inputElement.y = newCoords->y;
769         inputElement.element = tileElement;
770         if (track_block_get_next(&inputElement, &outputElement, &newCoords->z, &direction))
771         {
772             newCoords->x = outputElement.x;
773             newCoords->y = outputElement.y;
774             tileElement = outputElement.element;
775             if (!scenery_tool_is_active())
776             {
777                 // Set next element's height.
778                 virtual_floor_set_height(tileElement->GetBaseZ());
779             }
780 
781             _currentTrackBegin = *newCoords;
782             _currentTrackPieceDirection = tileElement->GetDirection();
783             _currentTrackPieceType = tileElement->AsTrack()->GetTrackType();
784             _currentTrackSelectionFlags = 0;
785             window_ride_construction_update_active_elements();
786         }
787         else
788         {
789             _rideConstructionState = RideConstructionState::Front;
790             _currentTrackBegin = { outputElement, newCoords->z };
791             _currentTrackPieceDirection = direction;
792             _currentTrackPieceType = tileElement->AsTrack()->GetTrackType();
793             _currentTrackSelectionFlags = 0;
794             ride_construction_set_default_next_piece();
795             window_ride_construction_update_active_elements();
796         }
797     }
798     else if (_rideConstructionState == RideConstructionState::Back)
799     {
800         gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW;
801 
802         if (ride_select_forwards_from_back())
803         {
804             window_ride_construction_update_active_elements();
805         }
806     }
807 }
808 
809 /**
810  *
811  *  rct2: 0x006C93B8
812  */
ride_select_previous_section()813 void ride_select_previous_section()
814 {
815     if (_rideConstructionState == RideConstructionState::Selected)
816     {
817         ride_construction_invalidate_current_track();
818         int32_t direction = _currentTrackPieceDirection;
819         int32_t type = _currentTrackPieceType;
820         TileElement* tileElement;
821         auto newCoords = GetTrackElementOriginAndApplyChanges(
822             { _currentTrackBegin, static_cast<Direction>(direction & 3) }, type, 0, &tileElement, 0);
823         if (newCoords == std::nullopt)
824         {
825             _rideConstructionState = RideConstructionState::State0;
826             window_ride_construction_update_active_elements();
827             return;
828         }
829 
830         // Invalidate previous track piece (we may not be changing height!)
831         virtual_floor_invalidate();
832 
833         track_begin_end trackBeginEnd;
834         if (track_block_get_previous({ *newCoords, tileElement }, &trackBeginEnd))
835         {
836             _currentTrackBegin.x = trackBeginEnd.begin_x;
837             _currentTrackBegin.y = trackBeginEnd.begin_y;
838             _currentTrackBegin.z = trackBeginEnd.begin_z;
839             _currentTrackPieceDirection = trackBeginEnd.begin_direction;
840             _currentTrackPieceType = trackBeginEnd.begin_element->AsTrack()->GetTrackType();
841             _currentTrackSelectionFlags = 0;
842             if (!scenery_tool_is_active())
843             {
844                 // Set previous element's height.
845                 virtual_floor_set_height(trackBeginEnd.begin_element->GetBaseZ());
846             }
847             window_ride_construction_update_active_elements();
848         }
849         else
850         {
851             _rideConstructionState = RideConstructionState::Back;
852             _currentTrackBegin.x = trackBeginEnd.end_x;
853             _currentTrackBegin.y = trackBeginEnd.end_y;
854             _currentTrackBegin.z = trackBeginEnd.begin_z;
855             _currentTrackPieceDirection = trackBeginEnd.end_direction;
856             _currentTrackPieceType = tileElement->AsTrack()->GetTrackType();
857             _currentTrackSelectionFlags = 0;
858             ride_construction_set_default_next_piece();
859             window_ride_construction_update_active_elements();
860         }
861     }
862     else if (_rideConstructionState == RideConstructionState::Front)
863     {
864         gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW;
865 
866         if (ride_select_backwards_from_front())
867         {
868             window_ride_construction_update_active_elements();
869         }
870     }
871 }
872 
873 /**
874  *
875  *  rct2: 0x006CC2CA
876  */
ride_modify_entrance_or_exit(const CoordsXYE & tileElement)877 static bool ride_modify_entrance_or_exit(const CoordsXYE& tileElement)
878 {
879     if (tileElement.element == nullptr)
880         return false;
881 
882     auto entranceElement = tileElement.element->AsEntrance();
883     if (entranceElement == nullptr)
884         return false;
885 
886     auto rideIndex = entranceElement->GetRideIndex();
887     auto ride = get_ride(rideIndex);
888     if (ride == nullptr)
889         return false;
890 
891     auto entranceType = entranceElement->GetEntranceType();
892     if (entranceType != ENTRANCE_TYPE_RIDE_ENTRANCE && entranceType != ENTRANCE_TYPE_RIDE_EXIT)
893         return false;
894 
895     auto stationIndex = entranceElement->GetStationIndex();
896 
897     // Get or create construction window for ride
898     auto constructionWindow = window_find_by_class(WC_RIDE_CONSTRUCTION);
899     if (constructionWindow == nullptr)
900     {
901         if (!ride_initialise_construction_window(ride))
902             return false;
903 
904         constructionWindow = window_find_by_class(WC_RIDE_CONSTRUCTION);
905         if (constructionWindow == nullptr)
906             return false;
907     }
908 
909     ride_construction_invalidate_current_track();
910     if (_rideConstructionState != RideConstructionState::EntranceExit || !(input_test_flag(INPUT_FLAG_TOOL_ACTIVE))
911         || gCurrentToolWidget.window_classification != WC_RIDE_CONSTRUCTION)
912     {
913         // Replace entrance / exit
914         tool_set(
915             constructionWindow,
916             entranceType == ENTRANCE_TYPE_RIDE_ENTRANCE ? WC_RIDE_CONSTRUCTION__WIDX_ENTRANCE : WC_RIDE_CONSTRUCTION__WIDX_EXIT,
917             Tool::Crosshair);
918         gRideEntranceExitPlaceType = entranceType;
919         gRideEntranceExitPlaceRideIndex = rideIndex;
920         gRideEntranceExitPlaceStationIndex = stationIndex;
921         input_set_flag(INPUT_FLAG_6, true);
922         if (_rideConstructionState != RideConstructionState::EntranceExit)
923         {
924             gRideEntranceExitPlacePreviousRideConstructionState = _rideConstructionState;
925             _rideConstructionState = RideConstructionState::EntranceExit;
926         }
927 
928         window_ride_construction_update_active_elements();
929         gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_CONSTRUCT;
930     }
931     else
932     {
933         // Remove entrance / exit
934         auto rideEntranceExitRemove = RideEntranceExitRemoveAction(
935             { tileElement.x, tileElement.y }, rideIndex, stationIndex, entranceType == ENTRANCE_TYPE_RIDE_EXIT);
936 
937         rideEntranceExitRemove.SetCallback([=](const GameAction* ga, const GameActions::Result* result) {
938             gCurrentToolWidget.widget_index = entranceType == ENTRANCE_TYPE_RIDE_ENTRANCE ? WC_RIDE_CONSTRUCTION__WIDX_ENTRANCE
939                                                                                           : WC_RIDE_CONSTRUCTION__WIDX_EXIT;
940             gRideEntranceExitPlaceType = entranceType;
941             window_invalidate_by_class(WC_RIDE_CONSTRUCTION);
942         });
943 
944         GameActions::Execute(&rideEntranceExitRemove);
945     }
946 
947     window_invalidate_by_class(WC_RIDE_CONSTRUCTION);
948     return true;
949 }
950 
951 /**
952  *
953  *  rct2: 0x006CC287
954  */
ride_modify_maze(const CoordsXYE & tileElement)955 static bool ride_modify_maze(const CoordsXYE& tileElement)
956 {
957     if (tileElement.element != nullptr)
958     {
959         auto trackElement = tileElement.element->AsTrack();
960         if (trackElement != nullptr)
961         {
962             _currentRideIndex = trackElement->GetRideIndex();
963             _rideConstructionState = RideConstructionState::MazeBuild;
964             _currentTrackBegin.x = tileElement.x;
965             _currentTrackBegin.y = tileElement.y;
966             _currentTrackBegin.z = trackElement->GetBaseZ();
967             _currentTrackSelectionFlags = 0;
968             _rideConstructionNextArrowPulse = 0;
969             gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW;
970 
971             auto intent = Intent(INTENT_ACTION_UPDATE_MAZE_CONSTRUCTION);
972             context_broadcast_intent(&intent);
973             return true;
974         }
975     }
976     return false;
977 }
978 
979 /**
980  *
981  *  rct2: 0x006CC056
982  */
ride_modify(CoordsXYE * input)983 bool ride_modify(CoordsXYE* input)
984 {
985     auto tileElement = *input;
986     if (tileElement.element == nullptr)
987         return false;
988 
989     auto rideIndex = tileElement.element->GetRideIndex();
990     auto ride = get_ride(rideIndex);
991     if (ride == nullptr)
992     {
993         return false;
994     }
995 
996     auto rideEntry = ride->GetRideEntry();
997     if (rideEntry == nullptr || !ride_check_if_construction_allowed(ride))
998         return false;
999 
1000     if (ride->lifecycle_flags & RIDE_LIFECYCLE_INDESTRUCTIBLE)
1001     {
1002         Formatter ft;
1003         ft.Increment(6);
1004         ride->FormatNameTo(ft);
1005         context_show_error(
1006             STR_CANT_START_CONSTRUCTION_ON, STR_LOCAL_AUTHORITY_FORBIDS_DEMOLITION_OR_MODIFICATIONS_TO_THIS_RIDE, ft);
1007         return false;
1008     }
1009 
1010     // Stop the ride again to clear all vehicles and peeps (compatible with network games)
1011     if (ride->status != RideStatus::Simulating)
1012     {
1013         ride_set_status(ride, RideStatus::Closed);
1014     }
1015 
1016     // Check if element is a station entrance or exit
1017     if (tileElement.element->GetType() == TILE_ELEMENT_TYPE_ENTRANCE)
1018         return ride_modify_entrance_or_exit(tileElement);
1019 
1020     ride_create_or_find_construction_window(rideIndex);
1021 
1022     if (ride->type == RIDE_TYPE_MAZE)
1023     {
1024         return ride_modify_maze(tileElement);
1025     }
1026 
1027     if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_CANNOT_HAVE_GAPS))
1028     {
1029         CoordsXYE endOfTrackElement{};
1030         if (ride_find_track_gap(ride, &tileElement, &endOfTrackElement))
1031             tileElement = endOfTrackElement;
1032     }
1033 
1034     if (tileElement.element == nullptr || tileElement.element->GetType() != TILE_ELEMENT_TYPE_TRACK)
1035         return false;
1036 
1037     auto tileCoords = CoordsXYZ{ tileElement, tileElement.element->GetBaseZ() };
1038     auto direction = tileElement.element->GetDirection();
1039     auto type = tileElement.element->AsTrack()->GetTrackType();
1040     auto newCoords = GetTrackElementOriginAndApplyChanges({ tileCoords, direction }, type, 0, nullptr, 0);
1041     if (!newCoords.has_value())
1042         return false;
1043 
1044     _currentRideIndex = rideIndex;
1045     _rideConstructionState = RideConstructionState::Selected;
1046     _currentTrackBegin = newCoords.value();
1047     _currentTrackPieceDirection = direction;
1048     _currentTrackPieceType = type;
1049     _currentTrackSelectionFlags = 0;
1050     _rideConstructionNextArrowPulse = 0;
1051     gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW;
1052 
1053     if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_NO_TRACK))
1054     {
1055         window_ride_construction_update_active_elements();
1056         return true;
1057     }
1058 
1059     ride_select_next_section();
1060     if (_rideConstructionState == RideConstructionState::Front)
1061     {
1062         window_ride_construction_update_active_elements();
1063         return true;
1064     }
1065 
1066     _rideConstructionState = RideConstructionState::Selected;
1067     _currentTrackBegin = *newCoords;
1068     _currentTrackPieceDirection = direction;
1069     _currentTrackPieceType = type;
1070     _currentTrackSelectionFlags = 0;
1071 
1072     ride_select_previous_section();
1073 
1074     if (_rideConstructionState != RideConstructionState::Back)
1075     {
1076         _rideConstructionState = RideConstructionState::Selected;
1077         _currentTrackBegin = *newCoords;
1078         _currentTrackPieceDirection = direction;
1079         _currentTrackPieceType = type;
1080         _currentTrackSelectionFlags = 0;
1081     }
1082 
1083     window_ride_construction_update_active_elements();
1084     return true;
1085 }
1086 
1087 /**
1088  *
1089  *  rct2: 0x006CC3FB
1090  */
ride_initialise_construction_window(Ride * ride)1091 int32_t ride_initialise_construction_window(Ride* ride)
1092 {
1093     rct_window* w;
1094 
1095     tool_cancel();
1096 
1097     if (!ride_check_if_construction_allowed(ride))
1098         return 0;
1099 
1100     ride_clear_for_construction(ride);
1101     ride_remove_peeps(ride);
1102 
1103     w = ride_create_or_find_construction_window(ride->id);
1104 
1105     tool_set(w, WC_RIDE_CONSTRUCTION__WIDX_CONSTRUCT, Tool::Crosshair);
1106     input_set_flag(INPUT_FLAG_6, true);
1107 
1108     ride = get_ride(_currentRideIndex);
1109     if (ride == nullptr)
1110         return 0;
1111 
1112     _currentTrackCurve = ride->GetRideTypeDescriptor().StartTrackPiece | RideConstructionSpecialPieceSelected;
1113     _currentTrackSlopeEnd = 0;
1114     _currentTrackBankEnd = 0;
1115     _currentTrackLiftHill = 0;
1116     _currentTrackAlternative = RIDE_TYPE_NO_ALTERNATIVES;
1117 
1118     if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_START_CONSTRUCTION_INVERTED))
1119         _currentTrackAlternative |= RIDE_TYPE_ALTERNATIVE_TRACK_TYPE;
1120 
1121     _previousTrackBankEnd = 0;
1122     _previousTrackSlopeEnd = 0;
1123 
1124     _currentTrackPieceDirection = 0;
1125     _rideConstructionState = RideConstructionState::Place;
1126     _currentTrackSelectionFlags = 0;
1127 
1128     window_ride_construction_update_active_elements();
1129     return 1;
1130 }
1131 
1132 /**
1133  *
1134  *  rct2: 0x006CB7FB
1135  */
ride_get_refund_price(const Ride * ride)1136 int32_t ride_get_refund_price(const Ride* ride)
1137 {
1138     CoordsXYE trackElement;
1139     money32 cost = 0;
1140 
1141     if (!ride_try_get_origin_element(ride, &trackElement))
1142     {
1143         return 0; // Ride has no track to refund
1144     }
1145 
1146     // Find the start in case it is not a complete circuit
1147     ride_get_start_of_track(&trackElement);
1148 
1149     uint8_t direction = trackElement.element->GetDirection();
1150 
1151     // Used in the following loop to know when we have
1152     // completed all of the elements and are back at the
1153     // start.
1154     TileElement* initial_map = trackElement.element;
1155     CoordsXYE slowIt = trackElement;
1156     bool moveSlowIt = true;
1157 
1158     do
1159     {
1160         auto trackRemoveAction = TrackRemoveAction(
1161             trackElement.element->AsTrack()->GetTrackType(), trackElement.element->AsTrack()->GetSequenceIndex(),
1162             { trackElement.x, trackElement.y, trackElement.element->GetBaseZ(), direction });
1163         trackRemoveAction.SetFlags(GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED);
1164 
1165         auto res = GameActions::Query(&trackRemoveAction);
1166 
1167         cost += res->Cost;
1168 
1169         if (!track_block_get_next(&trackElement, &trackElement, nullptr, nullptr))
1170         {
1171             break;
1172         }
1173 
1174         moveSlowIt = !moveSlowIt;
1175         if (moveSlowIt)
1176         {
1177             if (!track_block_get_next(&slowIt, &slowIt, nullptr, nullptr) || slowIt.element == trackElement.element)
1178             {
1179                 break;
1180             }
1181         }
1182 
1183         direction = trackElement.element->GetDirection();
1184 
1185     } while (trackElement.element != initial_map);
1186 
1187     return cost;
1188 }
1189 
set_operating_setting(ride_id_t rideId,RideSetSetting setting,uint8_t value)1190 money32 set_operating_setting(ride_id_t rideId, RideSetSetting setting, uint8_t value)
1191 {
1192     auto rideSetSetting = RideSetSettingAction(rideId, setting, value);
1193     auto res = GameActions::Execute(&rideSetSetting);
1194     return res->Error == GameActions::Status::Ok ? 0 : MONEY32_UNDEFINED;
1195 }
1196 
set_operating_setting_nested(ride_id_t rideId,RideSetSetting setting,uint8_t value,uint8_t flags)1197 money32 set_operating_setting_nested(ride_id_t rideId, RideSetSetting setting, uint8_t value, uint8_t flags)
1198 {
1199     auto rideSetSetting = RideSetSettingAction(rideId, setting, value);
1200     rideSetSetting.SetFlags(flags);
1201     auto res = flags & GAME_COMMAND_FLAG_APPLY ? GameActions::ExecuteNested(&rideSetSetting)
1202                                                : GameActions::QueryNested(&rideSetSetting);
1203     return res->Error == GameActions::Status::Ok ? 0 : MONEY32_UNDEFINED;
1204 }
1205 
1206 /**
1207  *
1208  *  rct2: 0x006CCF70
1209  */
ride_get_entrance_or_exit_position_from_screen_position(const ScreenCoordsXY & screenCoords)1210 CoordsXYZD ride_get_entrance_or_exit_position_from_screen_position(const ScreenCoordsXY& screenCoords)
1211 {
1212     CoordsXYZD entranceExitCoords{};
1213     gRideEntranceExitPlaceDirection = INVALID_DIRECTION;
1214     // determine if the mouse is hovering over a station - that's the station to add the entrance to
1215     auto info = get_map_coordinates_from_pos(screenCoords, EnumsToFlags(ViewportInteractionItem::Ride));
1216     if (info.SpriteType != ViewportInteractionItem::None)
1217     {
1218         if (info.Element->GetType() == TILE_ELEMENT_TYPE_TRACK)
1219         {
1220             const auto* trackElement = info.Element->AsTrack();
1221             if (trackElement->GetRideIndex() == gRideEntranceExitPlaceRideIndex)
1222             {
1223                 const auto& ted = GetTrackElementDescriptor(trackElement->GetTrackType());
1224                 if (ted.SequenceProperties[0] & TRACK_SEQUENCE_FLAG_ORIGIN)
1225                 {
1226                     if (trackElement->GetTrackType() == TrackElemType::Maze)
1227                     {
1228                         gRideEntranceExitPlaceStationIndex = 0;
1229                     }
1230                     else
1231                     {
1232                         gRideEntranceExitPlaceStationIndex = trackElement->GetStationIndex();
1233                     }
1234                 }
1235             }
1236         }
1237     }
1238 
1239     auto ride = get_ride(gRideEntranceExitPlaceRideIndex);
1240     if (ride == nullptr)
1241     {
1242         entranceExitCoords.SetNull();
1243         return entranceExitCoords;
1244     }
1245 
1246     auto stationBaseZ = ride->stations[gRideEntranceExitPlaceStationIndex].GetBaseZ();
1247 
1248     auto coordsAtHeight = screen_get_map_xy_with_z(screenCoords, stationBaseZ);
1249     if (!coordsAtHeight.has_value())
1250     {
1251         entranceExitCoords.SetNull();
1252         return entranceExitCoords;
1253     }
1254 
1255     entranceExitCoords = { coordsAtHeight->ToTileStart(), stationBaseZ, INVALID_DIRECTION };
1256 
1257     if (ride->type == RIDE_TYPE_NULL)
1258     {
1259         entranceExitCoords.SetNull();
1260         return entranceExitCoords;
1261     }
1262 
1263     auto stationStart = ride->stations[gRideEntranceExitPlaceStationIndex].Start;
1264     if (stationStart.IsNull())
1265     {
1266         entranceExitCoords.SetNull();
1267         return entranceExitCoords;
1268     }
1269 
1270     // find the quadrant the mouse is hovering over - that's the direction to start searching for a station TileElement
1271     Direction startDirection = 0;
1272     auto mapX = (coordsAtHeight->x & 0x1F) - 16;
1273     auto mapY = (coordsAtHeight->y & 0x1F) - 16;
1274     if (std::abs(mapX) < std::abs(mapY))
1275     {
1276         startDirection = mapY < 0 ? 3 : 1;
1277     }
1278     else
1279     {
1280         startDirection = mapX < 0 ? 0 : 2;
1281     }
1282     // check all 4 directions, starting with the mouse's quadrant
1283     for (uint8_t directionIncrement = 0; directionIncrement < 4; directionIncrement++)
1284     {
1285         entranceExitCoords.direction = (startDirection + directionIncrement) & 3;
1286         // search for TrackElement one tile over, shifted in the search direction
1287         auto nextLocation = entranceExitCoords;
1288         nextLocation += CoordsDirectionDelta[entranceExitCoords.direction];
1289         if (map_is_location_valid(nextLocation))
1290         {
1291             // iterate over every element in the tile until we find what we want
1292             auto* tileElement = map_get_first_element_at(nextLocation);
1293             if (tileElement == nullptr)
1294                 continue;
1295             do
1296             {
1297                 if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK)
1298                     continue;
1299                 if (tileElement->GetBaseZ() != stationBaseZ)
1300                     continue;
1301                 auto* trackElement = tileElement->AsTrack();
1302                 if (trackElement->GetRideIndex() != gRideEntranceExitPlaceRideIndex)
1303                     continue;
1304                 if (trackElement->GetTrackType() == TrackElemType::Maze)
1305                 {
1306                     // if it's a maze, it can place the entrance and exit immediately
1307                     entranceExitCoords.direction = direction_reverse(entranceExitCoords.direction);
1308                     gRideEntranceExitPlaceDirection = entranceExitCoords.direction;
1309                     return entranceExitCoords;
1310                 }
1311                 // if it's not a maze, the sequence properties for the TrackElement must be found to determine if an
1312                 // entrance can be placed on that side
1313 
1314                 gRideEntranceExitPlaceStationIndex = trackElement->GetStationIndex();
1315 
1316                 // get the ride entrance's side relative to the TrackElement
1317                 Direction direction = (direction_reverse(entranceExitCoords.direction) - tileElement->GetDirection()) & 3;
1318                 const auto& ted = GetTrackElementDescriptor(trackElement->GetTrackType());
1319                 if (ted.SequenceProperties[trackElement->GetSequenceIndex()] & (1 << direction))
1320                 {
1321                     // if that side of the TrackElement supports stations, the ride entrance is valid and faces away from
1322                     // the station
1323                     entranceExitCoords.direction = direction_reverse(entranceExitCoords.direction);
1324                     gRideEntranceExitPlaceDirection = entranceExitCoords.direction;
1325                     return entranceExitCoords;
1326                 }
1327             } while (!(tileElement++)->IsLastForTile());
1328         }
1329     }
1330     gRideEntranceExitPlaceDirection = INVALID_DIRECTION;
1331     return entranceExitCoords;
1332 }
1333 
1334 /**
1335  *
1336  *  rct2: 0x006CB945
1337  */
ValidateStations()1338 void Ride::ValidateStations()
1339 {
1340     const TrackElementDescriptor* ted;
1341     if (type != RIDE_TYPE_MAZE)
1342     {
1343         // find the stations of the ride to begin stepping over track elements from
1344         for (StationIndex stationId = 0; stationId < MAX_STATIONS; ++stationId)
1345         {
1346             if (stations[stationId].Start.IsNull())
1347                 continue;
1348 
1349             CoordsXYZ location = stations[stationId].GetStart();
1350             uint8_t direction = INVALID_DIRECTION;
1351 
1352             bool specialTrack = false;
1353             TileElement* tileElement = nullptr;
1354             while (true)
1355             {
1356                 // search backwards for the previous station TrackElement (only if the first station TrackElement is found)
1357                 if (direction != INVALID_DIRECTION)
1358                 {
1359                     location.x -= CoordsDirectionDelta[direction].x;
1360                     location.y -= CoordsDirectionDelta[direction].y;
1361                 }
1362                 tileElement = map_get_first_element_at(location);
1363                 if (tileElement == nullptr)
1364                     break;
1365 
1366                 // find the target TrackElement on the tile it's supposed to appear on
1367                 bool trackFound = false;
1368                 do
1369                 {
1370                     if (tileElement->GetBaseZ() != location.z)
1371                         continue;
1372                     if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK)
1373                         continue;
1374                     if (tileElement->AsTrack()->GetRideIndex() != id)
1375                         continue;
1376                     if (tileElement->AsTrack()->GetSequenceIndex() != 0)
1377                         continue;
1378 
1379                     ted = &GetTrackElementDescriptor(tileElement->AsTrack()->GetTrackType());
1380                     // keep searching for a station piece (coaster station, tower ride base, shops, and flat ride base)
1381                     if (!(ted->SequenceProperties[0] & TRACK_SEQUENCE_FLAG_ORIGIN))
1382                         continue;
1383 
1384                     trackFound = true;
1385                     break;
1386                 } while (!(tileElement++)->IsLastForTile());
1387 
1388                 if (!trackFound)
1389                 {
1390                     break;
1391                 }
1392                 // update the StationIndex, get the TrackElement's rotation
1393                 tileElement->AsTrack()->SetStationIndex(stationId);
1394                 direction = tileElement->GetDirection();
1395 
1396                 // In the future this could look at the TED and see if the station has a sequence longer than 1
1397                 // tower ride, flat ride, shop
1398                 if (GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_SINGLE_PIECE_STATION))
1399                 {
1400                     // if the track has multiple sequences, stop looking for the next one.
1401                     specialTrack = true;
1402                     break;
1403                 }
1404             }
1405 
1406             // if the track piece is not a tower ride, flat ride, or shop, continue to the next StationIndex
1407             if (!specialTrack)
1408             {
1409                 continue;
1410             }
1411             // update all the blocks with StationIndex
1412             ted = &GetTrackElementDescriptor(tileElement->AsTrack()->GetTrackType());
1413             const rct_preview_track* trackBlock = ted->Block;
1414             while ((++trackBlock)->index != 0xFF)
1415             {
1416                 CoordsXYZ blockLocation = location + CoordsXYZ{ CoordsXY{ trackBlock->x, trackBlock->y }.Rotate(direction), 0 };
1417 
1418                 bool trackFound = false;
1419                 tileElement = map_get_first_element_at(blockLocation);
1420                 if (tileElement == nullptr)
1421                     break;
1422                 // find the target TrackElement on the tile it's supposed to appear on
1423                 do
1424                 {
1425                     if (blockLocation.z != tileElement->GetBaseZ())
1426                         continue;
1427                     if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK)
1428                         continue;
1429 
1430                     ted = &GetTrackElementDescriptor(tileElement->AsTrack()->GetTrackType());
1431                     if (!(ted->SequenceProperties[0] & TRACK_SEQUENCE_FLAG_ORIGIN))
1432                         continue;
1433 
1434                     trackFound = true;
1435                     break;
1436                 } while (!(tileElement++)->IsLastForTile());
1437 
1438                 if (!trackFound)
1439                 {
1440                     // Critical error! Stop trying to find the next sequence to set StationIndex.
1441                     break;
1442                 }
1443 
1444                 tileElement->AsTrack()->SetStationIndex(stationId);
1445             }
1446         }
1447     }
1448     // determine what entrances and exits exist
1449     FixedVector<TileCoordsXYZD, MAX_STATION_LOCATIONS> locations;
1450     for (StationIndex stationId = 0; stationId < MAX_STATIONS; ++stationId)
1451     {
1452         auto entrance = ride_get_entrance_location(this, stationId);
1453         if (!entrance.IsNull())
1454         {
1455             locations.push_back(entrance);
1456             ride_clear_entrance_location(this, stationId);
1457         }
1458 
1459         auto exit = ride_get_exit_location(this, stationId);
1460         if (!exit.IsNull())
1461         {
1462             locations.push_back(exit);
1463             ride_clear_exit_location(this, stationId);
1464         }
1465     }
1466 
1467     auto locationListIter = locations.cbegin();
1468     for (const TileCoordsXYZD& locationCoords : locations)
1469     {
1470         auto locationList = ++locationListIter;
1471         // determine if there's another ride entrance at this location later in the array
1472         // if there is, skip it. The last ride entrance in the array at the location is not skipped
1473         bool duplicateLocation = false;
1474         while (locationList != locations.cend())
1475         {
1476             const TileCoordsXYZD& locationCoords2 = *locationList++;
1477             if (locationCoords.x == locationCoords2.x && locationCoords.y == locationCoords2.y
1478                 && locationCoords.z == locationCoords2.z)
1479             {
1480                 duplicateLocation = true;
1481                 break;
1482             }
1483         }
1484 
1485         if (duplicateLocation)
1486         {
1487             // if it's a duplicate continue to the next ride entrance
1488             continue;
1489         }
1490         // if it's not a duplicate location
1491         CoordsXY location = locationCoords.ToCoordsXY();
1492 
1493         TileElement* tileElement = map_get_first_element_at(location);
1494         if (tileElement == nullptr)
1495             continue;
1496         do
1497         {
1498             if (tileElement->GetType() != TILE_ELEMENT_TYPE_ENTRANCE)
1499                 continue;
1500             if (tileElement->base_height != locationCoords.z)
1501                 continue;
1502             if (tileElement->AsEntrance()->GetRideIndex() != id)
1503                 continue;
1504             // if it's a park entrance continue to the next tile element
1505             if (tileElement->AsEntrance()->GetEntranceType() > ENTRANCE_TYPE_RIDE_EXIT)
1506                 continue;
1507 
1508             // find the station that's connected to this ride entrance
1509             CoordsXY nextLocation = location;
1510             nextLocation.x += CoordsDirectionDelta[tileElement->GetDirection()].x;
1511             nextLocation.y += CoordsDirectionDelta[tileElement->GetDirection()].y;
1512 
1513             // if there's no connected station, remove the ride entrance (see below)
1514             bool shouldRemove = true;
1515             TileElement* trackElement = map_get_first_element_at(nextLocation);
1516             if (trackElement == nullptr)
1517                 continue;
1518             do
1519             {
1520                 if (trackElement->GetType() != TILE_ELEMENT_TYPE_TRACK)
1521                     continue;
1522                 if (trackElement->AsTrack()->GetRideIndex() != id)
1523                     continue;
1524                 if (trackElement->base_height != tileElement->base_height)
1525                     continue;
1526 
1527                 auto trackType = trackElement->AsTrack()->GetTrackType();
1528 
1529                 // get the StationIndex for the station
1530                 StationIndex stationId = 0;
1531                 if (trackType != TrackElemType::Maze)
1532                 {
1533                     uint8_t trackSequence = trackElement->AsTrack()->GetSequenceIndex();
1534 
1535                     // determine where the ride entrance is relative to the station track
1536                     Direction direction = (tileElement->GetDirection() - direction_reverse(trackElement->GetDirection())) & 3;
1537 
1538                     // if the ride entrance is not on a valid side, remove it
1539                     ted = &GetTrackElementDescriptor(trackType);
1540                     if (!(ted->SequenceProperties[trackSequence] & (1 << direction)))
1541                     {
1542                         continue;
1543                     }
1544 
1545                     stationId = trackElement->AsTrack()->GetStationIndex();
1546                 }
1547                 if (tileElement->AsEntrance()->GetEntranceType() == ENTRANCE_TYPE_RIDE_EXIT)
1548                 {
1549                     // if the location is already set for this station, big problem!
1550                     if (!ride_get_exit_location(this, stationId).IsNull())
1551                         break;
1552                     // set the station's exit location to this one
1553                     CoordsXYZD loc = { location, stations[stationId].GetBaseZ(), tileElement->GetDirection() };
1554                     ride_set_exit_location(this, stationId, TileCoordsXYZD{ loc });
1555                 }
1556                 else
1557                 {
1558                     // if the location is already set for this station, big problem!
1559                     if (!ride_get_entrance_location(this, stationId).IsNull())
1560                         break;
1561                     // set the station's entrance location to this one
1562                     CoordsXYZD loc = { location, stations[stationId].GetBaseZ(), tileElement->GetDirection() };
1563                     ride_set_entrance_location(this, stationId, TileCoordsXYZD{ loc });
1564                 }
1565                 // set the entrance's StationIndex as this station
1566                 tileElement->AsEntrance()->SetStationIndex(stationId);
1567                 shouldRemove = false;
1568             } while (!(trackElement++)->IsLastForTile());
1569 
1570             // remove the ride entrance and clean up if necessary
1571             if (shouldRemove)
1572             {
1573                 footpath_queue_chain_reset();
1574                 maze_entrance_hedge_replacement({ location, tileElement });
1575                 footpath_remove_edges_at(location, tileElement);
1576                 footpath_update_queue_chains();
1577                 map_invalidate_tile_full(location);
1578                 tile_element_remove(tileElement);
1579                 tileElement--;
1580             }
1581         } while (!(tileElement++)->IsLastForTile());
1582     }
1583 }
1584 
ride_select_backwards_from_front()1585 bool ride_select_backwards_from_front()
1586 {
1587     auto ride = get_ride(_currentRideIndex);
1588     if (ride != nullptr)
1589     {
1590         ride_construction_invalidate_current_track();
1591         track_begin_end trackBeginEnd;
1592         if (track_block_get_previous_from_zero(_currentTrackBegin, ride, _currentTrackPieceDirection, &trackBeginEnd))
1593         {
1594             _rideConstructionState = RideConstructionState::Selected;
1595             _currentTrackBegin.x = trackBeginEnd.begin_x;
1596             _currentTrackBegin.y = trackBeginEnd.begin_y;
1597             _currentTrackBegin.z = trackBeginEnd.begin_z;
1598             _currentTrackPieceDirection = trackBeginEnd.begin_direction;
1599             _currentTrackPieceType = trackBeginEnd.begin_element->AsTrack()->GetTrackType();
1600             _currentTrackSelectionFlags = 0;
1601             return true;
1602         }
1603     }
1604     return false;
1605 }
1606 
ride_select_forwards_from_back()1607 bool ride_select_forwards_from_back()
1608 {
1609     auto ride = get_ride(_currentRideIndex);
1610     if (ride != nullptr)
1611     {
1612         ride_construction_invalidate_current_track();
1613 
1614         int32_t z = _currentTrackBegin.z;
1615         int32_t direction = direction_reverse(_currentTrackPieceDirection);
1616         CoordsXYE next_track;
1617         if (track_block_get_next_from_zero(_currentTrackBegin, ride, direction, &next_track, &z, &direction, false))
1618         {
1619             _rideConstructionState = RideConstructionState::Selected;
1620             _currentTrackBegin.x = next_track.x;
1621             _currentTrackBegin.y = next_track.y;
1622             _currentTrackBegin.z = z;
1623             _currentTrackPieceDirection = next_track.element->GetDirection();
1624             _currentTrackPieceType = next_track.element->AsTrack()->GetTrackType();
1625             _currentTrackSelectionFlags = 0;
1626             return true;
1627         }
1628     }
1629     return false;
1630 }
1631 
1632 /**
1633  *
1634  *  rct2: 0x006B58EF
1635  */
ride_are_all_possible_entrances_and_exits_built(Ride * ride)1636 bool ride_are_all_possible_entrances_and_exits_built(Ride* ride)
1637 {
1638     if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IS_SHOP))
1639         return true;
1640 
1641     for (int32_t i = 0; i < MAX_STATIONS; i++)
1642     {
1643         if (ride->stations[i].Start.IsNull())
1644         {
1645             continue;
1646         }
1647         if (ride_get_entrance_location(ride, i).IsNull())
1648         {
1649             gGameCommandErrorText = STR_ENTRANCE_NOT_YET_BUILT;
1650             return false;
1651         }
1652         if (ride_get_exit_location(ride, i).IsNull())
1653         {
1654             gGameCommandErrorText = STR_EXIT_NOT_YET_BUILT;
1655             return false;
1656         }
1657     }
1658     return true;
1659 }
1660