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