1 /*****************************************************************************
2  * Copyright (c) 2014-2020 OpenRCT2 developers
3  *
4  * For a complete list of all authors, please refer to contributors.md
5  * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6  *
7  * OpenRCT2 is licensed under the GNU General Public License version 3.
8  *****************************************************************************/
9 
10 #include "Ride.h"
11 
12 #include "../Cheats.h"
13 #include "../Context.h"
14 #include "../Editor.h"
15 #include "../Game.h"
16 #include "../Input.h"
17 #include "../OpenRCT2.h"
18 #include "../actions/RideSetSettingAction.h"
19 #include "../actions/RideSetStatusAction.h"
20 #include "../actions/RideSetVehicleAction.h"
21 #include "../audio/AudioMixer.h"
22 #include "../audio/audio.h"
23 #include "../common.h"
24 #include "../config/Config.h"
25 #include "../core/FixedVector.h"
26 #include "../core/Guard.hpp"
27 #include "../core/Numerics.hpp"
28 #include "../interface/Window.h"
29 #include "../localisation/Date.h"
30 #include "../localisation/Localisation.h"
31 #include "../management/Finance.h"
32 #include "../management/Marketing.h"
33 #include "../management/NewsItem.h"
34 #include "../network/network.h"
35 #include "../object/MusicObject.h"
36 #include "../object/ObjectList.h"
37 #include "../object/ObjectManager.h"
38 #include "../object/StationObject.h"
39 #include "../paint/VirtualFloor.h"
40 #include "../peep/Peep.h"
41 #include "../peep/Staff.h"
42 #include "../rct1/RCT1.h"
43 #include "../scenario/Scenario.h"
44 #include "../ui/UiContext.h"
45 #include "../ui/WindowManager.h"
46 #include "../util/Util.h"
47 #include "../windows/Intent.h"
48 #include "../world/Banner.h"
49 #include "../world/Climate.h"
50 #include "../world/Footpath.h"
51 #include "../world/Location.hpp"
52 #include "../world/Map.h"
53 #include "../world/MapAnimation.h"
54 #include "../world/Park.h"
55 #include "../world/Scenery.h"
56 #include "../world/Sprite.h"
57 #include "CableLift.h"
58 #include "RideAudio.h"
59 #include "RideData.h"
60 #include "ShopItem.h"
61 #include "Station.h"
62 #include "Track.h"
63 #include "TrackData.h"
64 #include "TrackDesign.h"
65 #include "TrainManager.h"
66 #include "Vehicle.h"
67 
68 #include <algorithm>
69 #include <cassert>
70 #include <climits>
71 #include <cstdlib>
72 #include <iterator>
73 #include <limits>
74 #include <optional>
75 
76 using namespace OpenRCT2;
77 using namespace OpenRCT2::TrackMetaData;
78 
operator ++(RideMode & d,int)79 RideMode& operator++(RideMode& d, int)
80 {
81     return d = (d == RideMode::Count) ? RideMode::Normal : static_cast<RideMode>(static_cast<uint8_t>(d) + 1);
82 }
83 
84 static constexpr const int32_t RideInspectionInterval[] = {
85     10, 20, 30, 45, 60, 120, 0, 0,
86 };
87 
88 static std::vector<Ride> _rides;
89 
90 // Static function declarations
91 Staff* find_closest_mechanic(const CoordsXY& entrancePosition, int32_t forInspection);
92 static void ride_breakdown_status_update(Ride* ride);
93 static void ride_breakdown_update(Ride* ride);
94 static void ride_call_closest_mechanic(Ride* ride);
95 static void ride_call_mechanic(Ride* ride, Peep* mechanic, int32_t forInspection);
96 static void ride_entrance_exit_connected(Ride* ride);
97 static int32_t ride_get_new_breakdown_problem(Ride* ride);
98 static void ride_inspection_update(Ride* ride);
99 static void ride_mechanic_status_update(Ride* ride, int32_t mechanicStatus);
100 static void ride_music_update(Ride* ride);
101 static void ride_shop_connected(Ride* ride);
102 
GetRideManager()103 RideManager GetRideManager()
104 {
105     return {};
106 }
107 
size() const108 size_t RideManager::size() const
109 {
110     size_t count = 0;
111     for (size_t i = 0; i < _rides.size(); i++)
112     {
113         if (_rides[i].type != RIDE_TYPE_NULL)
114         {
115             count++;
116         }
117     }
118     return count;
119 }
120 
begin()121 RideManager::Iterator RideManager::begin()
122 {
123     return RideManager::Iterator(*this, 0, _rides.size());
124 }
125 
end()126 RideManager::Iterator RideManager::end()
127 {
128     return RideManager::Iterator(*this, _rides.size(), _rides.size());
129 }
130 
GetNextFreeRideId()131 ride_id_t GetNextFreeRideId()
132 {
133     size_t result = _rides.size();
134     for (size_t i = 0; i < _rides.size(); i++)
135     {
136         if (_rides[i].type == RIDE_TYPE_NULL)
137         {
138             result = i;
139             break;
140         }
141     }
142     if (result >= MAX_RIDES)
143     {
144         return RIDE_ID_NULL;
145     }
146     return static_cast<ride_id_t>(result);
147 }
148 
GetOrAllocateRide(ride_id_t index)149 Ride* GetOrAllocateRide(ride_id_t index)
150 {
151     const auto idx = static_cast<size_t>(index);
152     if (_rides.size() <= idx)
153     {
154         _rides.resize(idx + 1);
155     }
156 
157     auto result = &_rides[idx];
158     result->id = index;
159     return result;
160 }
161 
get_ride(ride_id_t index)162 Ride* get_ride(ride_id_t index)
163 {
164     const auto idx = static_cast<size_t>(index);
165     if (idx < _rides.size())
166     {
167         auto& ride = _rides[idx];
168         if (ride.type != RIDE_TYPE_NULL)
169         {
170             assert(ride.id == index);
171             return &ride;
172         }
173     }
174     return nullptr;
175 }
176 
get_ride_entry(ObjectEntryIndex index)177 rct_ride_entry* get_ride_entry(ObjectEntryIndex index)
178 {
179     rct_ride_entry* result = nullptr;
180     auto& objMgr = OpenRCT2::GetContext()->GetObjectManager();
181 
182     auto obj = objMgr.GetLoadedObject(ObjectType::Ride, index);
183     if (obj != nullptr)
184     {
185         result = static_cast<rct_ride_entry*>(obj->GetLegacyData());
186     }
187 
188     return result;
189 }
190 
get_ride_entry_name(ObjectEntryIndex index)191 std::string_view get_ride_entry_name(ObjectEntryIndex index)
192 {
193     if (index >= object_entry_group_counts[EnumValue(ObjectType::Ride)])
194     {
195         log_error("invalid index %d for ride type", index);
196         return {};
197     }
198 
199     auto objectEntry = object_entry_get_object(ObjectType::Ride, index);
200     if (objectEntry != nullptr)
201     {
202         return objectEntry->GetLegacyIdentifier();
203     }
204     return {};
205 }
206 
GetRideEntry() const207 rct_ride_entry* Ride::GetRideEntry() const
208 {
209     return get_ride_entry(subtype);
210 }
211 
ride_get_count()212 int32_t ride_get_count()
213 {
214     return static_cast<int32_t>(GetRideManager().size());
215 }
216 
GetNumPrices() const217 size_t Ride::GetNumPrices() const
218 {
219     size_t result = 0;
220     if (type == RIDE_TYPE_CASH_MACHINE || type == RIDE_TYPE_FIRST_AID)
221     {
222         result = 0;
223     }
224     else if (type == RIDE_TYPE_TOILETS)
225     {
226         result = 1;
227     }
228     else
229     {
230         result = 1;
231 
232         auto rideEntry = GetRideEntry();
233         if (rideEntry != nullptr)
234         {
235             if (lifecycle_flags & RIDE_LIFECYCLE_ON_RIDE_PHOTO)
236             {
237                 result++;
238             }
239             else if (rideEntry->shop_item[1] != ShopItem::None)
240             {
241                 result++;
242             }
243         }
244     }
245     return result;
246 }
247 
GetAge() const248 int32_t Ride::GetAge() const
249 {
250     return gDateMonthsElapsed - build_date;
251 }
252 
GetTotalQueueLength() const253 int32_t Ride::GetTotalQueueLength() const
254 {
255     int32_t i, queueLength = 0;
256     for (i = 0; i < MAX_STATIONS; i++)
257         if (!ride_get_entrance_location(this, i).IsNull())
258             queueLength += stations[i].QueueLength;
259     return queueLength;
260 }
261 
GetMaxQueueTime() const262 int32_t Ride::GetMaxQueueTime() const
263 {
264     uint8_t i, queueTime = 0;
265     for (i = 0; i < MAX_STATIONS; i++)
266         if (!ride_get_entrance_location(this, i).IsNull())
267             queueTime = std::max(queueTime, stations[i].QueueTime);
268     return static_cast<int32_t>(queueTime);
269 }
270 
GetQueueHeadGuest(StationIndex stationIndex) const271 Guest* Ride::GetQueueHeadGuest(StationIndex stationIndex) const
272 {
273     Guest* peep;
274     Guest* result = nullptr;
275     uint16_t spriteIndex = stations[stationIndex].LastPeepInQueue;
276     while ((peep = TryGetEntity<Guest>(spriteIndex)) != nullptr)
277     {
278         spriteIndex = peep->GuestNextInQueue;
279         result = peep;
280     }
281     return result;
282 }
283 
UpdateQueueLength(StationIndex stationIndex)284 void Ride::UpdateQueueLength(StationIndex stationIndex)
285 {
286     uint16_t count = 0;
287     Guest* peep;
288     uint16_t spriteIndex = stations[stationIndex].LastPeepInQueue;
289     while ((peep = TryGetEntity<Guest>(spriteIndex)) != nullptr)
290     {
291         spriteIndex = peep->GuestNextInQueue;
292         count++;
293     }
294     stations[stationIndex].QueueLength = count;
295 }
296 
QueueInsertGuestAtFront(StationIndex stationIndex,Guest * peep)297 void Ride::QueueInsertGuestAtFront(StationIndex stationIndex, Guest* peep)
298 {
299     assert(stationIndex < MAX_STATIONS);
300     assert(peep != nullptr);
301 
302     peep->GuestNextInQueue = SPRITE_INDEX_NULL;
303     auto* queueHeadGuest = GetQueueHeadGuest(peep->CurrentRideStation);
304     if (queueHeadGuest == nullptr)
305     {
306         stations[peep->CurrentRideStation].LastPeepInQueue = peep->sprite_index;
307     }
308     else
309     {
310         queueHeadGuest->GuestNextInQueue = peep->sprite_index;
311     }
312     UpdateQueueLength(peep->CurrentRideStation);
313 }
314 
315 /**
316  *
317  *  rct2: 0x006AC916
318  */
ride_update_favourited_stat()319 void ride_update_favourited_stat()
320 {
321     for (auto& ride : GetRideManager())
322         ride.guests_favourite = 0;
323 
324     for (auto peep : EntityList<Guest>())
325     {
326         if (peep->FavouriteRide != RIDE_ID_NULL)
327         {
328             auto ride = get_ride(peep->FavouriteRide);
329             if (ride != nullptr)
330             {
331                 ride->guests_favourite++;
332                 ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_CUSTOMER;
333             }
334         }
335     }
336 
337     window_invalidate_by_class(WC_RIDE_LIST);
338 }
339 
340 /**
341  *
342  *  rct2: 0x006AC3AB
343  */
CalculateIncomePerHour() const344 money64 Ride::CalculateIncomePerHour() const
345 {
346     // Get entry by ride to provide better reporting
347     rct_ride_entry* entry = GetRideEntry();
348     if (entry == nullptr)
349     {
350         return 0;
351     }
352     auto customersPerHour = ride_customers_per_hour(this);
353     money64 priceMinusCost = ride_get_price(this);
354 
355     ShopItem currentShopItem = entry->shop_item[0];
356     if (currentShopItem != ShopItem::None)
357     {
358         priceMinusCost -= GetShopItemDescriptor(currentShopItem).Cost;
359     }
360 
361     currentShopItem = (lifecycle_flags & RIDE_LIFECYCLE_ON_RIDE_PHOTO) ? GetRideTypeDescriptor().PhotoItem
362                                                                        : entry->shop_item[1];
363 
364     if (currentShopItem != ShopItem::None)
365     {
366         const money16 shopItemProfit = price[1] - GetShopItemDescriptor(currentShopItem).Cost;
367 
368         if (GetShopItemDescriptor(currentShopItem).IsPhoto())
369         {
370             const int32_t rideTicketsSold = total_customers - no_secondary_items_sold;
371 
372             // Use the ratio between photo sold and total admissions to approximate the photo income(as not every guest will buy
373             // one).
374             // TODO: use data from the last 5 minutes instead of all-time values for a more accurate calculation
375             if (rideTicketsSold > 0)
376             {
377                 priceMinusCost += ((static_cast<int32_t>(no_secondary_items_sold) * shopItemProfit) / rideTicketsSold);
378             }
379         }
380         else
381         {
382             priceMinusCost += shopItemProfit;
383         }
384 
385         if (entry->shop_item[0] != ShopItem::None)
386             priceMinusCost /= 2;
387     }
388 
389     return customersPerHour * priceMinusCost;
390 }
391 
392 /**
393  *
394  *  rct2: 0x006CAF80
395  * ax result x
396  * bx result y
397  * dl ride index
398  * esi result map element
399  */
ride_try_get_origin_element(const Ride * ride,CoordsXYE * output)400 bool ride_try_get_origin_element(const Ride* ride, CoordsXYE* output)
401 {
402     TileElement* resultTileElement = nullptr;
403 
404     tile_element_iterator it;
405     tile_element_iterator_begin(&it);
406     do
407     {
408         if (it.element->GetType() != TILE_ELEMENT_TYPE_TRACK)
409             continue;
410         if (it.element->AsTrack()->GetRideIndex() != ride->id)
411             continue;
412 
413         // Found a track piece for target ride
414 
415         // Check if it's not the station or ??? (but allow end piece of station)
416         const auto& ted = GetTrackElementDescriptor(it.element->AsTrack()->GetTrackType());
417         bool specialTrackPiece
418             = (it.element->AsTrack()->GetTrackType() != TrackElemType::BeginStation
419                && it.element->AsTrack()->GetTrackType() != TrackElemType::MiddleStation
420                && (ted.SequenceProperties[0] & TRACK_SEQUENCE_FLAG_ORIGIN));
421 
422         // Set result tile to this track piece if first found track or a ???
423         if (resultTileElement == nullptr || specialTrackPiece)
424         {
425             resultTileElement = it.element;
426 
427             if (output != nullptr)
428             {
429                 output->element = resultTileElement;
430                 output->x = it.x * COORDS_XY_STEP;
431                 output->y = it.y * COORDS_XY_STEP;
432             }
433         }
434 
435         if (specialTrackPiece)
436         {
437             return true;
438         }
439     } while (tile_element_iterator_next(&it));
440 
441     return resultTileElement != nullptr;
442 }
443 
444 /**
445  *
446  * rct2: 0x006C6096
447  * Gets the next track block coordinates from the
448  * coordinates of the first of element of a track block.
449  * Use track_block_get_next if you are unsure if you are
450  * on the first element of a track block
451  */
track_block_get_next_from_zero(const CoordsXYZ & startPos,Ride * ride,uint8_t direction_start,CoordsXYE * output,int32_t * z,int32_t * direction,bool isGhost)452 bool track_block_get_next_from_zero(
453     const CoordsXYZ& startPos, Ride* ride, uint8_t direction_start, CoordsXYE* output, int32_t* z, int32_t* direction,
454     bool isGhost)
455 {
456     auto trackPos = startPos;
457 
458     if (!(direction_start & TRACK_BLOCK_2))
459     {
460         trackPos += CoordsDirectionDelta[direction_start];
461     }
462 
463     TileElement* tileElement = map_get_first_element_at(trackPos);
464     if (tileElement == nullptr)
465     {
466         output->element = nullptr;
467         output->x = LOCATION_NULL;
468         return false;
469     }
470 
471     do
472     {
473         auto trackElement = tileElement->AsTrack();
474         if (trackElement == nullptr)
475             continue;
476 
477         if (trackElement->GetRideIndex() != ride->id)
478             continue;
479 
480         if (trackElement->GetSequenceIndex() != 0)
481             continue;
482 
483         if (tileElement->IsGhost() != isGhost)
484             continue;
485 
486         const auto& ted = GetTrackElementDescriptor(trackElement->GetTrackType());
487         const auto* nextTrackBlock = ted.Block;
488         if (nextTrackBlock == nullptr)
489             continue;
490 
491         const auto& nextTrackCoordinate = ted.Coordinates;
492         uint8_t nextRotation = tileElement->GetDirectionWithOffset(nextTrackCoordinate.rotation_begin)
493             | (nextTrackCoordinate.rotation_begin & TRACK_BLOCK_2);
494 
495         if (nextRotation != direction_start)
496             continue;
497 
498         int16_t nextZ = nextTrackCoordinate.z_begin - nextTrackBlock->z + tileElement->GetBaseZ();
499         if (nextZ != trackPos.z)
500             continue;
501 
502         if (z != nullptr)
503             *z = tileElement->GetBaseZ();
504         if (direction != nullptr)
505             *direction = nextRotation;
506         *output = { trackPos, tileElement };
507         return true;
508     } while (!(tileElement++)->IsLastForTile());
509 
510     if (direction != nullptr)
511         *direction = direction_start;
512     if (z != nullptr)
513         *z = trackPos.z;
514     *output = { trackPos, --tileElement };
515     return false;
516 }
517 
518 /**
519  *
520  *  rct2: 0x006C60C2
521  */
track_block_get_next(CoordsXYE * input,CoordsXYE * output,int32_t * z,int32_t * direction)522 bool track_block_get_next(CoordsXYE* input, CoordsXYE* output, int32_t* z, int32_t* direction)
523 {
524     if (input == nullptr || input->element == nullptr)
525         return false;
526 
527     auto inputElement = input->element->AsTrack();
528     if (inputElement == nullptr)
529         return false;
530 
531     auto rideIndex = inputElement->GetRideIndex();
532     auto ride = get_ride(rideIndex);
533     if (ride == nullptr)
534         return false;
535 
536     const auto& ted = GetTrackElementDescriptor(inputElement->GetTrackType());
537     const auto* trackBlock = ted.Block;
538     if (trackBlock == nullptr)
539         return false;
540 
541     trackBlock += inputElement->GetSequenceIndex();
542 
543     const auto& trackCoordinate = ted.Coordinates;
544 
545     int32_t x = input->x;
546     int32_t y = input->y;
547     int32_t OriginZ = inputElement->GetBaseZ();
548 
549     uint8_t rotation = inputElement->GetDirection();
550 
551     CoordsXY coords = { x, y };
552     CoordsXY trackCoordOffset = { trackCoordinate.x, trackCoordinate.y };
553     CoordsXY trackBlockOffset = { trackBlock->x, trackBlock->y };
554     coords += trackCoordOffset.Rotate(rotation);
555     coords += trackBlockOffset.Rotate(direction_reverse(rotation));
556 
557     OriginZ -= trackBlock->z;
558     OriginZ += trackCoordinate.z_end;
559 
560     uint8_t directionStart = ((trackCoordinate.rotation_end + rotation) & TILE_ELEMENT_DIRECTION_MASK)
561         | (trackCoordinate.rotation_end & TRACK_BLOCK_2);
562 
563     return track_block_get_next_from_zero({ coords, OriginZ }, ride, directionStart, output, z, direction, false);
564 }
565 
566 /**
567  * Returns the begin position / direction and end position / direction of the
568  * track piece that proceeds the given location. Gets the previous track block
569  * coordinates from the coordinates of the first of element of a track block.
570  * Use track_block_get_previous if you are unsure if you are on the first
571  * element of a track block
572  *  rct2: 0x006C63D6
573  */
track_block_get_previous_from_zero(const CoordsXYZ & startPos,Ride * ride,uint8_t direction,track_begin_end * outTrackBeginEnd)574 bool track_block_get_previous_from_zero(
575     const CoordsXYZ& startPos, Ride* ride, uint8_t direction, track_begin_end* outTrackBeginEnd)
576 {
577     uint8_t directionStart = direction;
578     direction = direction_reverse(direction);
579     auto trackPos = startPos;
580 
581     if (!(direction & TRACK_BLOCK_2))
582     {
583         trackPos += CoordsDirectionDelta[direction];
584     }
585 
586     TileElement* tileElement = map_get_first_element_at(trackPos);
587     if (tileElement == nullptr)
588     {
589         outTrackBeginEnd->end_x = trackPos.x;
590         outTrackBeginEnd->end_y = trackPos.y;
591         outTrackBeginEnd->begin_element = nullptr;
592         outTrackBeginEnd->begin_direction = direction_reverse(directionStart);
593         return false;
594     }
595 
596     do
597     {
598         auto trackElement = tileElement->AsTrack();
599         if (trackElement == nullptr)
600             continue;
601 
602         if (trackElement->GetRideIndex() != ride->id)
603             continue;
604 
605         const auto* ted = &GetTrackElementDescriptor(trackElement->GetTrackType());
606         const auto* nextTrackBlock = ted->Block;
607         if (nextTrackBlock == nullptr)
608             continue;
609         const auto& nextTrackCoordinate = ted->Coordinates;
610 
611         nextTrackBlock += trackElement->GetSequenceIndex();
612         if ((nextTrackBlock + 1)->index != 255)
613             continue;
614 
615         uint8_t nextRotation = tileElement->GetDirectionWithOffset(nextTrackCoordinate.rotation_end)
616             | (nextTrackCoordinate.rotation_end & TRACK_BLOCK_2);
617 
618         if (nextRotation != directionStart)
619             continue;
620 
621         int16_t nextZ = nextTrackCoordinate.z_end - nextTrackBlock->z + tileElement->GetBaseZ();
622         if (nextZ != trackPos.z)
623             continue;
624 
625         nextRotation = tileElement->GetDirectionWithOffset(nextTrackCoordinate.rotation_begin)
626             | (nextTrackCoordinate.rotation_begin & TRACK_BLOCK_2);
627         outTrackBeginEnd->begin_element = tileElement;
628         outTrackBeginEnd->begin_x = trackPos.x;
629         outTrackBeginEnd->begin_y = trackPos.y;
630         outTrackBeginEnd->end_x = trackPos.x;
631         outTrackBeginEnd->end_y = trackPos.y;
632 
633         CoordsXY coords = { outTrackBeginEnd->begin_x, outTrackBeginEnd->begin_y };
634         CoordsXY offsets = { nextTrackCoordinate.x, nextTrackCoordinate.y };
635         coords += offsets.Rotate(direction_reverse(nextRotation));
636         outTrackBeginEnd->begin_x = coords.x;
637         outTrackBeginEnd->begin_y = coords.y;
638 
639         outTrackBeginEnd->begin_z = tileElement->GetBaseZ();
640 
641         ted = &GetTrackElementDescriptor(trackElement->GetTrackType());
642         const auto* nextTrackBlock2 = ted->Block;
643         if (nextTrackBlock2 == nullptr)
644             continue;
645 
646         outTrackBeginEnd->begin_z += nextTrackBlock2->z - nextTrackBlock->z;
647         outTrackBeginEnd->begin_direction = nextRotation;
648         outTrackBeginEnd->end_direction = direction_reverse(directionStart);
649         return true;
650     } while (!(tileElement++)->IsLastForTile());
651 
652     outTrackBeginEnd->end_x = trackPos.x;
653     outTrackBeginEnd->end_y = trackPos.y;
654     outTrackBeginEnd->begin_z = trackPos.z;
655     outTrackBeginEnd->begin_element = nullptr;
656     outTrackBeginEnd->end_direction = direction_reverse(directionStart);
657     return false;
658 }
659 
660 /**
661  *
662  *  rct2: 0x006C6402
663  *
664  * @remarks outTrackBeginEnd.begin_x and outTrackBeginEnd.begin_y will be in the
665  * higher two bytes of ecx and edx where as outTrackBeginEnd.end_x and
666  * outTrackBeginEnd.end_y will be in the lower two bytes (cx and dx).
667  */
track_block_get_previous(const CoordsXYE & trackPos,track_begin_end * outTrackBeginEnd)668 bool track_block_get_previous(const CoordsXYE& trackPos, track_begin_end* outTrackBeginEnd)
669 {
670     if (trackPos.element == nullptr)
671         return false;
672 
673     auto trackElement = trackPos.element->AsTrack();
674     const auto& ted = GetTrackElementDescriptor(trackElement->GetTrackType());
675     if (trackElement == nullptr)
676         return false;
677 
678     auto rideIndex = trackElement->GetRideIndex();
679     auto ride = get_ride(rideIndex);
680     if (ride == nullptr)
681         return false;
682 
683     const auto* trackBlock = ted.Block;
684     if (trackBlock == nullptr)
685         return false;
686 
687     trackBlock += trackElement->GetSequenceIndex();
688 
689     auto trackCoordinate = ted.Coordinates;
690 
691     int32_t z = trackElement->GetBaseZ();
692 
693     uint8_t rotation = trackElement->GetDirection();
694     CoordsXY coords = CoordsXY{ trackPos };
695     CoordsXY offsets = { trackBlock->x, trackBlock->y };
696     coords += offsets.Rotate(direction_reverse(rotation));
697 
698     z -= trackBlock->z;
699     z += trackCoordinate.z_begin;
700 
701     rotation = ((trackCoordinate.rotation_begin + rotation) & TILE_ELEMENT_DIRECTION_MASK)
702         | (trackCoordinate.rotation_begin & TRACK_BLOCK_2);
703 
704     return track_block_get_previous_from_zero({ coords, z }, ride, rotation, outTrackBeginEnd);
705 }
706 
707 /**
708  *
709  * Make sure to pass in the x and y of the start track element too.
710  *  rct2: 0x006CB02F
711  * ax result x
712  * bx result y
713  * esi input / output map element
714  */
ride_find_track_gap(const Ride * ride,CoordsXYE * input,CoordsXYE * output)715 int32_t ride_find_track_gap(const Ride* ride, CoordsXYE* input, CoordsXYE* output)
716 {
717     if (ride == nullptr || input == nullptr || input->element == nullptr
718         || input->element->GetType() != TILE_ELEMENT_TYPE_TRACK)
719         return 0;
720 
721     if (ride->type == RIDE_TYPE_MAZE)
722     {
723         return 0;
724     }
725 
726     rct_window* w = window_find_by_class(WC_RIDE_CONSTRUCTION);
727     if (w != nullptr && _rideConstructionState != RideConstructionState::State0 && _currentRideIndex == ride->id)
728     {
729         ride_construction_invalidate_current_track();
730     }
731 
732     bool moveSlowIt = true;
733     track_circuit_iterator it = {};
734     track_circuit_iterator_begin(&it, *input);
735     track_circuit_iterator slowIt = it;
736     while (track_circuit_iterator_next(&it))
737     {
738         if (!track_is_connected_by_shape(it.last.element, it.current.element))
739         {
740             *output = it.current;
741             return 1;
742         }
743         //#2081: prevent an infinite loop
744         moveSlowIt = !moveSlowIt;
745         if (moveSlowIt)
746         {
747             track_circuit_iterator_next(&slowIt);
748             if (track_circuit_iterators_match(&it, &slowIt))
749             {
750                 *output = it.current;
751                 return 1;
752             }
753         }
754     }
755     if (!it.looped)
756     {
757         *output = it.last;
758         return 1;
759     }
760 
761     return 0;
762 }
763 
FormatStatusTo(Formatter & ft) const764 void Ride::FormatStatusTo(Formatter& ft) const
765 {
766     if (lifecycle_flags & RIDE_LIFECYCLE_CRASHED)
767     {
768         ft.Add<rct_string_id>(STR_CRASHED);
769     }
770     else if (lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN)
771     {
772         ft.Add<rct_string_id>(STR_BROKEN_DOWN);
773     }
774     else if (status == RideStatus::Closed)
775     {
776         if (!GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IS_SHOP))
777         {
778             if (num_riders != 0)
779             {
780                 ft.Add<rct_string_id>(num_riders == 1 ? STR_CLOSED_WITH_PERSON : STR_CLOSED_WITH_PEOPLE);
781                 ft.Add<uint16_t>(num_riders);
782             }
783             else
784             {
785                 ft.Add<rct_string_id>(STR_CLOSED);
786             }
787         }
788         else
789         {
790             ft.Add<rct_string_id>(STR_CLOSED);
791         }
792     }
793     else if (status == RideStatus::Simulating)
794     {
795         ft.Add<rct_string_id>(STR_SIMULATING);
796     }
797     else if (status == RideStatus::Testing)
798     {
799         ft.Add<rct_string_id>(STR_TEST_RUN);
800     }
801     else if (
802         mode == RideMode::Race && !(lifecycle_flags & RIDE_LIFECYCLE_PASS_STATION_NO_STOPPING)
803         && race_winner != SPRITE_INDEX_NULL)
804     {
805         auto peep = GetEntity<Guest>(race_winner);
806         if (peep != nullptr)
807         {
808             ft.Add<rct_string_id>(STR_RACE_WON_BY);
809             peep->FormatNameTo(ft);
810         }
811         else
812         {
813             ft.Add<rct_string_id>(STR_RACE_WON_BY);
814             ft.Add<rct_string_id>(STR_NONE);
815         }
816     }
817     else if (!GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IS_SHOP))
818     {
819         ft.Add<rct_string_id>(num_riders == 1 ? STR_PERSON_ON_RIDE : STR_PEOPLE_ON_RIDE);
820         ft.Add<uint16_t>(num_riders);
821     }
822     else
823     {
824         ft.Add<rct_string_id>(STR_OPEN);
825     }
826 }
827 
ride_get_total_length(const Ride * ride)828 int32_t ride_get_total_length(const Ride* ride)
829 {
830     int32_t i, totalLength = 0;
831     for (i = 0; i < ride->num_stations; i++)
832         totalLength += ride->stations[i].SegmentLength;
833     return totalLength;
834 }
835 
ride_get_total_time(Ride * ride)836 int32_t ride_get_total_time(Ride* ride)
837 {
838     int32_t i, totalTime = 0;
839     for (i = 0; i < ride->num_stations; i++)
840         totalTime += ride->stations[i].SegmentTime;
841     return totalTime;
842 }
843 
CanHaveMultipleCircuits() const844 bool Ride::CanHaveMultipleCircuits() const
845 {
846     if (!(GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_ALLOW_MULTIPLE_CIRCUITS)))
847         return false;
848 
849     // Only allow circuit or launch modes
850     if (mode != RideMode::ContinuousCircuit && mode != RideMode::ReverseInclineLaunchedShuttle
851         && mode != RideMode::PoweredLaunchPasstrough)
852     {
853         return false;
854     }
855 
856     // Must have no more than one vehicle and one station
857     if (num_vehicles > 1 || num_stations > 1)
858         return false;
859 
860     return true;
861 }
862 
SupportsStatus(RideStatus s) const863 bool Ride::SupportsStatus(RideStatus s) const
864 {
865     const auto& rtd = GetRideTypeDescriptor();
866 
867     switch (s)
868     {
869         case RideStatus::Closed:
870         case RideStatus::Open:
871             return true;
872         case RideStatus::Simulating:
873             return (!rtd.HasFlag(RIDE_TYPE_FLAG_NO_TEST_MODE) && rtd.HasFlag(RIDE_TYPE_FLAG_HAS_TRACK));
874         case RideStatus::Testing:
875             return !rtd.HasFlag(RIDE_TYPE_FLAG_NO_TEST_MODE);
876         case RideStatus::Count: // Meaningless but necessary to satisfy -Wswitch
877             return false;
878     }
879     // Unreachable
880     return false;
881 }
882 
883 #pragma region Initialisation functions
884 
885 /**
886  *
887  *  rct2: 0x006ACA89
888  */
ride_init_all()889 void ride_init_all()
890 {
891     _rides.clear();
892     _rides.shrink_to_fit();
893 }
894 
895 /**
896  *
897  *  rct2: 0x006B7A38
898  */
reset_all_ride_build_dates()899 void reset_all_ride_build_dates()
900 {
901     for (auto& ride : GetRideManager())
902     {
903         ride.build_date -= gDateMonthsElapsed;
904     }
905 }
906 
907 #pragma endregion
908 
909 #pragma region Construction
910 
911 #pragma endregion
912 
913 #pragma region Update functions
914 
915 /**
916  *
917  *  rct2: 0x006ABE4C
918  */
UpdateAll()919 void Ride::UpdateAll()
920 {
921     // Remove all rides if scenario editor
922     if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR)
923     {
924         switch (gEditorStep)
925         {
926             case EditorStep::ObjectSelection:
927             case EditorStep::LandscapeEditor:
928             case EditorStep::InventionsListSetUp:
929                 for (auto& ride : GetRideManager())
930                     ride.Delete();
931                 break;
932             case EditorStep::OptionsSelection:
933             case EditorStep::ObjectiveSelection:
934             case EditorStep::SaveScenario:
935             case EditorStep::RollercoasterDesigner:
936             case EditorStep::DesignsManager:
937             case EditorStep::Invalid:
938                 break;
939         }
940         return;
941     }
942 
943     window_update_viewport_ride_music();
944 
945     // Update rides
946     for (auto& ride : GetRideManager())
947         ride.Update();
948 
949     OpenRCT2::RideAudio::UpdateMusicChannels();
950 }
951 
SaveToTrackDesign() const952 std::unique_ptr<TrackDesign> Ride::SaveToTrackDesign() const
953 {
954     if (!(lifecycle_flags & RIDE_LIFECYCLE_TESTED))
955     {
956         context_show_error(STR_CANT_SAVE_TRACK_DESIGN, STR_NONE, {});
957         return nullptr;
958     }
959 
960     if (!ride_has_ratings(this))
961     {
962         context_show_error(STR_CANT_SAVE_TRACK_DESIGN, STR_NONE, {});
963         return nullptr;
964     }
965 
966     auto tds = TrackDesignState{};
967     auto td = std::make_unique<TrackDesign>();
968     auto errMessage = td->CreateTrackDesign(tds, *this);
969     if (errMessage != STR_NONE)
970     {
971         context_show_error(STR_CANT_SAVE_TRACK_DESIGN, errMessage, {});
972         return nullptr;
973     }
974 
975     return td;
976 }
977 
978 /**
979  *
980  *  rct2: 0x006ABE73
981  */
Update()982 void Ride::Update()
983 {
984     if (vehicle_change_timeout != 0)
985         vehicle_change_timeout--;
986 
987     ride_music_update(this);
988 
989     // Update stations
990     if (type != RIDE_TYPE_MAZE)
991         for (int32_t i = 0; i < MAX_STATIONS; i++)
992             ride_update_station(this, i);
993 
994     // Update financial statistics
995     num_customers_timeout++;
996 
997     if (num_customers_timeout >= 960)
998     {
999         // This is meant to update about every 30 seconds
1000         num_customers_timeout = 0;
1001 
1002         // Shift number of customers history, start of the array is the most recent one
1003         for (int32_t i = CUSTOMER_HISTORY_SIZE - 1; i > 0; i--)
1004         {
1005             num_customers[i] = num_customers[i - 1];
1006         }
1007         num_customers[0] = cur_num_customers;
1008 
1009         cur_num_customers = 0;
1010         window_invalidate_flags |= RIDE_INVALIDATE_RIDE_CUSTOMER;
1011 
1012         income_per_hour = CalculateIncomePerHour();
1013         window_invalidate_flags |= RIDE_INVALIDATE_RIDE_INCOME;
1014 
1015         if (upkeep_cost != MONEY16_UNDEFINED)
1016             profit = (income_per_hour - (static_cast<money32>(upkeep_cost * 16)));
1017     }
1018 
1019     // Ride specific updates
1020     if (type == RIDE_TYPE_CHAIRLIFT)
1021         UpdateChairlift();
1022     else if (type == RIDE_TYPE_SPIRAL_SLIDE)
1023         UpdateSpiralSlide();
1024 
1025     ride_breakdown_update(this);
1026 
1027     // Various things include news messages
1028     if (lifecycle_flags & (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_DUE_INSPECTION))
1029         if (((gCurrentTicks >> 1) & 255) == static_cast<uint32_t>(id))
1030             ride_breakdown_status_update(this);
1031 
1032     ride_inspection_update(this);
1033 
1034     // If ride is simulating but crashed, reset the vehicles
1035     if (status == RideStatus::Simulating && (lifecycle_flags & RIDE_LIFECYCLE_CRASHED))
1036     {
1037         if (mode == RideMode::ContinuousCircuitBlockSectioned || mode == RideMode::PoweredLaunchBlockSectioned)
1038         {
1039             // We require this to execute right away during the simulation, always ignore network and queue.
1040             RideSetStatusAction gameAction = RideSetStatusAction(id, RideStatus::Closed);
1041             GameActions::ExecuteNested(&gameAction);
1042         }
1043         else
1044         {
1045             // We require this to execute right away during the simulation, always ignore network and queue.
1046             RideSetStatusAction gameAction = RideSetStatusAction(id, RideStatus::Simulating);
1047             GameActions::ExecuteNested(&gameAction);
1048         }
1049     }
1050 }
1051 
1052 /**
1053  *
1054  *  rct2: 0x006AC489
1055  */
UpdateChairlift()1056 void Ride::UpdateChairlift()
1057 {
1058     if (!(lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK))
1059         return;
1060     if ((lifecycle_flags & (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_CRASHED))
1061         && breakdown_reason_pending == 0)
1062         return;
1063 
1064     uint16_t old_chairlift_bullwheel_rotation = chairlift_bullwheel_rotation >> 14;
1065     chairlift_bullwheel_rotation += speed * 2048;
1066     if (old_chairlift_bullwheel_rotation == speed / 8)
1067         return;
1068 
1069     auto bullwheelLoc = ChairliftBullwheelLocation[0].ToCoordsXYZ();
1070     map_invalidate_tile_zoom1({ bullwheelLoc, bullwheelLoc.z, bullwheelLoc.z + (4 * COORDS_Z_STEP) });
1071 
1072     bullwheelLoc = ChairliftBullwheelLocation[1].ToCoordsXYZ();
1073     map_invalidate_tile_zoom1({ bullwheelLoc, bullwheelLoc.z, bullwheelLoc.z + (4 * COORDS_Z_STEP) });
1074 }
1075 
1076 /**
1077  *
1078  *  rct2: 0x0069A3A2
1079  * edi: ride (in code as bytes offset from start of rides list)
1080  * bl: happiness
1081  */
ride_update_satisfaction(Ride * ride,uint8_t happiness)1082 void ride_update_satisfaction(Ride* ride, uint8_t happiness)
1083 {
1084     ride->satisfaction_next += happiness;
1085     ride->satisfaction_time_out++;
1086     if (ride->satisfaction_time_out >= 20)
1087     {
1088         ride->satisfaction = ride->satisfaction_next >> 2;
1089         ride->satisfaction_next = 0;
1090         ride->satisfaction_time_out = 0;
1091         ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_CUSTOMER;
1092     }
1093 }
1094 
1095 /**
1096  *
1097  *  rct2: 0x0069A3D7
1098  * Updates the ride popularity
1099  * edi : ride
1100  * bl  : pop_amount
1101  * pop_amount can be zero if peep visited but did not purchase.
1102  */
ride_update_popularity(Ride * ride,uint8_t pop_amount)1103 void ride_update_popularity(Ride* ride, uint8_t pop_amount)
1104 {
1105     ride->popularity_next += pop_amount;
1106     ride->popularity_time_out++;
1107     if (ride->popularity_time_out < 25)
1108         return;
1109 
1110     ride->popularity = ride->popularity_next;
1111     ride->popularity_next = 0;
1112     ride->popularity_time_out = 0;
1113     ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_CUSTOMER;
1114 }
1115 
1116 /** rct2: 0x0098DDB8, 0x0098DDBA */
1117 static constexpr const CoordsXY ride_spiral_slide_main_tile_offset[][4] = {
1118     {
1119         { 32, 32 },
1120         { 0, 32 },
1121         { 0, 0 },
1122         { 32, 0 },
1123     },
1124     {
1125         { 32, 0 },
1126         { 0, 0 },
1127         { 0, -32 },
1128         { 32, -32 },
1129     },
1130     {
1131         { 0, 0 },
1132         { -32, 0 },
1133         { -32, -32 },
1134         { 0, -32 },
1135     },
1136     {
1137         { 0, 0 },
1138         { 0, 32 },
1139         { -32, 32 },
1140         { -32, 0 },
1141     },
1142 };
1143 
1144 /**
1145  *
1146  *  rct2: 0x006AC545
1147  */
UpdateSpiralSlide()1148 void Ride::UpdateSpiralSlide()
1149 {
1150     if (gCurrentTicks & 3)
1151         return;
1152     if (slide_in_use == 0)
1153         return;
1154 
1155     spiral_slide_progress++;
1156     if (spiral_slide_progress >= 48)
1157     {
1158         slide_in_use--;
1159 
1160         auto* peep = GetEntity<Guest>(slide_peep);
1161         if (peep != nullptr)
1162         {
1163             auto destination = peep->GetDestination();
1164             destination.x++;
1165             peep->SetDestination(destination);
1166         }
1167     }
1168 
1169     const uint8_t current_rotation = get_current_rotation();
1170     // Invalidate something related to station start
1171     for (int32_t i = 0; i < MAX_STATIONS; i++)
1172     {
1173         if (stations[i].Start.IsNull())
1174             continue;
1175 
1176         auto startLoc = stations[i].Start;
1177 
1178         TileElement* tileElement = ride_get_station_start_track_element(this, i);
1179         if (tileElement == nullptr)
1180             continue;
1181 
1182         int32_t rotation = tileElement->GetDirection();
1183         startLoc += ride_spiral_slide_main_tile_offset[rotation][current_rotation];
1184 
1185         map_invalidate_tile_zoom0({ startLoc, tileElement->GetBaseZ(), tileElement->GetClearanceZ() });
1186     }
1187 }
1188 
1189 #pragma endregion
1190 
1191 #pragma region Breakdown and inspection functions
1192 
1193 static uint8_t _breakdownProblemProbabilities[] = {
1194     25, // BREAKDOWN_SAFETY_CUT_OUT
1195     12, // BREAKDOWN_RESTRAINTS_STUCK_CLOSED
1196     10, // BREAKDOWN_RESTRAINTS_STUCK_OPEN
1197     13, // BREAKDOWN_DOORS_STUCK_CLOSED
1198     10, // BREAKDOWN_DOORS_STUCK_OPEN
1199     6,  // BREAKDOWN_VEHICLE_MALFUNCTION
1200     0,  // BREAKDOWN_BRAKES_FAILURE
1201     3,  // BREAKDOWN_CONTROL_FAILURE
1202 };
1203 
1204 /**
1205  *
1206  *  rct2: 0x006AC7C2
1207  */
ride_inspection_update(Ride * ride)1208 static void ride_inspection_update(Ride* ride)
1209 {
1210     if (gCurrentTicks & 2047)
1211         return;
1212     if (gScreenFlags & SCREEN_FLAGS_TRACK_DESIGNER)
1213         return;
1214 
1215     ride->last_inspection++;
1216     if (ride->last_inspection == 0)
1217         ride->last_inspection--;
1218 
1219     int32_t inspectionIntervalMinutes = RideInspectionInterval[ride->inspection_interval];
1220     // An inspection interval of 0 minutes means the ride is set to never be inspected.
1221     if (inspectionIntervalMinutes == 0)
1222     {
1223         ride->lifecycle_flags &= ~RIDE_LIFECYCLE_DUE_INSPECTION;
1224         return;
1225     }
1226 
1227     if (ride->GetRideTypeDescriptor().AvailableBreakdowns == 0)
1228         return;
1229 
1230     if (inspectionIntervalMinutes > ride->last_inspection)
1231         return;
1232 
1233     if (ride->lifecycle_flags
1234         & (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_DUE_INSPECTION
1235            | RIDE_LIFECYCLE_CRASHED))
1236         return;
1237 
1238     // Inspect the first station that has an exit
1239     ride->lifecycle_flags |= RIDE_LIFECYCLE_DUE_INSPECTION;
1240     ride->mechanic_status = RIDE_MECHANIC_STATUS_CALLING;
1241 
1242     auto stationIndex = ride_get_first_valid_station_exit(ride);
1243     ride->inspection_station = (stationIndex != STATION_INDEX_NULL) ? stationIndex : 0;
1244 }
1245 
get_age_penalty(Ride * ride)1246 static int32_t get_age_penalty(Ride* ride)
1247 {
1248     auto years = date_get_year(ride->GetAge());
1249     switch (years)
1250     {
1251         case 0:
1252             return 0;
1253         case 1:
1254             return ride->unreliability_factor / 8;
1255         case 2:
1256             return ride->unreliability_factor / 4;
1257         case 3:
1258         case 4:
1259             return ride->unreliability_factor / 2;
1260         case 5:
1261         case 6:
1262         case 7:
1263             return ride->unreliability_factor;
1264         default:
1265             return ride->unreliability_factor * 2;
1266     }
1267 }
1268 
1269 /**
1270  *
1271  *  rct2: 0x006AC622
1272  */
ride_breakdown_update(Ride * ride)1273 static void ride_breakdown_update(Ride* ride)
1274 {
1275     if (gCurrentTicks & 255)
1276         return;
1277     if (gScreenFlags & SCREEN_FLAGS_TRACK_DESIGNER)
1278         return;
1279 
1280     if (ride->lifecycle_flags & (RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_CRASHED))
1281         ride->downtime_history[0]++;
1282 
1283     if (!(gCurrentTicks & 8191))
1284     {
1285         int32_t totalDowntime = 0;
1286 
1287         for (int32_t i = 0; i < DOWNTIME_HISTORY_SIZE; i++)
1288         {
1289             totalDowntime += ride->downtime_history[i];
1290         }
1291 
1292         ride->downtime = std::min(totalDowntime / 2, 100);
1293 
1294         for (int32_t i = DOWNTIME_HISTORY_SIZE - 1; i > 0; i--)
1295         {
1296             ride->downtime_history[i] = ride->downtime_history[i - 1];
1297         }
1298         ride->downtime_history[0] = 0;
1299 
1300         ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAINTENANCE;
1301     }
1302 
1303     if (ride->lifecycle_flags & (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_CRASHED))
1304         return;
1305     if (ride->status == RideStatus::Closed || ride->status == RideStatus::Simulating)
1306         return;
1307 
1308     if (!ride->CanBreakDown())
1309     {
1310         ride->reliability = RIDE_INITIAL_RELIABILITY;
1311         return;
1312     }
1313 
1314     // Calculate breakdown probability?
1315     int32_t unreliabilityAccumulator = ride->unreliability_factor + get_age_penalty(ride);
1316     ride->reliability = static_cast<uint16_t>(std::max(0, (ride->reliability - unreliabilityAccumulator)));
1317     ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAINTENANCE;
1318 
1319     // Random probability of a breakdown. Roughly this is 1 in
1320     //
1321     // (25000 - reliability) / 3 000 000
1322     //
1323     // a 0.8% chance, less the breakdown factor which accumulates as the game
1324     // continues.
1325     if ((ride->reliability == 0
1326          || static_cast<int32_t>(scenario_rand() & 0x2FFFFF) <= 1 + RIDE_INITIAL_RELIABILITY - ride->reliability)
1327         && !gCheatsDisableAllBreakdowns)
1328     {
1329         int32_t breakdownReason = ride_get_new_breakdown_problem(ride);
1330         if (breakdownReason != -1)
1331             ride_prepare_breakdown(ride, breakdownReason);
1332     }
1333 }
1334 
1335 /**
1336  *
1337  *  rct2: 0x006B7294
1338  */
ride_get_new_breakdown_problem(Ride * ride)1339 static int32_t ride_get_new_breakdown_problem(Ride* ride)
1340 {
1341     int32_t availableBreakdownProblems, totalProbability, randomProbability, problemBits, breakdownProblem;
1342 
1343     // Brake failure is more likely when it's raining
1344     _breakdownProblemProbabilities[BREAKDOWN_BRAKES_FAILURE] = climate_is_raining() ? 20 : 3;
1345 
1346     if (!ride->CanBreakDown())
1347         return -1;
1348 
1349     availableBreakdownProblems = ride->GetRideTypeDescriptor().AvailableBreakdowns;
1350 
1351     // Calculate the total probability range for all possible breakdown problems
1352     totalProbability = 0;
1353     problemBits = availableBreakdownProblems;
1354     while (problemBits != 0)
1355     {
1356         breakdownProblem = bitscanforward(problemBits);
1357         problemBits &= ~(1 << breakdownProblem);
1358         totalProbability += _breakdownProblemProbabilities[breakdownProblem];
1359     }
1360     if (totalProbability == 0)
1361         return -1;
1362 
1363     // Choose a random number within this range
1364     randomProbability = scenario_rand() % totalProbability;
1365 
1366     // Find which problem range the random number lies
1367     problemBits = availableBreakdownProblems;
1368     do
1369     {
1370         breakdownProblem = bitscanforward(problemBits);
1371         problemBits &= ~(1 << breakdownProblem);
1372         randomProbability -= _breakdownProblemProbabilities[breakdownProblem];
1373     } while (randomProbability >= 0);
1374 
1375     if (breakdownProblem != BREAKDOWN_BRAKES_FAILURE)
1376         return breakdownProblem;
1377 
1378     // Brakes failure can not happen if block brakes are used (so long as there is more than one vehicle)
1379     // However if this is the case, brake failure should be taken out the equation, otherwise block brake
1380     // rides have a lower probability to break down due to a random implementation reason.
1381     if (ride->IsBlockSectioned())
1382         if (ride->num_vehicles != 1)
1383             return -1;
1384 
1385     // If brakes failure is disabled, also take it out of the equation (see above comment why)
1386     if (gCheatsDisableBrakesFailure)
1387         return -1;
1388 
1389     auto monthsOld = ride->GetAge();
1390     if (monthsOld < 16 || ride->reliability_percentage > 50)
1391         return -1;
1392 
1393     return BREAKDOWN_BRAKES_FAILURE;
1394 }
1395 
CanBreakDown() const1396 bool Ride::CanBreakDown() const
1397 {
1398     if (GetRideTypeDescriptor().AvailableBreakdowns == 0)
1399     {
1400         return false;
1401     }
1402 
1403     rct_ride_entry* entry = GetRideEntry();
1404     return entry != nullptr && !(entry->flags & RIDE_ENTRY_FLAG_CANNOT_BREAK_DOWN);
1405 }
1406 
choose_random_train_to_breakdown_safe(Ride * ride)1407 static void choose_random_train_to_breakdown_safe(Ride* ride)
1408 {
1409     // Prevent integer division by zero in case of hacked ride.
1410     if (ride->num_vehicles == 0)
1411         return;
1412 
1413     ride->broken_vehicle = scenario_rand() % ride->num_vehicles;
1414 
1415     // Prevent crash caused by accessing SPRITE_INDEX_NULL on hacked rides.
1416     // This should probably be cleaned up on import instead.
1417     while (ride->vehicles[ride->broken_vehicle] == SPRITE_INDEX_NULL && ride->broken_vehicle != 0)
1418     {
1419         --ride->broken_vehicle;
1420     }
1421 }
1422 
1423 /**
1424  *
1425  *  rct2: 0x006B7348
1426  */
ride_prepare_breakdown(Ride * ride,int32_t breakdownReason)1427 void ride_prepare_breakdown(Ride* ride, int32_t breakdownReason)
1428 {
1429     StationIndex i;
1430     Vehicle* vehicle;
1431 
1432     if (ride->lifecycle_flags & (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_CRASHED))
1433         return;
1434 
1435     ride->lifecycle_flags |= RIDE_LIFECYCLE_BREAKDOWN_PENDING;
1436 
1437     ride->breakdown_reason_pending = breakdownReason;
1438     ride->breakdown_sound_modifier = 0;
1439     ride->not_fixed_timeout = 0;
1440     ride->inspection_station = 0; // ensure set to something.
1441 
1442     switch (breakdownReason)
1443     {
1444         case BREAKDOWN_SAFETY_CUT_OUT:
1445         case BREAKDOWN_CONTROL_FAILURE:
1446             // Inspect first station with an exit
1447             i = ride_get_first_valid_station_exit(ride);
1448             if (i != STATION_INDEX_NULL)
1449             {
1450                 ride->inspection_station = i;
1451             }
1452 
1453             break;
1454         case BREAKDOWN_RESTRAINTS_STUCK_CLOSED:
1455         case BREAKDOWN_RESTRAINTS_STUCK_OPEN:
1456         case BREAKDOWN_DOORS_STUCK_CLOSED:
1457         case BREAKDOWN_DOORS_STUCK_OPEN:
1458             // Choose a random train and car
1459             choose_random_train_to_breakdown_safe(ride);
1460             if (ride->num_cars_per_train != 0)
1461             {
1462                 ride->broken_car = scenario_rand() % ride->num_cars_per_train;
1463 
1464                 // Set flag on broken car
1465                 vehicle = GetEntity<Vehicle>(ride->vehicles[ride->broken_vehicle]);
1466                 if (vehicle != nullptr)
1467                 {
1468                     vehicle = vehicle->GetCar(ride->broken_car);
1469                 }
1470                 if (vehicle != nullptr)
1471                 {
1472                     vehicle->SetUpdateFlag(VEHICLE_UPDATE_FLAG_BROKEN_CAR);
1473                 }
1474             }
1475             break;
1476         case BREAKDOWN_VEHICLE_MALFUNCTION:
1477             // Choose a random train
1478             choose_random_train_to_breakdown_safe(ride);
1479             ride->broken_car = 0;
1480 
1481             // Set flag on broken train, first car
1482             vehicle = GetEntity<Vehicle>(ride->vehicles[ride->broken_vehicle]);
1483             if (vehicle != nullptr)
1484             {
1485                 vehicle->SetUpdateFlag(VEHICLE_UPDATE_FLAG_BROKEN_TRAIN);
1486             }
1487             break;
1488         case BREAKDOWN_BRAKES_FAILURE:
1489             // Original code generates a random number but does not use it
1490             // Unsure if this was supposed to choose a random station (or random station with an exit)
1491             i = ride_get_first_valid_station_exit(ride);
1492             if (i != STATION_INDEX_NULL)
1493             {
1494                 ride->inspection_station = i;
1495             }
1496             break;
1497     }
1498 }
1499 
1500 /**
1501  *
1502  *  rct2: 0x006B74FA
1503  */
ride_breakdown_add_news_item(Ride * ride)1504 void ride_breakdown_add_news_item(Ride* ride)
1505 {
1506     if (gConfigNotifications.ride_broken_down)
1507     {
1508         Formatter ft;
1509         ride->FormatNameTo(ft);
1510         News::AddItemToQueue(News::ItemType::Ride, STR_RIDE_IS_BROKEN_DOWN, EnumValue(ride->id), ft);
1511     }
1512 }
1513 
1514 /**
1515  *
1516  *  rct2: 0x006B75C8
1517  */
ride_breakdown_status_update(Ride * ride)1518 static void ride_breakdown_status_update(Ride* ride)
1519 {
1520     // Warn player if ride hasn't been fixed for ages
1521     if (ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN)
1522     {
1523         ride->not_fixed_timeout++;
1524         // When there has been a full 255 timeout ticks this
1525         // will force timeout ticks to keep issuing news every
1526         // 16 ticks. Note there is no reason to do this.
1527         if (ride->not_fixed_timeout == 0)
1528             ride->not_fixed_timeout -= 16;
1529 
1530         if (!(ride->not_fixed_timeout & 15) && ride->mechanic_status != RIDE_MECHANIC_STATUS_FIXING
1531             && ride->mechanic_status != RIDE_MECHANIC_STATUS_HAS_FIXED_STATION_BRAKES)
1532         {
1533             if (gConfigNotifications.ride_warnings)
1534             {
1535                 Formatter ft;
1536                 ride->FormatNameTo(ft);
1537                 News::AddItemToQueue(News::ItemType::Ride, STR_RIDE_IS_STILL_NOT_FIXED, EnumValue(ride->id), ft);
1538             }
1539         }
1540     }
1541 
1542     ride_mechanic_status_update(ride, ride->mechanic_status);
1543 }
1544 
1545 /**
1546  *
1547  *  rct2: 0x006B762F
1548  */
ride_mechanic_status_update(Ride * ride,int32_t mechanicStatus)1549 static void ride_mechanic_status_update(Ride* ride, int32_t mechanicStatus)
1550 {
1551     // Turn a pending breakdown into a breakdown.
1552     if ((mechanicStatus == RIDE_MECHANIC_STATUS_UNDEFINED || mechanicStatus == RIDE_MECHANIC_STATUS_CALLING
1553          || mechanicStatus == RIDE_MECHANIC_STATUS_HEADING)
1554         && (ride->lifecycle_flags & RIDE_LIFECYCLE_BREAKDOWN_PENDING) && !(ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN))
1555     {
1556         auto breakdownReason = ride->breakdown_reason_pending;
1557         if (breakdownReason == BREAKDOWN_SAFETY_CUT_OUT || breakdownReason == BREAKDOWN_BRAKES_FAILURE
1558             || breakdownReason == BREAKDOWN_CONTROL_FAILURE)
1559         {
1560             ride->lifecycle_flags |= RIDE_LIFECYCLE_BROKEN_DOWN;
1561             ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAINTENANCE | RIDE_INVALIDATE_RIDE_LIST
1562                 | RIDE_INVALIDATE_RIDE_MAIN;
1563             ride->breakdown_reason = breakdownReason;
1564             ride_breakdown_add_news_item(ride);
1565         }
1566     }
1567     switch (mechanicStatus)
1568     {
1569         case RIDE_MECHANIC_STATUS_UNDEFINED:
1570             if (ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN)
1571             {
1572                 ride->mechanic_status = RIDE_MECHANIC_STATUS_CALLING;
1573             }
1574             break;
1575         case RIDE_MECHANIC_STATUS_CALLING:
1576             if (ride->GetRideTypeDescriptor().AvailableBreakdowns == 0)
1577             {
1578                 ride->lifecycle_flags &= ~(
1579                     RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_DUE_INSPECTION);
1580                 break;
1581             }
1582 
1583             ride_call_closest_mechanic(ride);
1584             break;
1585         case RIDE_MECHANIC_STATUS_HEADING:
1586         {
1587             auto mechanic = ride_get_mechanic(ride);
1588             bool rideNeedsRepair = (ride->lifecycle_flags & (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN));
1589             if (mechanic == nullptr
1590                 || (mechanic->State != PeepState::HeadingToInspection && mechanic->State != PeepState::Answering)
1591                 || mechanic->CurrentRide != ride->id)
1592             {
1593                 ride->mechanic_status = RIDE_MECHANIC_STATUS_CALLING;
1594                 ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAINTENANCE;
1595                 ride_mechanic_status_update(ride, RIDE_MECHANIC_STATUS_CALLING);
1596             }
1597             // if the ride is broken down, but a mechanic was heading for an inspection, update orders to fix
1598             else if (rideNeedsRepair && mechanic->State == PeepState::HeadingToInspection)
1599             {
1600                 // updates orders for mechanic already heading to inspect ride
1601                 // forInspection == false means start repair (goes to PeepState::Answering)
1602                 ride_call_mechanic(ride, mechanic, false);
1603             }
1604             break;
1605         }
1606         case RIDE_MECHANIC_STATUS_FIXING:
1607         {
1608             auto mechanic = ride_get_mechanic(ride);
1609             if (mechanic == nullptr
1610                 || (mechanic->State != PeepState::HeadingToInspection && mechanic->State != PeepState::Fixing
1611                     && mechanic->State != PeepState::Inspecting && mechanic->State != PeepState::Answering))
1612             {
1613                 ride->mechanic_status = RIDE_MECHANIC_STATUS_CALLING;
1614                 ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAINTENANCE;
1615                 ride_mechanic_status_update(ride, RIDE_MECHANIC_STATUS_CALLING);
1616             }
1617             break;
1618         }
1619     }
1620 }
1621 
1622 /**
1623  *
1624  *  rct2: 0x006B796C
1625  */
ride_call_mechanic(Ride * ride,Peep * mechanic,int32_t forInspection)1626 static void ride_call_mechanic(Ride* ride, Peep* mechanic, int32_t forInspection)
1627 {
1628     mechanic->SetState(forInspection ? PeepState::HeadingToInspection : PeepState::Answering);
1629     mechanic->SubState = 0;
1630     ride->mechanic_status = RIDE_MECHANIC_STATUS_HEADING;
1631     ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAINTENANCE;
1632     ride->mechanic = mechanic->sprite_index;
1633     mechanic->CurrentRide = ride->id;
1634     mechanic->CurrentRideStation = ride->inspection_station;
1635 }
1636 
1637 /**
1638  *
1639  *  rct2: 0x006B76AB
1640  */
ride_call_closest_mechanic(Ride * ride)1641 static void ride_call_closest_mechanic(Ride* ride)
1642 {
1643     auto forInspection = (ride->lifecycle_flags & (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN)) == 0;
1644     auto mechanic = ride_find_closest_mechanic(ride, forInspection);
1645     if (mechanic != nullptr)
1646         ride_call_mechanic(ride, mechanic, forInspection);
1647 }
1648 
ride_find_closest_mechanic(Ride * ride,int32_t forInspection)1649 Staff* ride_find_closest_mechanic(Ride* ride, int32_t forInspection)
1650 {
1651     // Get either exit position or entrance position if there is no exit
1652     auto stationIndex = ride->inspection_station;
1653     TileCoordsXYZD location = ride_get_exit_location(ride, stationIndex);
1654     if (location.IsNull())
1655     {
1656         location = ride_get_entrance_location(ride, stationIndex);
1657         if (location.IsNull())
1658             return nullptr;
1659     }
1660 
1661     // Get station start track element and position
1662     auto mapLocation = location.ToCoordsXYZ();
1663     TileElement* tileElement = ride_get_station_exit_element(mapLocation);
1664     if (tileElement == nullptr)
1665         return nullptr;
1666 
1667     // Set x,y to centre of the station exit for the mechanic search.
1668     auto centreMapLocation = mapLocation.ToTileCentre();
1669 
1670     return find_closest_mechanic(centreMapLocation, forInspection);
1671 }
1672 
1673 /**
1674  *
1675  *  rct2: 0x006B774B (forInspection = 0)
1676  *  rct2: 0x006B78C3 (forInspection = 1)
1677  */
find_closest_mechanic(const CoordsXY & entrancePosition,int32_t forInspection)1678 Staff* find_closest_mechanic(const CoordsXY& entrancePosition, int32_t forInspection)
1679 {
1680     Staff* closestMechanic = nullptr;
1681     uint32_t closestDistance = std::numeric_limits<uint32_t>::max();
1682 
1683     for (auto peep : EntityList<Staff>())
1684     {
1685         if (!peep->IsMechanic())
1686             continue;
1687 
1688         if (!forInspection)
1689         {
1690             if (peep->State == PeepState::HeadingToInspection)
1691             {
1692                 if (peep->SubState >= 4)
1693                     continue;
1694             }
1695             else if (peep->State != PeepState::Patrolling)
1696                 continue;
1697 
1698             if (!(peep->StaffOrders & STAFF_ORDERS_FIX_RIDES))
1699                 continue;
1700         }
1701         else
1702         {
1703             if (peep->State != PeepState::Patrolling || !(peep->StaffOrders & STAFF_ORDERS_INSPECT_RIDES))
1704                 continue;
1705         }
1706 
1707         auto location = entrancePosition.ToTileStart();
1708         if (map_is_location_in_park(location))
1709             if (!peep->IsLocationInPatrol(location))
1710                 continue;
1711 
1712         if (peep->x == LOCATION_NULL)
1713             continue;
1714 
1715         // Manhattan distance
1716         uint32_t distance = std::abs(peep->x - entrancePosition.x) + std::abs(peep->y - entrancePosition.y);
1717         if (distance < closestDistance)
1718         {
1719             closestDistance = distance;
1720             closestMechanic = peep;
1721         }
1722     }
1723 
1724     return closestMechanic;
1725 }
1726 
ride_get_mechanic(Ride * ride)1727 Staff* ride_get_mechanic(Ride* ride)
1728 {
1729     auto staff = GetEntity<Staff>(ride->mechanic);
1730     if (staff != nullptr && staff->IsMechanic())
1731     {
1732         return staff;
1733     }
1734     return nullptr;
1735 }
1736 
ride_get_assigned_mechanic(Ride * ride)1737 Staff* ride_get_assigned_mechanic(Ride* ride)
1738 {
1739     if (ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN)
1740     {
1741         if (ride->mechanic_status == RIDE_MECHANIC_STATUS_HEADING || ride->mechanic_status == RIDE_MECHANIC_STATUS_FIXING
1742             || ride->mechanic_status == RIDE_MECHANIC_STATUS_HAS_FIXED_STATION_BRAKES)
1743         {
1744             return ride_get_mechanic(ride);
1745         }
1746     }
1747 
1748     return nullptr;
1749 }
1750 
1751 #pragma endregion
1752 
1753 #pragma region Music functions
1754 
1755 /**
1756  *
1757  *  Calculates the sample rate for ride music.
1758  */
RideMusicSampleRate(Ride * ride)1759 static int32_t RideMusicSampleRate(Ride* ride)
1760 {
1761     int32_t sampleRate = 22050;
1762 
1763     // Alter sample rate for a power cut effect
1764     if (ride->lifecycle_flags & (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN))
1765     {
1766         sampleRate = ride->breakdown_sound_modifier * 70;
1767         if (ride->breakdown_reason_pending != BREAKDOWN_CONTROL_FAILURE)
1768             sampleRate *= -1;
1769         sampleRate += 22050;
1770     }
1771 
1772     return sampleRate;
1773 }
1774 
1775 /**
1776  *
1777  *  Ride music slows down upon breaking. If it's completely broken, no music should play.
1778  */
RideMusicBreakdownEffect(Ride * ride)1779 static bool RideMusicBreakdownEffect(Ride* ride)
1780 {
1781     // Oscillate parameters for a power cut effect when breaking down
1782     if (ride->lifecycle_flags & (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN))
1783     {
1784         if (ride->breakdown_reason_pending == BREAKDOWN_CONTROL_FAILURE)
1785         {
1786             if (!(gCurrentTicks & 7))
1787                 if (ride->breakdown_sound_modifier != 255)
1788                     ride->breakdown_sound_modifier++;
1789         }
1790         else
1791         {
1792             if ((ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN)
1793                 || ride->breakdown_reason_pending == BREAKDOWN_BRAKES_FAILURE
1794                 || ride->breakdown_reason_pending == BREAKDOWN_CONTROL_FAILURE)
1795             {
1796                 if (ride->breakdown_sound_modifier != 255)
1797                     ride->breakdown_sound_modifier++;
1798             }
1799 
1800             if (ride->breakdown_sound_modifier == 255)
1801             {
1802                 ride->music_tune_id = 255;
1803                 return true;
1804             }
1805         }
1806     }
1807     return false;
1808 }
1809 
1810 /**
1811  *
1812  *  Circus music is a sound effect, rather than music. Needs separate processing.
1813  */
CircusMusicUpdate(Ride * ride)1814 static void CircusMusicUpdate(Ride* ride)
1815 {
1816     Vehicle* vehicle = GetEntity<Vehicle>(ride->vehicles[0]);
1817     if (vehicle == nullptr || vehicle->status != Vehicle::Status::DoingCircusShow)
1818     {
1819         ride->music_position = 0;
1820         ride->music_tune_id = 255;
1821         return;
1822     }
1823 
1824     if (RideMusicBreakdownEffect(ride))
1825     {
1826         return;
1827     }
1828 
1829     CoordsXYZ rideCoords = ride->stations[0].GetStart().ToTileCentre();
1830 
1831     const auto sampleRate = RideMusicSampleRate(ride);
1832 
1833     OpenRCT2::RideAudio::UpdateMusicInstance(*ride, rideCoords, sampleRate);
1834 }
1835 
1836 /**
1837  *
1838  *  rct2: 0x006ABE85
1839  */
ride_music_update(Ride * ride)1840 static void ride_music_update(Ride* ride)
1841 {
1842     if (ride->type == RIDE_TYPE_CIRCUS)
1843     {
1844         CircusMusicUpdate(ride);
1845         return;
1846     }
1847 
1848     const auto& rtd = ride->GetRideTypeDescriptor();
1849     if (!rtd.HasFlag(RIDE_TYPE_FLAG_MUSIC_ON_DEFAULT) && !rtd.HasFlag(RIDE_TYPE_FLAG_ALLOW_MUSIC))
1850     {
1851         return;
1852     }
1853 
1854     if (ride->status != RideStatus::Open || !(ride->lifecycle_flags & RIDE_LIFECYCLE_MUSIC))
1855     {
1856         ride->music_tune_id = 255;
1857         return;
1858     }
1859 
1860     if (RideMusicBreakdownEffect(ride))
1861     {
1862         return;
1863     }
1864 
1865     // Select random tune from available tunes for a music style (of course only merry-go-rounds have more than one tune)
1866     if (ride->music_tune_id == 255)
1867     {
1868         auto& objManager = GetContext()->GetObjectManager();
1869         auto musicObj = static_cast<MusicObject*>(objManager.GetLoadedObject(ObjectType::Music, ride->music));
1870         if (musicObj != nullptr)
1871         {
1872             auto numTracks = musicObj->GetTrackCount();
1873             ride->music_tune_id = static_cast<uint8_t>(util_rand() % numTracks);
1874             ride->music_position = 0;
1875         }
1876         return;
1877     }
1878 
1879     CoordsXYZ rideCoords = ride->stations[0].GetStart().ToTileCentre();
1880 
1881     int32_t sampleRate = RideMusicSampleRate(ride);
1882 
1883     OpenRCT2::RideAudio::UpdateMusicInstance(*ride, rideCoords, sampleRate);
1884 }
1885 
1886 #pragma endregion
1887 
1888 #pragma region Measurement functions
1889 
1890 /**
1891  *
1892  *  rct2: 0x006B64F2
1893  */
ride_measurement_update(Ride & ride,RideMeasurement & measurement)1894 static void ride_measurement_update(Ride& ride, RideMeasurement& measurement)
1895 {
1896     if (measurement.vehicle_index >= std::size(ride.vehicles))
1897         return;
1898 
1899     auto vehicle = GetEntity<Vehicle>(ride.vehicles[measurement.vehicle_index]);
1900     if (vehicle == nullptr)
1901         return;
1902 
1903     if (measurement.flags & RIDE_MEASUREMENT_FLAG_UNLOADING)
1904     {
1905         if (vehicle->status != Vehicle::Status::Departing && vehicle->status != Vehicle::Status::TravellingCableLift)
1906             return;
1907 
1908         measurement.flags &= ~RIDE_MEASUREMENT_FLAG_UNLOADING;
1909         if (measurement.current_station == vehicle->current_station)
1910             measurement.current_item = 0;
1911     }
1912 
1913     if (vehicle->status == Vehicle::Status::UnloadingPassengers)
1914     {
1915         measurement.flags |= RIDE_MEASUREMENT_FLAG_UNLOADING;
1916         return;
1917     }
1918 
1919     auto trackType = vehicle->GetTrackType();
1920     if (trackType == TrackElemType::BlockBrakes || trackType == TrackElemType::CableLiftHill
1921         || trackType == TrackElemType::Up25ToFlat || trackType == TrackElemType::Up60ToFlat
1922         || trackType == TrackElemType::DiagUp25ToFlat || trackType == TrackElemType::DiagUp60ToFlat)
1923         if (vehicle->velocity == 0)
1924             return;
1925 
1926     if (measurement.current_item >= RideMeasurement::MAX_ITEMS)
1927         return;
1928 
1929     if (measurement.flags & RIDE_MEASUREMENT_FLAG_G_FORCES)
1930     {
1931         auto gForces = vehicle->GetGForces();
1932         gForces.VerticalG = std::clamp(gForces.VerticalG / 8, -127, 127);
1933         gForces.LateralG = std::clamp(gForces.LateralG / 8, -127, 127);
1934 
1935         if (gCurrentTicks & 1)
1936         {
1937             gForces.VerticalG = (gForces.VerticalG + measurement.vertical[measurement.current_item]) / 2;
1938             gForces.LateralG = (gForces.LateralG + measurement.lateral[measurement.current_item]) / 2;
1939         }
1940 
1941         measurement.vertical[measurement.current_item] = gForces.VerticalG & 0xFF;
1942         measurement.lateral[measurement.current_item] = gForces.LateralG & 0xFF;
1943     }
1944 
1945     auto velocity = std::min(std::abs((vehicle->velocity * 5) >> 16), 255);
1946     auto altitude = std::min(vehicle->z / 8, 255);
1947 
1948     if (gCurrentTicks & 1)
1949     {
1950         velocity = (velocity + measurement.velocity[measurement.current_item]) / 2;
1951         altitude = (altitude + measurement.altitude[measurement.current_item]) / 2;
1952     }
1953 
1954     measurement.velocity[measurement.current_item] = velocity & 0xFF;
1955     measurement.altitude[measurement.current_item] = altitude & 0xFF;
1956 
1957     if (gCurrentTicks & 1)
1958     {
1959         measurement.current_item++;
1960         measurement.num_items = std::max(measurement.num_items, measurement.current_item);
1961     }
1962 }
1963 
1964 /**
1965  *
1966  *  rct2: 0x006B6456
1967  */
ride_measurements_update()1968 void ride_measurements_update()
1969 {
1970     if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR)
1971         return;
1972 
1973     // For each ride measurement
1974     for (auto& ride : GetRideManager())
1975     {
1976         auto measurement = ride.measurement.get();
1977         if (measurement != nullptr && (ride.lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK) && ride.status != RideStatus::Simulating)
1978         {
1979             if (measurement->flags & RIDE_MEASUREMENT_FLAG_RUNNING)
1980             {
1981                 ride_measurement_update(ride, *measurement);
1982             }
1983             else
1984             {
1985                 // For each vehicle
1986                 for (int32_t j = 0; j < ride.num_vehicles; j++)
1987                 {
1988                     uint16_t vehicleSpriteIdx = ride.vehicles[j];
1989                     auto vehicle = GetEntity<Vehicle>(vehicleSpriteIdx);
1990                     if (vehicle != nullptr)
1991                     {
1992                         if (vehicle->status == Vehicle::Status::Departing
1993                             || vehicle->status == Vehicle::Status::TravellingCableLift)
1994                         {
1995                             measurement->vehicle_index = j;
1996                             measurement->current_station = vehicle->current_station;
1997                             measurement->flags |= RIDE_MEASUREMENT_FLAG_RUNNING;
1998                             measurement->flags &= ~RIDE_MEASUREMENT_FLAG_UNLOADING;
1999                             ride_measurement_update(ride, *measurement);
2000                             break;
2001                         }
2002                     }
2003                 }
2004             }
2005         }
2006     }
2007 }
2008 
2009 /**
2010  * If there are more than the threshold of allowed ride measurements, free the non-LRU one.
2011  */
ride_free_old_measurements()2012 static void ride_free_old_measurements()
2013 {
2014     size_t numRideMeasurements;
2015     do
2016     {
2017         Ride* lruRide{};
2018         numRideMeasurements = 0;
2019         for (auto& ride : GetRideManager())
2020         {
2021             if (ride.measurement != nullptr)
2022             {
2023                 if (lruRide == nullptr || ride.measurement->last_use_tick > lruRide->measurement->last_use_tick)
2024                 {
2025                     lruRide = &ride;
2026                 }
2027                 numRideMeasurements++;
2028             }
2029         }
2030         if (numRideMeasurements > MAX_RIDE_MEASUREMENTS && lruRide != nullptr)
2031         {
2032             lruRide->measurement = {};
2033             numRideMeasurements--;
2034         }
2035     } while (numRideMeasurements > MAX_RIDE_MEASUREMENTS);
2036 }
2037 
GetMeasurement()2038 std::pair<RideMeasurement*, OpenRCT2String> Ride::GetMeasurement()
2039 {
2040     const auto& rtd = GetRideTypeDescriptor();
2041 
2042     // Check if ride type supports data logging
2043     if (!rtd.HasFlag(RIDE_TYPE_FLAG_HAS_DATA_LOGGING))
2044     {
2045         return { nullptr, { STR_DATA_LOGGING_NOT_AVAILABLE_FOR_THIS_TYPE_OF_RIDE, {} } };
2046     }
2047 
2048     // Check if a measurement already exists for this ride
2049     if (measurement == nullptr)
2050     {
2051         measurement = std::make_unique<RideMeasurement>();
2052         if (rtd.HasFlag(RIDE_TYPE_FLAG_HAS_G_FORCES))
2053         {
2054             measurement->flags |= RIDE_MEASUREMENT_FLAG_G_FORCES;
2055         }
2056         ride_free_old_measurements();
2057         assert(measurement != nullptr);
2058     }
2059 
2060     measurement->last_use_tick = gCurrentTicks;
2061     if (measurement->flags & 1)
2062     {
2063         return { measurement.get(), { STR_EMPTY, {} } };
2064     }
2065 
2066     auto ft = Formatter();
2067     ft.Add<rct_string_id>(GetRideComponentName(GetRideTypeDescriptor().NameConvention.vehicle).singular);
2068     ft.Add<rct_string_id>(GetRideComponentName(GetRideTypeDescriptor().NameConvention.station).singular);
2069     return { nullptr, { STR_DATA_LOGGING_WILL_START_WHEN_NEXT_LEAVES, ft } };
2070 }
2071 
2072 #pragma endregion
2073 
2074 #pragma region Colour functions
2075 
ride_get_track_colour(Ride * ride,int32_t colourScheme)2076 TrackColour ride_get_track_colour(Ride* ride, int32_t colourScheme)
2077 {
2078     TrackColour result;
2079     result.main = ride->track_colour[colourScheme].main;
2080     result.additional = ride->track_colour[colourScheme].additional;
2081     result.supports = ride->track_colour[colourScheme].supports;
2082     return result;
2083 }
2084 
ride_get_vehicle_colour(Ride * ride,int32_t vehicleIndex)2085 vehicle_colour ride_get_vehicle_colour(Ride* ride, int32_t vehicleIndex)
2086 {
2087     vehicle_colour result;
2088 
2089     // Prevent indexing array out of bounds
2090     vehicleIndex = std::min<int32_t>(vehicleIndex, MAX_CARS_PER_TRAIN);
2091 
2092     result.main = ride->vehicle_colours[vehicleIndex].Body;
2093     result.additional_1 = ride->vehicle_colours[vehicleIndex].Trim;
2094     result.additional_2 = ride->vehicle_colours[vehicleIndex].Ternary;
2095     return result;
2096 }
2097 
ride_does_vehicle_colour_exist(ObjectEntryIndex subType,vehicle_colour * vehicleColour)2098 static bool ride_does_vehicle_colour_exist(ObjectEntryIndex subType, vehicle_colour* vehicleColour)
2099 {
2100     for (auto& ride : GetRideManager())
2101     {
2102         if (ride.subtype != subType)
2103             continue;
2104         if (ride.vehicle_colours[0].Body != vehicleColour->main)
2105             continue;
2106         return false;
2107     }
2108     return true;
2109 }
2110 
ride_get_unused_preset_vehicle_colour(ObjectEntryIndex subType)2111 int32_t ride_get_unused_preset_vehicle_colour(ObjectEntryIndex subType)
2112 {
2113     if (subType >= MAX_RIDE_OBJECTS)
2114     {
2115         return 0;
2116     }
2117     rct_ride_entry* rideEntry = get_ride_entry(subType);
2118     if (rideEntry == nullptr)
2119     {
2120         return 0;
2121     }
2122     vehicle_colour_preset_list* presetList = rideEntry->vehicle_preset_list;
2123     if (presetList->count == 0)
2124         return 0;
2125     if (presetList->count == 255)
2126         return 255;
2127 
2128     for (int32_t attempt = 0; attempt < 200; attempt++)
2129     {
2130         uint8_t numColourConfigurations = presetList->count;
2131         int32_t randomConfigIndex = util_rand() % numColourConfigurations;
2132         vehicle_colour* preset = &presetList->list[randomConfigIndex];
2133 
2134         if (ride_does_vehicle_colour_exist(subType, preset))
2135         {
2136             return randomConfigIndex;
2137         }
2138     }
2139     return 0;
2140 }
2141 
2142 /**
2143  *
2144  *  rct2: 0x006DE52C
2145  */
ride_set_vehicle_colours_to_random_preset(Ride * ride,uint8_t preset_index)2146 void ride_set_vehicle_colours_to_random_preset(Ride* ride, uint8_t preset_index)
2147 {
2148     rct_ride_entry* rideEntry = get_ride_entry(ride->subtype);
2149     vehicle_colour_preset_list* presetList = rideEntry->vehicle_preset_list;
2150 
2151     if (presetList->count != 0 && presetList->count != 255)
2152     {
2153         assert(preset_index < presetList->count);
2154 
2155         ride->colour_scheme_type = RIDE_COLOUR_SCHEME_ALL_SAME;
2156         vehicle_colour* preset = &presetList->list[preset_index];
2157         ride->vehicle_colours[0].Body = preset->main;
2158         ride->vehicle_colours[0].Trim = preset->additional_1;
2159         ride->vehicle_colours[0].Ternary = preset->additional_2;
2160     }
2161     else
2162     {
2163         ride->colour_scheme_type = RIDE_COLOUR_SCHEME_DIFFERENT_PER_TRAIN;
2164         uint32_t count = std::min(presetList->count, static_cast<uint8_t>(32));
2165         for (uint32_t i = 0; i < count; i++)
2166         {
2167             vehicle_colour* preset = &presetList->list[i];
2168             ride->vehicle_colours[i].Body = preset->main;
2169             ride->vehicle_colours[i].Trim = preset->additional_1;
2170             ride->vehicle_colours[i].Ternary = preset->additional_2;
2171         }
2172     }
2173 }
2174 
2175 #pragma endregion
2176 
2177 #pragma region Reachability
2178 
2179 /**
2180  *
2181  *  rct2: 0x006B7A5E
2182  */
ride_check_all_reachable()2183 void ride_check_all_reachable()
2184 {
2185     for (auto& ride : GetRideManager())
2186     {
2187         if (ride.connected_message_throttle != 0)
2188             ride.connected_message_throttle--;
2189         if (ride.status != RideStatus::Open || ride.connected_message_throttle != 0)
2190             continue;
2191 
2192         if (ride.GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IS_SHOP))
2193             ride_shop_connected(&ride);
2194         else
2195             ride_entrance_exit_connected(&ride);
2196     }
2197 }
2198 
2199 /**
2200  *
2201  *  rct2: 0x006B7C59
2202  * @return true if the coordinate is reachable or has no entrance, false otherwise
2203  */
ride_entrance_exit_is_reachable(const TileCoordsXYZD & coordinates)2204 static bool ride_entrance_exit_is_reachable(const TileCoordsXYZD& coordinates)
2205 {
2206     if (coordinates.IsNull())
2207         return true;
2208 
2209     TileCoordsXYZ loc{ coordinates.x, coordinates.y, coordinates.z };
2210     loc -= TileDirectionDelta[coordinates.direction];
2211 
2212     return map_coord_is_connected(loc, coordinates.direction);
2213 }
2214 
ride_entrance_exit_connected(Ride * ride)2215 static void ride_entrance_exit_connected(Ride* ride)
2216 {
2217     for (int32_t i = 0; i < MAX_STATIONS; ++i)
2218     {
2219         auto station_start = ride->stations[i].Start;
2220         auto entrance = ride_get_entrance_location(ride, i);
2221         auto exit = ride_get_exit_location(ride, i);
2222 
2223         if (station_start.IsNull())
2224             continue;
2225         if (!entrance.IsNull() && !ride_entrance_exit_is_reachable(entrance))
2226         {
2227             // name of ride is parameter of the format string
2228             Formatter ft;
2229             ride->FormatNameTo(ft);
2230             if (gConfigNotifications.ride_warnings)
2231             {
2232                 News::AddItemToQueue(News::ItemType::Ride, STR_ENTRANCE_NOT_CONNECTED, EnumValue(ride->id), ft);
2233             }
2234             ride->connected_message_throttle = 3;
2235         }
2236 
2237         if (!exit.IsNull() && !ride_entrance_exit_is_reachable(exit))
2238         {
2239             // name of ride is parameter of the format string
2240             Formatter ft;
2241             ride->FormatNameTo(ft);
2242             if (gConfigNotifications.ride_warnings)
2243             {
2244                 News::AddItemToQueue(News::ItemType::Ride, STR_EXIT_NOT_CONNECTED, EnumValue(ride->id), ft);
2245             }
2246             ride->connected_message_throttle = 3;
2247         }
2248     }
2249 }
2250 
ride_shop_connected(Ride * ride)2251 static void ride_shop_connected(Ride* ride)
2252 {
2253     auto shopLoc = TileCoordsXY(ride->stations[0].Start);
2254     if (shopLoc.IsNull())
2255         return;
2256 
2257     TrackElement* trackElement = nullptr;
2258     TileElement* tileElement = map_get_first_element_at(shopLoc);
2259     do
2260     {
2261         if (tileElement == nullptr)
2262             break;
2263         if (tileElement->GetType() == TILE_ELEMENT_TYPE_TRACK && tileElement->AsTrack()->GetRideIndex() == ride->id)
2264         {
2265             trackElement = tileElement->AsTrack();
2266             break;
2267         }
2268     } while (!(tileElement++)->IsLastForTile());
2269 
2270     if (trackElement == nullptr)
2271         return;
2272 
2273     auto track_type = trackElement->GetTrackType();
2274     ride = get_ride(trackElement->GetRideIndex());
2275     if (ride == nullptr)
2276     {
2277         return;
2278     }
2279 
2280     const auto& ted = GetTrackElementDescriptor(track_type);
2281     uint8_t entrance_directions = ted.SequenceProperties[0] & 0xF;
2282     uint8_t tile_direction = trackElement->GetDirection();
2283     entrance_directions = Numerics::rol4(entrance_directions, tile_direction);
2284 
2285     // Now each bit in entrance_directions stands for an entrance direction to check
2286     if (entrance_directions == 0)
2287         return;
2288 
2289     for (auto count = 0; entrance_directions != 0; count++)
2290     {
2291         if (!(entrance_directions & 1))
2292         {
2293             entrance_directions >>= 1;
2294             continue;
2295         }
2296         entrance_directions >>= 1;
2297 
2298         // Flip direction north<->south, east<->west
2299         uint8_t face_direction = direction_reverse(count);
2300 
2301         int32_t y2 = shopLoc.y - TileDirectionDelta[face_direction].y;
2302         int32_t x2 = shopLoc.x - TileDirectionDelta[face_direction].x;
2303 
2304         if (map_coord_is_connected({ x2, y2, tileElement->base_height }, face_direction))
2305             return;
2306     }
2307 
2308     // Name of ride is parameter of the format string
2309     if (gConfigNotifications.ride_warnings)
2310     {
2311         Formatter ft;
2312         ride->FormatNameTo(ft);
2313         News::AddItemToQueue(News::ItemType::Ride, STR_ENTRANCE_NOT_CONNECTED, EnumValue(ride->id), ft);
2314     }
2315 
2316     ride->connected_message_throttle = 3;
2317 }
2318 
2319 #pragma endregion
2320 
2321 #pragma region Interface
2322 
ride_track_set_map_tooltip(TileElement * tileElement)2323 static void ride_track_set_map_tooltip(TileElement* tileElement)
2324 {
2325     auto rideIndex = tileElement->AsTrack()->GetRideIndex();
2326     auto ride = get_ride(rideIndex);
2327     if (ride != nullptr)
2328     {
2329         auto ft = Formatter();
2330         ft.Add<rct_string_id>(STR_RIDE_MAP_TIP);
2331         ride->FormatNameTo(ft);
2332         ride->FormatStatusTo(ft);
2333         auto intent = Intent(INTENT_ACTION_SET_MAP_TOOLTIP);
2334         intent.putExtra(INTENT_EXTRA_FORMATTER, &ft);
2335         context_broadcast_intent(&intent);
2336     }
2337 }
2338 
ride_queue_banner_set_map_tooltip(TileElement * tileElement)2339 static void ride_queue_banner_set_map_tooltip(TileElement* tileElement)
2340 {
2341     auto rideIndex = tileElement->AsPath()->GetRideIndex();
2342     auto ride = get_ride(rideIndex);
2343     if (ride != nullptr)
2344     {
2345         auto ft = Formatter();
2346         ft.Add<rct_string_id>(STR_RIDE_MAP_TIP);
2347         ride->FormatNameTo(ft);
2348         ride->FormatStatusTo(ft);
2349         auto intent = Intent(INTENT_ACTION_SET_MAP_TOOLTIP);
2350         intent.putExtra(INTENT_EXTRA_FORMATTER, &ft);
2351         context_broadcast_intent(&intent);
2352     }
2353 }
2354 
ride_station_set_map_tooltip(TileElement * tileElement)2355 static void ride_station_set_map_tooltip(TileElement* tileElement)
2356 {
2357     auto rideIndex = tileElement->AsTrack()->GetRideIndex();
2358     auto ride = get_ride(rideIndex);
2359     if (ride != nullptr)
2360     {
2361         auto stationIndex = tileElement->AsTrack()->GetStationIndex();
2362         for (int32_t i = stationIndex; i >= 0; i--)
2363             if (ride->stations[i].Start.IsNull())
2364                 stationIndex--;
2365 
2366         auto ft = Formatter();
2367         ft.Add<rct_string_id>(STR_RIDE_MAP_TIP);
2368         ft.Add<rct_string_id>(ride->num_stations <= 1 ? STR_RIDE_STATION : STR_RIDE_STATION_X);
2369         ride->FormatNameTo(ft);
2370         ft.Add<rct_string_id>(GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.station).capitalised);
2371         ft.Add<uint16_t>(stationIndex + 1);
2372         ride->FormatStatusTo(ft);
2373         auto intent = Intent(INTENT_ACTION_SET_MAP_TOOLTIP);
2374         intent.putExtra(INTENT_EXTRA_FORMATTER, &ft);
2375         context_broadcast_intent(&intent);
2376     }
2377 }
2378 
ride_entrance_set_map_tooltip(TileElement * tileElement)2379 static void ride_entrance_set_map_tooltip(TileElement* tileElement)
2380 {
2381     auto rideIndex = tileElement->AsEntrance()->GetRideIndex();
2382     auto ride = get_ride(rideIndex);
2383     if (ride != nullptr)
2384     {
2385         // Get the station
2386         auto stationIndex = tileElement->AsEntrance()->GetStationIndex();
2387         for (int32_t i = stationIndex; i >= 0; i--)
2388             if (ride->stations[i].Start.IsNull())
2389                 stationIndex--;
2390 
2391         if (tileElement->AsEntrance()->GetEntranceType() == ENTRANCE_TYPE_RIDE_ENTRANCE)
2392         {
2393             // Get the queue length
2394             int32_t queueLength = 0;
2395             if (!ride_get_entrance_location(ride, stationIndex).IsNull())
2396                 queueLength = ride->stations[stationIndex].QueueLength;
2397 
2398             auto ft = Formatter();
2399             ft.Add<rct_string_id>(STR_RIDE_MAP_TIP);
2400             ft.Add<rct_string_id>(ride->num_stations <= 1 ? STR_RIDE_ENTRANCE : STR_RIDE_STATION_X_ENTRANCE);
2401             ride->FormatNameTo(ft);
2402 
2403             // String IDs have an extra pop16 for some reason
2404             ft.Increment(sizeof(uint16_t));
2405 
2406             ft.Add<uint16_t>(stationIndex + 1);
2407             if (queueLength == 0)
2408             {
2409                 ft.Add<rct_string_id>(STR_QUEUE_EMPTY);
2410             }
2411             else if (queueLength == 1)
2412             {
2413                 ft.Add<rct_string_id>(STR_QUEUE_ONE_PERSON);
2414             }
2415             else
2416             {
2417                 ft.Add<rct_string_id>(STR_QUEUE_PEOPLE);
2418             }
2419             ft.Add<uint16_t>(queueLength);
2420             auto intent = Intent(INTENT_ACTION_SET_MAP_TOOLTIP);
2421             intent.putExtra(INTENT_EXTRA_FORMATTER, &ft);
2422             context_broadcast_intent(&intent);
2423         }
2424         else
2425         {
2426             // Get the station
2427             stationIndex = tileElement->AsEntrance()->GetStationIndex();
2428             for (int32_t i = stationIndex; i >= 0; i--)
2429                 if (ride->stations[i].Start.IsNull())
2430                     stationIndex--;
2431 
2432             auto ft = Formatter();
2433             ft.Add<rct_string_id>(ride->num_stations <= 1 ? STR_RIDE_EXIT : STR_RIDE_STATION_X_EXIT);
2434             ride->FormatNameTo(ft);
2435 
2436             // String IDs have an extra pop16 for some reason
2437             ft.Increment(sizeof(uint16_t));
2438 
2439             ft.Add<uint16_t>(stationIndex + 1);
2440             auto intent = Intent(INTENT_ACTION_SET_MAP_TOOLTIP);
2441             intent.putExtra(INTENT_EXTRA_FORMATTER, &ft);
2442             context_broadcast_intent(&intent);
2443         }
2444     }
2445 }
2446 
ride_set_map_tooltip(TileElement * tileElement)2447 void ride_set_map_tooltip(TileElement* tileElement)
2448 {
2449     if (tileElement->GetType() == TILE_ELEMENT_TYPE_ENTRANCE)
2450     {
2451         ride_entrance_set_map_tooltip(tileElement);
2452     }
2453     else if (tileElement->GetType() == TILE_ELEMENT_TYPE_TRACK)
2454     {
2455         if (tileElement->AsTrack()->IsStation())
2456         {
2457             ride_station_set_map_tooltip(tileElement);
2458         }
2459         else
2460         {
2461             ride_track_set_map_tooltip(tileElement);
2462         }
2463     }
2464     else if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH)
2465     {
2466         ride_queue_banner_set_map_tooltip(tileElement);
2467     }
2468 }
2469 
2470 #pragma endregion
2471 
2472 /**
2473  *
2474  *  rct2: 0x006B4CC1
2475  */
ride_mode_check_valid_station_numbers(Ride * ride)2476 static StationIndex ride_mode_check_valid_station_numbers(Ride* ride)
2477 {
2478     uint16_t numStations = 0;
2479     for (StationIndex stationIndex = 0; stationIndex < MAX_STATIONS; ++stationIndex)
2480     {
2481         if (!ride->stations[stationIndex].Start.IsNull())
2482         {
2483             numStations++;
2484         }
2485     }
2486 
2487     switch (ride->mode)
2488     {
2489         case RideMode::ReverseInclineLaunchedShuttle:
2490         case RideMode::PoweredLaunchPasstrough:
2491         case RideMode::PoweredLaunch:
2492         case RideMode::LimPoweredLaunch:
2493             if (numStations <= 1)
2494                 return 1;
2495             gGameCommandErrorText = STR_UNABLE_TO_OPERATE_WITH_MORE_THAN_ONE_STATION_IN_THIS_MODE;
2496             return 0;
2497         case RideMode::Shuttle:
2498             if (numStations >= 2)
2499                 return 1;
2500             gGameCommandErrorText = STR_UNABLE_TO_OPERATE_WITH_LESS_THAN_TWO_STATIONS_IN_THIS_MODE;
2501             return 0;
2502         default:
2503         {
2504             // This is workaround for multiple compilation errors of type "enumeration value ‘RIDE_MODE_*' not handled
2505             // in switch [-Werror=switch]"
2506         }
2507     }
2508 
2509     if (ride->type == RIDE_TYPE_GO_KARTS || ride->type == RIDE_TYPE_MINI_GOLF)
2510     {
2511         if (numStations <= 1)
2512             return 1;
2513         gGameCommandErrorText = STR_UNABLE_TO_OPERATE_WITH_MORE_THAN_ONE_STATION_IN_THIS_MODE;
2514         return 0;
2515     }
2516 
2517     return 1;
2518 }
2519 
2520 /**
2521  * returns stationIndex of first station on success
2522  * STATION_INDEX_NULL on failure.
2523  */
ride_mode_check_station_present(Ride * ride)2524 static StationIndex ride_mode_check_station_present(Ride* ride)
2525 {
2526     auto stationIndex = ride_get_first_valid_station_start(ride);
2527 
2528     if (stationIndex == STATION_INDEX_NULL)
2529     {
2530         gGameCommandErrorText = STR_NOT_YET_CONSTRUCTED;
2531         if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_NO_TRACK))
2532             return STATION_INDEX_NULL;
2533 
2534         if (ride->type == RIDE_TYPE_MAZE)
2535             return STATION_INDEX_NULL;
2536 
2537         gGameCommandErrorText = STR_REQUIRES_A_STATION_PLATFORM;
2538         return STATION_INDEX_NULL;
2539     }
2540 
2541     return stationIndex;
2542 }
2543 
2544 /**
2545  *
2546  *  rct2: 0x006B5872
2547  */
ride_check_for_entrance_exit(ride_id_t rideIndex)2548 static int32_t ride_check_for_entrance_exit(ride_id_t rideIndex)
2549 {
2550     auto ride = get_ride(rideIndex);
2551     if (ride == nullptr)
2552         return 0;
2553 
2554     if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IS_SHOP))
2555         return 1;
2556 
2557     uint8_t entrance = 0;
2558     uint8_t exit = 0;
2559     for (int32_t i = 0; i < MAX_STATIONS; i++)
2560     {
2561         if (ride->stations[i].Start.IsNull())
2562             continue;
2563 
2564         if (!ride_get_entrance_location(ride, i).IsNull())
2565         {
2566             entrance = 1;
2567         }
2568 
2569         if (!ride_get_exit_location(ride, i).IsNull())
2570         {
2571             exit = 1;
2572         }
2573 
2574         // If station start and no entrance/exit
2575         // Sets same error message as no entrance
2576         if (ride_get_exit_location(ride, i).IsNull() && ride_get_entrance_location(ride, i).IsNull())
2577         {
2578             entrance = 0;
2579             break;
2580         }
2581     }
2582 
2583     if (entrance == 0)
2584     {
2585         gGameCommandErrorText = STR_ENTRANCE_NOT_YET_BUILT;
2586         return 0;
2587     }
2588 
2589     if (exit == 0)
2590     {
2591         gGameCommandErrorText = STR_EXIT_NOT_YET_BUILT;
2592         return 0;
2593     }
2594 
2595     return 1;
2596 }
2597 
2598 /**
2599  * Calls footpath_chain_ride_queue for all entrances of the ride
2600  *  rct2: 0x006B5952
2601  */
ChainQueues() const2602 void Ride::ChainQueues() const
2603 {
2604     for (int32_t i = 0; i < MAX_STATIONS; i++)
2605     {
2606         auto location = ride_get_entrance_location(this, i);
2607         if (location.IsNull())
2608             continue;
2609 
2610         auto mapLocation = location.ToCoordsXYZ();
2611 
2612         // This will fire for every entrance on this x, y and z, regardless whether that actually belongs to
2613         // the ride or not.
2614         TileElement* tileElement = map_get_first_element_at(location);
2615         if (tileElement != nullptr)
2616         {
2617             do
2618             {
2619                 if (tileElement->GetType() != TILE_ELEMENT_TYPE_ENTRANCE)
2620                     continue;
2621                 if (tileElement->GetBaseZ() != mapLocation.z)
2622                     continue;
2623 
2624                 int32_t direction = tileElement->GetDirection();
2625                 footpath_chain_ride_queue(id, i, mapLocation, tileElement, direction_reverse(direction));
2626             } while (!(tileElement++)->IsLastForTile());
2627         }
2628     }
2629 }
2630 
2631 /**
2632  *
2633  *  rct2: 0x006D3319
2634  */
ride_check_block_brakes(CoordsXYE * input,CoordsXYE * output)2635 static int32_t ride_check_block_brakes(CoordsXYE* input, CoordsXYE* output)
2636 {
2637     ride_id_t rideIndex = input->element->AsTrack()->GetRideIndex();
2638     rct_window* w = window_find_by_class(WC_RIDE_CONSTRUCTION);
2639     if (w != nullptr && _rideConstructionState != RideConstructionState::State0 && _currentRideIndex == rideIndex)
2640         ride_construction_invalidate_current_track();
2641 
2642     track_circuit_iterator it;
2643     track_circuit_iterator_begin(&it, *input);
2644     while (track_circuit_iterator_next(&it))
2645     {
2646         if (it.current.element->AsTrack()->GetTrackType() == TrackElemType::BlockBrakes)
2647         {
2648             auto type = it.last.element->AsTrack()->GetTrackType();
2649             if (type == TrackElemType::EndStation)
2650             {
2651                 gGameCommandErrorText = STR_BLOCK_BRAKES_CANNOT_BE_USED_DIRECTLY_AFTER_STATION;
2652                 *output = it.current;
2653                 return 0;
2654             }
2655             if (type == TrackElemType::BlockBrakes)
2656             {
2657                 gGameCommandErrorText = STR_BLOCK_BRAKES_CANNOT_BE_USED_DIRECTLY_AFTER_EACH_OTHER;
2658                 *output = it.current;
2659                 return 0;
2660             }
2661             if (it.last.element->AsTrack()->HasChain() && type != TrackElemType::LeftCurvedLiftHill
2662                 && type != TrackElemType::RightCurvedLiftHill)
2663             {
2664                 gGameCommandErrorText = STR_BLOCK_BRAKES_CANNOT_BE_USED_DIRECTLY_AFTER_THE_TOP_OF_THIS_LIFT_HILL;
2665                 *output = it.current;
2666                 return 0;
2667             }
2668         }
2669     }
2670     if (!it.looped)
2671     {
2672         // Not sure why this is the case...
2673         gGameCommandErrorText = STR_BLOCK_BRAKES_CANNOT_BE_USED_DIRECTLY_AFTER_STATION;
2674         *output = it.last;
2675         return 0;
2676     }
2677 
2678     return 1;
2679 }
2680 
2681 /**
2682  * Iterates along the track until an inversion (loop, corkscrew, barrel roll etc.) track piece is reached.
2683  * @param input The start track element and position.
2684  * @param output The first track element and position which is classified as an inversion.
2685  * @returns true if an inversion track piece is found, otherwise false.
2686  *  rct2: 0x006CB149
2687  */
ride_check_track_contains_inversions(CoordsXYE * input,CoordsXYE * output)2688 static bool ride_check_track_contains_inversions(CoordsXYE* input, CoordsXYE* output)
2689 {
2690     if (input->element == nullptr)
2691         return false;
2692 
2693     const auto* trackElement = input->element->AsTrack();
2694     if (trackElement == nullptr)
2695         return false;
2696 
2697     ride_id_t rideIndex = trackElement->GetRideIndex();
2698     auto ride = get_ride(rideIndex);
2699     if (ride != nullptr && ride->type == RIDE_TYPE_MAZE)
2700         return true;
2701 
2702     rct_window* w = window_find_by_class(WC_RIDE_CONSTRUCTION);
2703     if (w != nullptr && _rideConstructionState != RideConstructionState::State0 && rideIndex == _currentRideIndex)
2704     {
2705         ride_construction_invalidate_current_track();
2706     }
2707 
2708     bool moveSlowIt = true;
2709     track_circuit_iterator it, slowIt;
2710     track_circuit_iterator_begin(&it, *input);
2711     slowIt = it;
2712 
2713     while (track_circuit_iterator_next(&it))
2714     {
2715         auto trackType = it.current.element->AsTrack()->GetTrackType();
2716         const auto& ted = GetTrackElementDescriptor(trackType);
2717         if (ted.Flags & TRACK_ELEM_FLAG_INVERSION_TO_NORMAL)
2718         {
2719             *output = it.current;
2720             return true;
2721         }
2722 
2723         // Prevents infinite loops
2724         moveSlowIt = !moveSlowIt;
2725         if (moveSlowIt)
2726         {
2727             track_circuit_iterator_next(&slowIt);
2728             if (track_circuit_iterators_match(&it, &slowIt))
2729             {
2730                 return false;
2731             }
2732         }
2733     }
2734     return false;
2735 }
2736 
2737 /**
2738  * Iterates along the track until a banked track piece is reached.
2739  * @param input The start track element and position.
2740  * @param output The first track element and position which is banked.
2741  * @returns true if a banked track piece is found, otherwise false.
2742  *  rct2: 0x006CB1D3
2743  */
ride_check_track_contains_banked(CoordsXYE * input,CoordsXYE * output)2744 static bool ride_check_track_contains_banked(CoordsXYE* input, CoordsXYE* output)
2745 {
2746     if (input->element == nullptr)
2747         return false;
2748 
2749     const auto* trackElement = input->element->AsTrack();
2750     if (trackElement == nullptr)
2751         return false;
2752 
2753     auto rideIndex = trackElement->GetRideIndex();
2754     auto ride = get_ride(rideIndex);
2755     if (ride == nullptr)
2756         return false;
2757 
2758     if (ride->type == RIDE_TYPE_MAZE)
2759         return true;
2760 
2761     rct_window* w = window_find_by_class(WC_RIDE_CONSTRUCTION);
2762     if (w != nullptr && _rideConstructionState != RideConstructionState::State0 && rideIndex == _currentRideIndex)
2763     {
2764         ride_construction_invalidate_current_track();
2765     }
2766 
2767     bool moveSlowIt = true;
2768     track_circuit_iterator it, slowIt;
2769     track_circuit_iterator_begin(&it, *input);
2770     slowIt = it;
2771 
2772     while (track_circuit_iterator_next(&it))
2773     {
2774         auto trackType = output->element->AsTrack()->GetTrackType();
2775         const auto& ted = GetTrackElementDescriptor(trackType);
2776         if (ted.Flags & TRACK_ELEM_FLAG_BANKED)
2777         {
2778             *output = it.current;
2779             return true;
2780         }
2781 
2782         // Prevents infinite loops
2783         moveSlowIt = !moveSlowIt;
2784         if (moveSlowIt)
2785         {
2786             track_circuit_iterator_next(&slowIt);
2787             if (track_circuit_iterators_match(&it, &slowIt))
2788             {
2789                 return false;
2790             }
2791         }
2792     }
2793     return false;
2794 }
2795 
2796 /**
2797  *
2798  *  rct2: 0x006CB25D
2799  */
ride_check_station_length(CoordsXYE * input,CoordsXYE * output)2800 static int32_t ride_check_station_length(CoordsXYE* input, CoordsXYE* output)
2801 {
2802     rct_window* w = window_find_by_class(WC_RIDE_CONSTRUCTION);
2803     if (w != nullptr && _rideConstructionState != RideConstructionState::State0
2804         && _currentRideIndex == input->element->AsTrack()->GetRideIndex())
2805     {
2806         ride_construction_invalidate_current_track();
2807     }
2808 
2809     output->x = input->x;
2810     output->y = input->y;
2811     output->element = input->element;
2812     track_begin_end trackBeginEnd;
2813     while (track_block_get_previous(*output, &trackBeginEnd))
2814     {
2815         output->x = trackBeginEnd.begin_x;
2816         output->y = trackBeginEnd.begin_y;
2817         output->element = trackBeginEnd.begin_element;
2818     }
2819 
2820     int32_t num_station_elements = 0;
2821     CoordsXYE last_good_station = *output;
2822 
2823     do
2824     {
2825         const auto& ted = GetTrackElementDescriptor(output->element->AsTrack()->GetTrackType());
2826         if (ted.SequenceProperties[0] & TRACK_SEQUENCE_FLAG_ORIGIN)
2827         {
2828             num_station_elements++;
2829             last_good_station = *output;
2830         }
2831         else
2832         {
2833             if (num_station_elements == 0)
2834                 continue;
2835             if (num_station_elements == 1)
2836             {
2837                 return 0;
2838             }
2839             num_station_elements = 0;
2840         }
2841     } while (track_block_get_next(output, output, nullptr, nullptr));
2842 
2843     // Prevent returning a pointer to a map element with no track.
2844     *output = last_good_station;
2845     if (num_station_elements == 1)
2846         return 0;
2847 
2848     return 1;
2849 }
2850 
2851 /**
2852  *
2853  *  rct2: 0x006CB2DA
2854  */
ride_check_start_and_end_is_station(CoordsXYE * input)2855 static bool ride_check_start_and_end_is_station(CoordsXYE* input)
2856 {
2857     CoordsXYE trackBack, trackFront;
2858 
2859     ride_id_t rideIndex = input->element->AsTrack()->GetRideIndex();
2860     auto ride = get_ride(rideIndex);
2861     if (ride == nullptr)
2862         return false;
2863 
2864     auto w = window_find_by_class(WC_RIDE_CONSTRUCTION);
2865     if (w != nullptr && _rideConstructionState != RideConstructionState::State0 && rideIndex == _currentRideIndex)
2866     {
2867         ride_construction_invalidate_current_track();
2868     }
2869 
2870     // Check back of the track
2871     track_get_back(input, &trackBack);
2872     auto trackType = trackBack.element->AsTrack()->GetTrackType();
2873     const auto* ted = &GetTrackElementDescriptor(trackType);
2874     if (!(ted->SequenceProperties[0] & TRACK_SEQUENCE_FLAG_ORIGIN))
2875     {
2876         return false;
2877     }
2878     ride->ChairliftBullwheelLocation[0] = TileCoordsXYZ{ CoordsXYZ{ trackBack.x, trackBack.y, trackBack.element->GetBaseZ() } };
2879 
2880     // Check front of the track
2881     track_get_front(input, &trackFront);
2882     trackType = trackFront.element->AsTrack()->GetTrackType();
2883     ted = &GetTrackElementDescriptor(trackType);
2884     if (!(ted->SequenceProperties[0] & TRACK_SEQUENCE_FLAG_ORIGIN))
2885     {
2886         return false;
2887     }
2888     ride->ChairliftBullwheelLocation[1] = TileCoordsXYZ{ CoordsXYZ{ trackFront.x, trackFront.y,
2889                                                                     trackFront.element->GetBaseZ() } };
2890     return true;
2891 }
2892 
2893 /**
2894  * Sets the position and direction of the returning point on the track of a boat hire ride. This will either be the end of the
2895  * station or the last track piece from the end of the direction.
2896  *  rct2: 0x006B4D39
2897  */
ride_set_boat_hire_return_point(Ride * ride,CoordsXYE * startElement)2898 static void ride_set_boat_hire_return_point(Ride* ride, CoordsXYE* startElement)
2899 {
2900     int32_t trackType = -1;
2901     auto returnPos = *startElement;
2902     int32_t startX = returnPos.x;
2903     int32_t startY = returnPos.y;
2904     track_begin_end trackBeginEnd;
2905     while (track_block_get_previous(returnPos, &trackBeginEnd))
2906     {
2907         // If previous track is back to the starting x, y, then break loop (otherwise possible infinite loop)
2908         if (trackType != -1 && startX == trackBeginEnd.begin_x && startY == trackBeginEnd.begin_y)
2909             break;
2910 
2911         auto trackCoords = CoordsXYZ{ trackBeginEnd.begin_x, trackBeginEnd.begin_y, trackBeginEnd.begin_z };
2912         int32_t direction = trackBeginEnd.begin_direction;
2913         trackType = trackBeginEnd.begin_element->AsTrack()->GetTrackType();
2914         auto newCoords = GetTrackElementOriginAndApplyChanges(
2915             { trackCoords, static_cast<Direction>(direction) }, trackType, 0, &returnPos.element, 0);
2916         returnPos = newCoords.has_value() ? CoordsXYE{ newCoords.value(), returnPos.element }
2917                                           : CoordsXYE{ trackCoords, returnPos.element };
2918     };
2919 
2920     trackType = returnPos.element->AsTrack()->GetTrackType();
2921     const auto& ted = GetTrackElementDescriptor(trackType);
2922     int32_t elementReturnDirection = ted.Coordinates.rotation_begin;
2923     ride->boat_hire_return_direction = returnPos.element->GetDirectionWithOffset(elementReturnDirection);
2924     ride->boat_hire_return_position = TileCoordsXY{ returnPos };
2925 }
2926 
2927 /**
2928  *
2929  *  rct2: 0x006B4D39
2930  */
ride_set_maze_entrance_exit_points(Ride * ride)2931 static void ride_set_maze_entrance_exit_points(Ride* ride)
2932 {
2933     // Needs room for an entrance and an exit per station, plus one position for the list terminator.
2934     TileCoordsXYZD positions[(MAX_STATIONS * 2) + 1];
2935 
2936     // Create a list of all the entrance and exit positions
2937     TileCoordsXYZD* position = positions;
2938     for (int32_t i = 0; i < MAX_STATIONS; i++)
2939     {
2940         const auto entrance = ride_get_entrance_location(ride, i);
2941         const auto exit = ride_get_exit_location(ride, i);
2942 
2943         if (!entrance.IsNull())
2944         {
2945             *position++ = entrance;
2946         }
2947         if (!exit.IsNull())
2948         {
2949             *position++ = exit;
2950         }
2951     }
2952     (*position++).SetNull();
2953 
2954     // Enumerate entrance and exit positions
2955     for (position = positions; !(*position).IsNull(); position++)
2956     {
2957         auto entranceExitMapPos = position->ToCoordsXYZ();
2958 
2959         TileElement* tileElement = map_get_first_element_at(*position);
2960         do
2961         {
2962             if (tileElement == nullptr)
2963                 break;
2964             if (tileElement->GetType() != TILE_ELEMENT_TYPE_ENTRANCE)
2965                 continue;
2966             if (tileElement->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_RIDE_ENTRANCE
2967                 && tileElement->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_RIDE_EXIT)
2968             {
2969                 continue;
2970             }
2971             if (tileElement->GetBaseZ() != entranceExitMapPos.z)
2972                 continue;
2973 
2974             maze_entrance_hedge_removal({ entranceExitMapPos, tileElement });
2975         } while (!(tileElement++)->IsLastForTile());
2976     }
2977 }
2978 
2979 /**
2980  * Opens all block brakes of a ride.
2981  *  rct2: 0x006B4E6B
2982  */
RideOpenBlockBrakes(CoordsXYE * startElement)2983 static void RideOpenBlockBrakes(CoordsXYE* startElement)
2984 {
2985     CoordsXYE currentElement = *startElement;
2986     do
2987     {
2988         auto trackType = currentElement.element->AsTrack()->GetTrackType();
2989         switch (trackType)
2990         {
2991             case TrackElemType::EndStation:
2992             case TrackElemType::CableLiftHill:
2993             case TrackElemType::Up25ToFlat:
2994             case TrackElemType::Up60ToFlat:
2995             case TrackElemType::DiagUp25ToFlat:
2996             case TrackElemType::DiagUp60ToFlat:
2997             case TrackElemType::BlockBrakes:
2998                 currentElement.element->AsTrack()->SetBlockBrakeClosed(false);
2999                 break;
3000         }
3001     } while (track_block_get_next(&currentElement, &currentElement, nullptr, nullptr)
3002              && currentElement.element != startElement->element);
3003 }
3004 
3005 /**
3006  *
3007  *  rct2: 0x006B4D26
3008  */
ride_set_start_finish_points(ride_id_t rideIndex,CoordsXYE * startElement)3009 static void ride_set_start_finish_points(ride_id_t rideIndex, CoordsXYE* startElement)
3010 {
3011     auto ride = get_ride(rideIndex);
3012     if (ride == nullptr)
3013         return;
3014 
3015     switch (ride->type)
3016     {
3017         case RIDE_TYPE_BOAT_HIRE:
3018             ride_set_boat_hire_return_point(ride, startElement);
3019             break;
3020         case RIDE_TYPE_MAZE:
3021             ride_set_maze_entrance_exit_points(ride);
3022             break;
3023     }
3024 
3025     if (ride->IsBlockSectioned() && !(ride->lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK))
3026     {
3027         RideOpenBlockBrakes(startElement);
3028     }
3029 }
3030 
3031 /**
3032  *
3033  *  rct2: 0x0069ED9E
3034  */
count_free_misc_sprite_slots()3035 static int32_t count_free_misc_sprite_slots()
3036 {
3037     int32_t miscSpriteCount = GetMiscEntityCount();
3038     int32_t remainingSpriteCount = GetNumFreeEntities();
3039     return std::max(0, miscSpriteCount + remainingSpriteCount - 300);
3040 }
3041 
3042 static constexpr const CoordsXY word_9A3AB4[4] = {
3043     { 0, 0 },
3044     { 0, -96 },
3045     { -96, -96 },
3046     { -96, 0 },
3047 };
3048 
3049 // clang-format off
3050 static constexpr const CoordsXY word_9A2A60[] = {
3051     { 0, 16 },
3052     { 16, 31 },
3053     { 31, 16 },
3054     { 16, 0 },
3055     { 16, 16 },
3056     { 64, 64 },
3057     { 64, -32 },
3058     { -32, -32 },
3059     { -32, 64 },
3060 };
3061 // clang-format on
3062 
3063 /**
3064  *
3065  *  rct2: 0x006DD90D
3066  */
vehicle_create_car(ride_id_t rideIndex,int32_t vehicleEntryIndex,int32_t carIndex,int32_t vehicleIndex,const CoordsXYZ & carPosition,int32_t * remainingDistance,TrackElement * trackElement)3067 static Vehicle* vehicle_create_car(
3068     ride_id_t rideIndex, int32_t vehicleEntryIndex, int32_t carIndex, int32_t vehicleIndex, const CoordsXYZ& carPosition,
3069     int32_t* remainingDistance, TrackElement* trackElement)
3070 {
3071     if (trackElement == nullptr)
3072         return nullptr;
3073 
3074     auto ride = get_ride(rideIndex);
3075     if (ride == nullptr)
3076         return nullptr;
3077 
3078     auto rideEntry = ride->GetRideEntry();
3079     if (rideEntry == nullptr)
3080         return nullptr;
3081 
3082     auto vehicleEntry = &rideEntry->vehicles[vehicleEntryIndex];
3083     auto vehicle = CreateEntity<Vehicle>();
3084     if (vehicle == nullptr)
3085         return nullptr;
3086 
3087     vehicle->ride = rideIndex;
3088     vehicle->ride_subtype = ride->subtype;
3089 
3090     vehicle->vehicle_type = vehicleEntryIndex;
3091     vehicle->SubType = carIndex == 0 ? Vehicle::Type::Head : Vehicle::Type::Tail;
3092     vehicle->var_44 = Numerics::ror32(vehicleEntry->spacing, 10) & 0xFFFF;
3093     auto edx = vehicleEntry->spacing >> 1;
3094     *remainingDistance -= edx;
3095     vehicle->remaining_distance = *remainingDistance;
3096     if (!(vehicleEntry->flags & VEHICLE_ENTRY_FLAG_GO_KART))
3097     {
3098         *remainingDistance -= edx;
3099     }
3100 
3101     // loc_6DD9A5:
3102     vehicle->sprite_width = vehicleEntry->sprite_width;
3103     vehicle->sprite_height_negative = vehicleEntry->sprite_height_negative;
3104     vehicle->sprite_height_positive = vehicleEntry->sprite_height_positive;
3105     vehicle->mass = vehicleEntry->car_mass;
3106     vehicle->num_seats = vehicleEntry->num_seats;
3107     vehicle->speed = vehicleEntry->powered_max_speed;
3108     vehicle->powered_acceleration = vehicleEntry->powered_acceleration;
3109     vehicle->velocity = 0;
3110     vehicle->acceleration = 0;
3111     vehicle->SwingSprite = 0;
3112     vehicle->SwingPosition = 0;
3113     vehicle->SwingSpeed = 0;
3114     vehicle->restraints_position = 0;
3115     vehicle->spin_sprite = 0;
3116     vehicle->spin_speed = 0;
3117     vehicle->sound2_flags = 0;
3118     vehicle->sound1_id = OpenRCT2::Audio::SoundId::Null;
3119     vehicle->sound2_id = OpenRCT2::Audio::SoundId::Null;
3120     vehicle->next_vehicle_on_train = SPRITE_INDEX_NULL;
3121     vehicle->var_C4 = 0;
3122     vehicle->animation_frame = 0;
3123     vehicle->animationState = 0;
3124     vehicle->scream_sound_id = OpenRCT2::Audio::SoundId::Null;
3125     vehicle->Pitch = 0;
3126     vehicle->bank_rotation = 0;
3127     vehicle->target_seat_rotation = 4;
3128     vehicle->seat_rotation = 4;
3129     for (int32_t i = 0; i < 32; i++)
3130     {
3131         vehicle->peep[i] = SPRITE_INDEX_NULL;
3132     }
3133 
3134     if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_DODGEM_CAR_PLACEMENT)
3135     {
3136         // loc_6DDCA4:
3137         vehicle->TrackSubposition = VehicleTrackSubposition::Default;
3138         int32_t direction = trackElement->GetDirection();
3139         auto dodgemPos = carPosition + CoordsXYZ{ word_9A3AB4[direction], 0 };
3140         vehicle->TrackLocation = dodgemPos;
3141         vehicle->current_station = trackElement->GetStationIndex();
3142 
3143         dodgemPos.z += ride->GetRideTypeDescriptor().Heights.VehicleZOffset;
3144 
3145         vehicle->SetTrackDirection(0);
3146         vehicle->SetTrackType(trackElement->GetTrackType());
3147         vehicle->track_progress = 0;
3148         vehicle->SetState(Vehicle::Status::MovingToEndOfStation);
3149         vehicle->update_flags = 0;
3150 
3151         CoordsXY chosenLoc;
3152         auto numAttempts = 0;
3153         // loc_6DDD26:
3154         do
3155         {
3156             numAttempts++;
3157             // This can happen when trying to spawn dozens of cars in a tiny area.
3158             if (numAttempts > 10000)
3159                 return nullptr;
3160 
3161             vehicle->sprite_direction = scenario_rand() & 0x1E;
3162             chosenLoc.y = dodgemPos.y + (scenario_rand() & 0xFF);
3163             chosenLoc.x = dodgemPos.x + (scenario_rand() & 0xFF);
3164         } while (vehicle->DodgemsCarWouldCollideAt(chosenLoc, nullptr));
3165 
3166         vehicle->MoveTo({ chosenLoc, dodgemPos.z });
3167     }
3168     else
3169     {
3170         VehicleTrackSubposition subposition = VehicleTrackSubposition::Default;
3171         if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_CHAIRLIFT)
3172         {
3173             subposition = VehicleTrackSubposition::ChairliftGoingOut;
3174         }
3175 
3176         if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_GO_KART)
3177         {
3178             // Choose which lane Go Kart should start in
3179             subposition = VehicleTrackSubposition::GoKartsLeftLane;
3180             if (vehicleIndex & 1)
3181             {
3182                 subposition = VehicleTrackSubposition::GoKartsRightLane;
3183             }
3184         }
3185         if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_MINI_GOLF)
3186         {
3187             subposition = VehicleTrackSubposition::MiniGolfStart9;
3188             vehicle->var_D3 = 0;
3189             vehicle->mini_golf_current_animation = MiniGolfAnimation::Walk;
3190             vehicle->mini_golf_flags = 0;
3191         }
3192         if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_REVERSER_BOGIE)
3193         {
3194             if (vehicle->IsHead())
3195             {
3196                 subposition = VehicleTrackSubposition::ReverserRCFrontBogie;
3197             }
3198         }
3199         if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_REVERSER_PASSENGER_CAR)
3200         {
3201             subposition = VehicleTrackSubposition::ReverserRCRearBogie;
3202         }
3203         vehicle->TrackSubposition = subposition;
3204 
3205         auto chosenLoc = carPosition;
3206         vehicle->TrackLocation = chosenLoc;
3207 
3208         int32_t direction = trackElement->GetDirection();
3209         vehicle->sprite_direction = direction << 3;
3210 
3211         if (ride->type == RIDE_TYPE_SPACE_RINGS)
3212         {
3213             direction = 4;
3214         }
3215         else
3216         {
3217             if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_VEHICLE_IS_INTEGRAL))
3218             {
3219                 if (ride->GetRideTypeDescriptor().StartTrackPiece != TrackElemType::FlatTrack1x4B)
3220                 {
3221                     if (ride->GetRideTypeDescriptor().StartTrackPiece != TrackElemType::FlatTrack1x4A)
3222                     {
3223                         if (ride->type == RIDE_TYPE_ENTERPRISE)
3224                         {
3225                             direction += 5;
3226                         }
3227                         else
3228                         {
3229                             direction = 4;
3230                         }
3231                     }
3232                 }
3233             }
3234         }
3235 
3236         chosenLoc += CoordsXYZ{ word_9A2A60[direction], ride->GetRideTypeDescriptor().Heights.VehicleZOffset };
3237 
3238         vehicle->current_station = trackElement->GetStationIndex();
3239 
3240         vehicle->MoveTo(chosenLoc);
3241         vehicle->SetTrackType(trackElement->GetTrackType());
3242         vehicle->SetTrackDirection(vehicle->sprite_direction >> 3);
3243         vehicle->track_progress = 31;
3244         if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_MINI_GOLF)
3245         {
3246             vehicle->track_progress = 15;
3247         }
3248         vehicle->update_flags = VEHICLE_UPDATE_FLAG_COLLISION_DISABLED;
3249         if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_HAS_INVERTED_SPRITE_SET)
3250         {
3251             if (trackElement->IsInverted())
3252             {
3253                 vehicle->SetUpdateFlag(VEHICLE_UPDATE_FLAG_USE_INVERTED_SPRITES);
3254             }
3255         }
3256         vehicle->SetState(Vehicle::Status::MovingToEndOfStation);
3257     }
3258 
3259     // loc_6DDD5E:
3260     vehicle->num_peeps = 0;
3261     vehicle->next_free_seat = 0;
3262     vehicle->BoatLocation.SetNull();
3263     vehicle->IsCrashedVehicle = false;
3264     return vehicle;
3265 }
3266 
3267 /**
3268  *
3269  *  rct2: 0x006DD84C
3270  */
vehicle_create_train(ride_id_t rideIndex,const CoordsXYZ & trainPos,int32_t vehicleIndex,int32_t * remainingDistance,TrackElement * trackElement)3271 static train_ref vehicle_create_train(
3272     ride_id_t rideIndex, const CoordsXYZ& trainPos, int32_t vehicleIndex, int32_t* remainingDistance,
3273     TrackElement* trackElement)
3274 {
3275     train_ref train = { nullptr, nullptr };
3276     auto ride = get_ride(rideIndex);
3277     if (ride != nullptr)
3278     {
3279         for (int32_t carIndex = 0; carIndex < ride->num_cars_per_train; carIndex++)
3280         {
3281             auto vehicle = ride_entry_get_vehicle_at_position(ride->subtype, ride->num_cars_per_train, carIndex);
3282             auto car = vehicle_create_car(
3283                 rideIndex, vehicle, carIndex, vehicleIndex, trainPos, remainingDistance, trackElement);
3284             if (car == nullptr)
3285                 break;
3286 
3287             if (carIndex == 0)
3288             {
3289                 train.head = car;
3290             }
3291             else
3292             {
3293                 // Link the previous car with this car
3294                 train.tail->next_vehicle_on_train = car->sprite_index;
3295                 train.tail->next_vehicle_on_ride = car->sprite_index;
3296                 car->prev_vehicle_on_ride = train.tail->sprite_index;
3297             }
3298             train.tail = car;
3299         }
3300     }
3301     return train;
3302 }
3303 
vehicle_create_trains(ride_id_t rideIndex,const CoordsXYZ & trainsPos,TrackElement * trackElement)3304 static bool vehicle_create_trains(ride_id_t rideIndex, const CoordsXYZ& trainsPos, TrackElement* trackElement)
3305 {
3306     auto ride = get_ride(rideIndex);
3307     if (ride == nullptr)
3308         return false;
3309 
3310     train_ref firstTrain = {};
3311     train_ref lastTrain = {};
3312     int32_t remainingDistance = 0;
3313     bool allTrainsCreated = true;
3314 
3315     for (int32_t vehicleIndex = 0; vehicleIndex < ride->num_vehicles; vehicleIndex++)
3316     {
3317         if (ride->IsBlockSectioned())
3318         {
3319             remainingDistance = 0;
3320         }
3321         train_ref train = vehicle_create_train(rideIndex, trainsPos, vehicleIndex, &remainingDistance, trackElement);
3322         if (train.head == nullptr || train.tail == nullptr)
3323         {
3324             allTrainsCreated = false;
3325             continue;
3326         }
3327 
3328         if (vehicleIndex == 0)
3329         {
3330             firstTrain = train;
3331         }
3332         else
3333         {
3334             // Link the end of the previous train with the front of this train
3335             lastTrain.tail->next_vehicle_on_ride = train.head->sprite_index;
3336             train.head->prev_vehicle_on_ride = lastTrain.tail->sprite_index;
3337         }
3338         lastTrain = train;
3339 
3340         for (int32_t i = 0; i <= MAX_VEHICLES_PER_RIDE; i++)
3341         {
3342             if (ride->vehicles[i] == SPRITE_INDEX_NULL)
3343             {
3344                 ride->vehicles[i] = train.head->sprite_index;
3345                 break;
3346             }
3347         }
3348     }
3349 
3350     // Link the first train and last train together. Nullptr checks are there to keep Clang happy.
3351     if (lastTrain.tail != nullptr)
3352         firstTrain.head->prev_vehicle_on_ride = lastTrain.tail->sprite_index;
3353     if (firstTrain.head != nullptr)
3354         lastTrain.tail->next_vehicle_on_ride = firstTrain.head->sprite_index;
3355 
3356     return allTrainsCreated;
3357 }
3358 
3359 /**
3360  *
3361  *  rct2: 0x006DDE9E
3362  */
ride_create_vehicles_find_first_block(Ride * ride,CoordsXYE * outXYElement)3363 static void ride_create_vehicles_find_first_block(Ride* ride, CoordsXYE* outXYElement)
3364 {
3365     Vehicle* vehicle = GetEntity<Vehicle>(ride->vehicles[0]);
3366     if (vehicle == nullptr)
3367         return;
3368 
3369     auto curTrackPos = vehicle->TrackLocation;
3370     auto curTrackElement = map_get_track_element_at(curTrackPos);
3371 
3372     assert(curTrackElement != nullptr);
3373 
3374     CoordsXY trackPos = curTrackPos;
3375     auto trackElement = curTrackElement;
3376     track_begin_end trackBeginEnd;
3377     while (track_block_get_previous({ trackPos, reinterpret_cast<TileElement*>(trackElement) }, &trackBeginEnd))
3378     {
3379         trackPos = { trackBeginEnd.end_x, trackBeginEnd.end_y };
3380         trackElement = trackBeginEnd.begin_element->AsTrack();
3381         if (trackPos == curTrackPos && trackElement == curTrackElement)
3382         {
3383             break;
3384         }
3385 
3386         auto trackType = trackElement->GetTrackType();
3387         switch (trackType)
3388         {
3389             case TrackElemType::Up25ToFlat:
3390             case TrackElemType::Up60ToFlat:
3391                 if (trackElement->HasChain())
3392                 {
3393                     *outXYElement = { trackPos, reinterpret_cast<TileElement*>(trackElement) };
3394                     return;
3395                 }
3396                 break;
3397             case TrackElemType::DiagUp25ToFlat:
3398             case TrackElemType::DiagUp60ToFlat:
3399                 if (trackElement->HasChain())
3400                 {
3401                     TileElement* tileElement = map_get_track_element_at_of_type_seq(
3402                         { trackBeginEnd.begin_x, trackBeginEnd.begin_y, trackBeginEnd.begin_z }, trackType, 0);
3403 
3404                     if (tileElement != nullptr)
3405                     {
3406                         outXYElement->x = trackBeginEnd.begin_x;
3407                         outXYElement->y = trackBeginEnd.begin_y;
3408                         outXYElement->element = tileElement;
3409                         return;
3410                     }
3411                 }
3412                 break;
3413             case TrackElemType::EndStation:
3414             case TrackElemType::CableLiftHill:
3415             case TrackElemType::BlockBrakes:
3416                 *outXYElement = { trackPos, reinterpret_cast<TileElement*>(trackElement) };
3417                 return;
3418         }
3419     }
3420 
3421     outXYElement->x = curTrackPos.x;
3422     outXYElement->y = curTrackPos.y;
3423     outXYElement->element = reinterpret_cast<TileElement*>(curTrackElement);
3424 }
3425 
3426 /**
3427  * Create and place the rides vehicles
3428  *  rct2: 0x006DD84C
3429  */
CreateVehicles(const CoordsXYE & element,bool isApplying)3430 bool Ride::CreateVehicles(const CoordsXYE& element, bool isApplying)
3431 {
3432     UpdateMaxVehicles();
3433     if (subtype == OBJECT_ENTRY_INDEX_NULL)
3434     {
3435         return true;
3436     }
3437 
3438     // Check if there are enough free sprite slots for all the vehicles
3439     int32_t totalCars = num_vehicles * num_cars_per_train;
3440     if (totalCars > count_free_misc_sprite_slots())
3441     {
3442         gGameCommandErrorText = STR_UNABLE_TO_CREATE_ENOUGH_VEHICLES;
3443         return false;
3444     }
3445 
3446     if (!isApplying)
3447     {
3448         return true;
3449     }
3450 
3451     auto* trackElement = element.element->AsTrack();
3452     auto vehiclePos = CoordsXYZ{ element, element.element->GetBaseZ() };
3453     int32_t direction = trackElement->GetDirection();
3454 
3455     //
3456     if (mode == RideMode::StationToStation)
3457     {
3458         vehiclePos -= CoordsXYZ{ CoordsDirectionDelta[direction], 0 };
3459 
3460         trackElement = map_get_track_element_at(vehiclePos);
3461 
3462         vehiclePos.z = trackElement->GetBaseZ();
3463     }
3464 
3465     if (!vehicle_create_trains(id, vehiclePos, trackElement))
3466     {
3467         // This flag is needed for Ride::RemoveVehicles()
3468         lifecycle_flags |= RIDE_LIFECYCLE_ON_TRACK;
3469         RemoveVehicles();
3470         gGameCommandErrorText = STR_UNABLE_TO_CREATE_ENOUGH_VEHICLES;
3471         return false;
3472     }
3473     // return true;
3474 
3475     // Initialise station departs
3476     // 006DDDD0:
3477     lifecycle_flags |= RIDE_LIFECYCLE_ON_TRACK;
3478     for (int32_t i = 0; i < MAX_STATIONS; i++)
3479     {
3480         stations[i].Depart = (stations[i].Depart & STATION_DEPART_FLAG) | 1;
3481     }
3482 
3483     //
3484     if (type != RIDE_TYPE_SPACE_RINGS && !GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_VEHICLE_IS_INTEGRAL))
3485     {
3486         if (IsBlockSectioned())
3487         {
3488             CoordsXYE firstBlock{};
3489             ride_create_vehicles_find_first_block(this, &firstBlock);
3490             MoveTrainsToBlockBrakes(firstBlock.element->AsTrack());
3491         }
3492         else
3493         {
3494             for (int32_t i = 0; i < num_vehicles; i++)
3495             {
3496                 Vehicle* vehicle = GetEntity<Vehicle>(vehicles[i]);
3497                 if (vehicle == nullptr)
3498                 {
3499                     continue;
3500                 }
3501 
3502                 auto vehicleEntry = vehicle->Entry();
3503 
3504                 if (!(vehicleEntry->flags & VEHICLE_ENTRY_FLAG_DODGEM_CAR_PLACEMENT))
3505                 {
3506                     vehicle->UpdateTrackMotion(nullptr);
3507                 }
3508 
3509                 vehicle->EnableCollisionsForTrain();
3510             }
3511         }
3512     }
3513     ride_update_vehicle_colours(this);
3514     return true;
3515 }
3516 
3517 /**
3518  * Move all the trains so each one will be placed at the block brake of a different block.
3519  * The first vehicle will placed into the first block and all other vehicles in the blocks
3520  * preceding that block.
3521  *  rct2: 0x006DDF9C
3522  */
MoveTrainsToBlockBrakes(TrackElement * firstBlock)3523 void Ride::MoveTrainsToBlockBrakes(TrackElement* firstBlock)
3524 {
3525     for (int32_t i = 0; i < num_vehicles; i++)
3526     {
3527         auto train = GetEntity<Vehicle>(vehicles[i]);
3528         if (train == nullptr)
3529             continue;
3530 
3531         train->UpdateTrackMotion(nullptr);
3532 
3533         if (i == 0)
3534         {
3535             train->EnableCollisionsForTrain();
3536             continue;
3537         }
3538 
3539         size_t numIterations = 0;
3540         do
3541         {
3542             // Fixes both freezing issues in #15503.
3543             // TODO: refactor the code so a tortoise-and-hare algorithm can be used.
3544             if (numIterations++ > 1000000)
3545             {
3546                 break;
3547             }
3548 
3549             firstBlock->SetBlockBrakeClosed(true);
3550             for (Vehicle* car = train; car != nullptr; car = GetEntity<Vehicle>(car->next_vehicle_on_train))
3551             {
3552                 car->velocity = 0;
3553                 car->acceleration = 0;
3554                 car->SwingSprite = 0;
3555                 car->remaining_distance += 13962;
3556             }
3557         } while (!(train->UpdateTrackMotion(nullptr) & VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_AT_BLOCK_BRAKE));
3558 
3559         firstBlock->SetBlockBrakeClosed(true);
3560         for (Vehicle* car = train; car != nullptr; car = GetEntity<Vehicle>(car->next_vehicle_on_train))
3561         {
3562             car->ClearUpdateFlag(VEHICLE_UPDATE_FLAG_COLLISION_DISABLED);
3563             car->SetState(Vehicle::Status::Travelling, car->sub_state);
3564             if ((car->GetTrackType()) == TrackElemType::EndStation)
3565             {
3566                 car->SetState(Vehicle::Status::MovingToEndOfStation, car->sub_state);
3567             }
3568         }
3569     }
3570 }
3571 
3572 /**
3573  * Checks and initialises the cable lift track returns false if unable to find
3574  * appropriate track.
3575  *  rct2: 0x006D31A6
3576  */
ride_initialise_cable_lift_track(Ride * ride,bool isApplying)3577 static bool ride_initialise_cable_lift_track(Ride* ride, bool isApplying)
3578 {
3579     CoordsXYZ location;
3580     for (StationIndex stationIndex = 0; stationIndex < MAX_STATIONS; stationIndex++)
3581     {
3582         location = ride->stations[stationIndex].GetStart();
3583         if (!location.IsNull())
3584             break;
3585         if (stationIndex == (MAX_STATIONS - 1))
3586         {
3587             gGameCommandErrorText = STR_CABLE_LIFT_HILL_MUST_START_IMMEDIATELY_AFTER_STATION;
3588             return false;
3589         }
3590     }
3591 
3592     bool success = false;
3593     TileElement* tileElement = map_get_first_element_at(location);
3594     if (tileElement == nullptr)
3595         return success;
3596     do
3597     {
3598         if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK)
3599             continue;
3600         if (tileElement->GetBaseZ() != location.z)
3601             continue;
3602 
3603         const auto& ted = GetTrackElementDescriptor(tileElement->AsTrack()->GetTrackType());
3604         if (!(ted.SequenceProperties[0] & TRACK_SEQUENCE_FLAG_ORIGIN))
3605         {
3606             continue;
3607         }
3608         success = true;
3609         break;
3610     } while (!(tileElement++)->IsLastForTile());
3611 
3612     if (!success)
3613         return false;
3614 
3615     enum
3616     {
3617         STATE_FIND_CABLE_LIFT,
3618         STATE_FIND_STATION,
3619         STATE_REST_OF_TRACK
3620     };
3621     int32_t state = STATE_FIND_CABLE_LIFT;
3622 
3623     track_circuit_iterator it;
3624     track_circuit_iterator_begin(&it, { location, tileElement });
3625     while (track_circuit_iterator_previous(&it))
3626     {
3627         tileElement = it.current.element;
3628         auto trackType = tileElement->AsTrack()->GetTrackType();
3629 
3630         uint16_t flags = TRACK_ELEMENT_SET_HAS_CABLE_LIFT_FALSE;
3631         switch (state)
3632         {
3633             case STATE_FIND_CABLE_LIFT:
3634                 // Search for a cable lift hill track element
3635                 if (trackType == TrackElemType::CableLiftHill)
3636                 {
3637                     flags = TRACK_ELEMENT_SET_HAS_CABLE_LIFT_TRUE;
3638                     state = STATE_FIND_STATION;
3639                 }
3640                 break;
3641             case STATE_FIND_STATION:
3642                 // Search for the start of the hill
3643                 switch (trackType)
3644                 {
3645                     case TrackElemType::Flat:
3646                     case TrackElemType::Up25:
3647                     case TrackElemType::Up60:
3648                     case TrackElemType::FlatToUp25:
3649                     case TrackElemType::Up25ToFlat:
3650                     case TrackElemType::Up25ToUp60:
3651                     case TrackElemType::Up60ToUp25:
3652                     case TrackElemType::FlatToUp60LongBase:
3653                         flags = TRACK_ELEMENT_SET_HAS_CABLE_LIFT_TRUE;
3654                         break;
3655                     case TrackElemType::EndStation:
3656                         state = STATE_REST_OF_TRACK;
3657                         break;
3658                     default:
3659                         gGameCommandErrorText = STR_CABLE_LIFT_HILL_MUST_START_IMMEDIATELY_AFTER_STATION;
3660                         return false;
3661                 }
3662                 break;
3663         }
3664         if (isApplying)
3665         {
3666             auto tmpLoc = CoordsXYZ{ it.current, tileElement->GetBaseZ() };
3667             auto direction = tileElement->GetDirection();
3668             trackType = tileElement->AsTrack()->GetTrackType();
3669             GetTrackElementOriginAndApplyChanges({ tmpLoc, direction }, trackType, 0, &tileElement, flags);
3670         }
3671     }
3672     return true;
3673 }
3674 
3675 /**
3676  *
3677  *  rct2: 0x006DF4D4
3678  */
ride_create_cable_lift(ride_id_t rideIndex,bool isApplying)3679 static bool ride_create_cable_lift(ride_id_t rideIndex, bool isApplying)
3680 {
3681     auto ride = get_ride(rideIndex);
3682     if (ride == nullptr)
3683         return false;
3684 
3685     if (ride->mode != RideMode::ContinuousCircuitBlockSectioned && ride->mode != RideMode::ContinuousCircuit)
3686     {
3687         gGameCommandErrorText = STR_CABLE_LIFT_UNABLE_TO_WORK_IN_THIS_OPERATING_MODE;
3688         return false;
3689     }
3690 
3691     if (ride->num_circuits > 1)
3692     {
3693         gGameCommandErrorText = STR_MULTICIRCUIT_NOT_POSSIBLE_WITH_CABLE_LIFT_HILL;
3694         return false;
3695     }
3696 
3697     if (count_free_misc_sprite_slots() <= 5)
3698     {
3699         gGameCommandErrorText = STR_UNABLE_TO_CREATE_ENOUGH_VEHICLES;
3700         return false;
3701     }
3702 
3703     if (!ride_initialise_cable_lift_track(ride, isApplying))
3704     {
3705         return false;
3706     }
3707 
3708     if (!isApplying)
3709     {
3710         return true;
3711     }
3712 
3713     auto cableLiftLoc = ride->CableLiftLoc;
3714     auto tileElement = map_get_track_element_at(cableLiftLoc);
3715     int32_t direction = tileElement->GetDirection();
3716 
3717     Vehicle* head = nullptr;
3718     Vehicle* tail = nullptr;
3719     uint32_t ebx = 0;
3720     for (int32_t i = 0; i < 5; i++)
3721     {
3722         uint32_t edx = Numerics::ror32(0x15478, 10);
3723         uint16_t var_44 = edx & 0xFFFF;
3724         edx = Numerics::rol32(edx, 10) >> 1;
3725         ebx -= edx;
3726         int32_t remaining_distance = ebx;
3727         ebx -= edx;
3728 
3729         Vehicle* current = cable_lift_segment_create(
3730             *ride, cableLiftLoc.x, cableLiftLoc.y, cableLiftLoc.z / 8, direction, var_44, remaining_distance, i == 0);
3731         current->next_vehicle_on_train = SPRITE_INDEX_NULL;
3732         if (i == 0)
3733         {
3734             head = current;
3735         }
3736         else
3737         {
3738             tail->next_vehicle_on_train = current->sprite_index;
3739             tail->next_vehicle_on_ride = current->sprite_index;
3740             current->prev_vehicle_on_ride = tail->sprite_index;
3741         }
3742         tail = current;
3743     }
3744     head->prev_vehicle_on_ride = tail->sprite_index;
3745     tail->next_vehicle_on_ride = head->sprite_index;
3746 
3747     ride->lifecycle_flags |= RIDE_LIFECYCLE_CABLE_LIFT;
3748     head->CableLiftUpdateTrackMotion();
3749     return true;
3750 }
3751 
3752 /**
3753  * Opens the construction window prompting to construct a missing entrance or exit.
3754  * This will also the screen to the first station missing the entrance or exit.
3755  *  rct2: 0x006B51C0
3756  */
ConstructMissingEntranceOrExit() const3757 void Ride::ConstructMissingEntranceOrExit() const
3758 {
3759     auto* w = window_get_main();
3760     if (w == nullptr)
3761         return;
3762 
3763     int8_t entranceOrExit = -1;
3764     int32_t i;
3765     for (i = 0; i < MAX_STATIONS; i++)
3766     {
3767         if (stations[i].Start.IsNull())
3768             continue;
3769 
3770         if (ride_get_entrance_location(this, i).IsNull())
3771         {
3772             entranceOrExit = WC_RIDE_CONSTRUCTION__WIDX_ENTRANCE;
3773             break;
3774         }
3775 
3776         if (ride_get_exit_location(this, i).IsNull())
3777         {
3778             entranceOrExit = WC_RIDE_CONSTRUCTION__WIDX_EXIT;
3779             break;
3780         }
3781     }
3782 
3783     if (entranceOrExit == -1)
3784         return;
3785 
3786     if (type != RIDE_TYPE_MAZE)
3787     {
3788         auto location = stations[i].GetStart();
3789         window_scroll_to_location(w, location);
3790 
3791         CoordsXYE trackElement;
3792         ride_try_get_origin_element(this, &trackElement);
3793         ride_find_track_gap(this, &trackElement, &trackElement);
3794         int32_t ok = ride_modify(&trackElement);
3795         if (ok == 0)
3796         {
3797             return;
3798         }
3799 
3800         w = window_find_by_class(WC_RIDE_CONSTRUCTION);
3801         if (w != nullptr)
3802             window_event_mouse_up_call(w, entranceOrExit);
3803     }
3804 }
3805 
3806 /**
3807  *
3808  *  rct2: 0x006B528A
3809  */
ride_scroll_to_track_error(CoordsXYE * trackElement)3810 static void ride_scroll_to_track_error(CoordsXYE* trackElement)
3811 {
3812     auto* w = window_get_main();
3813     if (w != nullptr)
3814     {
3815         window_scroll_to_location(w, { *trackElement, trackElement->element->GetBaseZ() });
3816         ride_modify(trackElement);
3817     }
3818 }
3819 
3820 /**
3821  *
3822  *  rct2: 0x006B4F6B
3823  */
GetOriginElement(StationIndex stationIndex) const3824 TrackElement* Ride::GetOriginElement(StationIndex stationIndex) const
3825 {
3826     auto stationLoc = stations[stationIndex].Start;
3827     TileElement* tileElement = map_get_first_element_at(stationLoc);
3828     if (tileElement == nullptr)
3829         return nullptr;
3830     do
3831     {
3832         if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK)
3833             continue;
3834 
3835         auto* trackElement = tileElement->AsTrack();
3836         const auto& ted = GetTrackElementDescriptor(trackElement->GetTrackType());
3837         if (!(ted.SequenceProperties[0] & TRACK_SEQUENCE_FLAG_ORIGIN))
3838             continue;
3839 
3840         if (trackElement->GetRideIndex() == id)
3841             return trackElement;
3842     } while (!(tileElement++)->IsLastForTile());
3843 
3844     return nullptr;
3845 }
3846 
Test(RideStatus newStatus,bool isApplying)3847 bool Ride::Test(RideStatus newStatus, bool isApplying)
3848 {
3849     CoordsXYE trackElement, problematicTrackElement = {};
3850 
3851     if (type == RIDE_TYPE_NULL)
3852     {
3853         log_warning("Invalid ride type for ride %u", EnumValue(id));
3854         return false;
3855     }
3856 
3857     if (newStatus != RideStatus::Simulating)
3858     {
3859         window_close_by_number(WC_RIDE_CONSTRUCTION, EnumValue(id));
3860     }
3861 
3862     StationIndex stationIndex = ride_mode_check_station_present(this);
3863     if (stationIndex == STATION_INDEX_NULL)
3864         return false;
3865 
3866     if (!ride_mode_check_valid_station_numbers(this))
3867         return false;
3868 
3869     if (newStatus != RideStatus::Simulating && !ride_check_for_entrance_exit(id))
3870     {
3871         ConstructMissingEntranceOrExit();
3872         return false;
3873     }
3874 
3875     // z = ride->stations[i].GetBaseZ();
3876     auto startLoc = stations[stationIndex].Start;
3877     trackElement.x = startLoc.x;
3878     trackElement.y = startLoc.y;
3879     trackElement.element = reinterpret_cast<TileElement*>(GetOriginElement(stationIndex));
3880     if (trackElement.element == nullptr)
3881     {
3882         // Maze is strange, station start is 0... investigation required
3883         if (type != RIDE_TYPE_MAZE)
3884             return false;
3885     }
3886 
3887     if (mode == RideMode::ContinuousCircuit || IsBlockSectioned())
3888     {
3889         if (ride_find_track_gap(this, &trackElement, &problematicTrackElement)
3890             && (newStatus != RideStatus::Simulating || IsBlockSectioned()))
3891         {
3892             gGameCommandErrorText = STR_TRACK_IS_NOT_A_COMPLETE_CIRCUIT;
3893             ride_scroll_to_track_error(&problematicTrackElement);
3894             return false;
3895         }
3896     }
3897 
3898     if (IsBlockSectioned())
3899     {
3900         if (!ride_check_block_brakes(&trackElement, &problematicTrackElement))
3901         {
3902             ride_scroll_to_track_error(&problematicTrackElement);
3903             return false;
3904         }
3905     }
3906 
3907     if (subtype != OBJECT_ENTRY_INDEX_NULL && !gCheatsEnableAllDrawableTrackPieces)
3908     {
3909         rct_ride_entry* rideType = get_ride_entry(subtype);
3910         if (rideType->flags & RIDE_ENTRY_FLAG_NO_INVERSIONS)
3911         {
3912             gGameCommandErrorText = STR_TRACK_UNSUITABLE_FOR_TYPE_OF_TRAIN;
3913             if (ride_check_track_contains_inversions(&trackElement, &problematicTrackElement))
3914             {
3915                 ride_scroll_to_track_error(&problematicTrackElement);
3916                 return false;
3917             }
3918         }
3919         if (rideType->flags & RIDE_ENTRY_FLAG_NO_BANKED_TRACK)
3920         {
3921             gGameCommandErrorText = STR_TRACK_UNSUITABLE_FOR_TYPE_OF_TRAIN;
3922             if (ride_check_track_contains_banked(&trackElement, &problematicTrackElement))
3923             {
3924                 ride_scroll_to_track_error(&problematicTrackElement);
3925                 return false;
3926             }
3927         }
3928     }
3929 
3930     if (mode == RideMode::StationToStation)
3931     {
3932         if (!ride_find_track_gap(this, &trackElement, &problematicTrackElement))
3933         {
3934             gGameCommandErrorText = STR_RIDE_MUST_START_AND_END_WITH_STATIONS;
3935             return false;
3936         }
3937 
3938         gGameCommandErrorText = STR_STATION_NOT_LONG_ENOUGH;
3939         if (!ride_check_station_length(&trackElement, &problematicTrackElement))
3940         {
3941             ride_scroll_to_track_error(&problematicTrackElement);
3942             return false;
3943         }
3944 
3945         gGameCommandErrorText = STR_RIDE_MUST_START_AND_END_WITH_STATIONS;
3946         if (!ride_check_start_and_end_is_station(&trackElement))
3947         {
3948             ride_scroll_to_track_error(&problematicTrackElement);
3949             return false;
3950         }
3951     }
3952 
3953     if (isApplying)
3954         ride_set_start_finish_points(id, &trackElement);
3955 
3956     const auto& rtd = GetRideTypeDescriptor();
3957     if (!rtd.HasFlag(RIDE_TYPE_FLAG_NO_VEHICLES) && !(lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK))
3958     {
3959         if (!CreateVehicles(trackElement, isApplying))
3960         {
3961             return false;
3962         }
3963     }
3964 
3965     if (rtd.HasFlag(RIDE_TYPE_FLAG_ALLOW_CABLE_LIFT_HILL) && (lifecycle_flags & RIDE_LIFECYCLE_CABLE_LIFT_HILL_COMPONENT_USED)
3966         && !(lifecycle_flags & RIDE_LIFECYCLE_CABLE_LIFT))
3967     {
3968         if (!ride_create_cable_lift(id, isApplying))
3969             return false;
3970     }
3971 
3972     return true;
3973 }
3974 /**
3975  *
3976  *  rct2: 0x006B4EEA
3977  */
Open(bool isApplying)3978 bool Ride::Open(bool isApplying)
3979 {
3980     CoordsXYE trackElement, problematicTrackElement = {};
3981 
3982     // Check to see if construction tool is in use. If it is close the construction window
3983     // to set the track to its final state and clean up ghosts.
3984     // We can't just call close as it would cause a stack overflow during shop creation
3985     // with auto open on.
3986     if (WC_RIDE_CONSTRUCTION == gCurrentToolWidget.window_classification && EnumValue(id) == gCurrentToolWidget.window_number
3987         && (input_test_flag(INPUT_FLAG_TOOL_ACTIVE)))
3988     {
3989         window_close_by_number(WC_RIDE_CONSTRUCTION, EnumValue(id));
3990     }
3991 
3992     StationIndex stationIndex = ride_mode_check_station_present(this);
3993     if (stationIndex == STATION_INDEX_NULL)
3994         return false;
3995 
3996     if (!ride_mode_check_valid_station_numbers(this))
3997         return false;
3998 
3999     if (!ride_check_for_entrance_exit(id))
4000     {
4001         ConstructMissingEntranceOrExit();
4002         return false;
4003     }
4004 
4005     if (isApplying)
4006     {
4007         ChainQueues();
4008         lifecycle_flags |= RIDE_LIFECYCLE_EVER_BEEN_OPENED;
4009     }
4010 
4011     // z = ride->stations[i].GetBaseZ();
4012     auto startLoc = stations[stationIndex].Start;
4013     trackElement.x = startLoc.x;
4014     trackElement.y = startLoc.y;
4015     trackElement.element = reinterpret_cast<TileElement*>(GetOriginElement(stationIndex));
4016     if (trackElement.element == nullptr)
4017     {
4018         // Maze is strange, station start is 0... investigation required
4019         if (type != RIDE_TYPE_MAZE)
4020             return false;
4021     }
4022 
4023     if (mode == RideMode::Race || mode == RideMode::ContinuousCircuit || IsBlockSectioned())
4024     {
4025         if (ride_find_track_gap(this, &trackElement, &problematicTrackElement))
4026         {
4027             gGameCommandErrorText = STR_TRACK_IS_NOT_A_COMPLETE_CIRCUIT;
4028             ride_scroll_to_track_error(&problematicTrackElement);
4029             return false;
4030         }
4031     }
4032 
4033     if (IsBlockSectioned())
4034     {
4035         if (!ride_check_block_brakes(&trackElement, &problematicTrackElement))
4036         {
4037             ride_scroll_to_track_error(&problematicTrackElement);
4038             return false;
4039         }
4040     }
4041 
4042     if (subtype != OBJECT_ENTRY_INDEX_NULL && !gCheatsEnableAllDrawableTrackPieces)
4043     {
4044         rct_ride_entry* rideEntry = get_ride_entry(subtype);
4045         if (rideEntry->flags & RIDE_ENTRY_FLAG_NO_INVERSIONS)
4046         {
4047             gGameCommandErrorText = STR_TRACK_UNSUITABLE_FOR_TYPE_OF_TRAIN;
4048             if (ride_check_track_contains_inversions(&trackElement, &problematicTrackElement))
4049             {
4050                 ride_scroll_to_track_error(&problematicTrackElement);
4051                 return false;
4052             }
4053         }
4054         if (rideEntry->flags & RIDE_ENTRY_FLAG_NO_BANKED_TRACK)
4055         {
4056             gGameCommandErrorText = STR_TRACK_UNSUITABLE_FOR_TYPE_OF_TRAIN;
4057             if (ride_check_track_contains_banked(&trackElement, &problematicTrackElement))
4058             {
4059                 ride_scroll_to_track_error(&problematicTrackElement);
4060                 return false;
4061             }
4062         }
4063     }
4064 
4065     if (mode == RideMode::StationToStation)
4066     {
4067         if (!ride_find_track_gap(this, &trackElement, &problematicTrackElement))
4068         {
4069             gGameCommandErrorText = STR_RIDE_MUST_START_AND_END_WITH_STATIONS;
4070             return false;
4071         }
4072 
4073         gGameCommandErrorText = STR_STATION_NOT_LONG_ENOUGH;
4074         if (!ride_check_station_length(&trackElement, &problematicTrackElement))
4075         {
4076             ride_scroll_to_track_error(&problematicTrackElement);
4077             return false;
4078         }
4079 
4080         gGameCommandErrorText = STR_RIDE_MUST_START_AND_END_WITH_STATIONS;
4081         if (!ride_check_start_and_end_is_station(&trackElement))
4082         {
4083             ride_scroll_to_track_error(&problematicTrackElement);
4084             return false;
4085         }
4086     }
4087 
4088     if (isApplying)
4089         ride_set_start_finish_points(id, &trackElement);
4090 
4091     if (!GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_NO_VEHICLES) && !(lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK))
4092     {
4093         if (!CreateVehicles(trackElement, isApplying))
4094         {
4095             return false;
4096         }
4097     }
4098 
4099     if ((GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_ALLOW_CABLE_LIFT_HILL))
4100         && (lifecycle_flags & RIDE_LIFECYCLE_CABLE_LIFT_HILL_COMPONENT_USED) && !(lifecycle_flags & RIDE_LIFECYCLE_CABLE_LIFT))
4101     {
4102         if (!ride_create_cable_lift(id, isApplying))
4103             return false;
4104     }
4105 
4106     return true;
4107 }
4108 
4109 /**
4110  * Given a track element of the ride, find the start of the track.
4111  * It has to do this as a backwards loop in case this is an incomplete track.
4112  */
ride_get_start_of_track(CoordsXYE * output)4113 void ride_get_start_of_track(CoordsXYE* output)
4114 {
4115     track_begin_end trackBeginEnd;
4116     CoordsXYE trackElement = *output;
4117     if (track_block_get_previous(trackElement, &trackBeginEnd))
4118     {
4119         TileElement* initial_map = trackElement.element;
4120         track_begin_end slowIt = trackBeginEnd;
4121         bool moveSlowIt = true;
4122         do
4123         {
4124             // Because we are working backwards, begin_element is the section at the end of a piece of track, whereas
4125             // begin_x and begin_y are the coordinates at the start of a piece of track, so we need to pass end_x and
4126             // end_y
4127             CoordsXYE lastGood = {
4128                 /* .x = */ trackBeginEnd.end_x,
4129                 /* .y = */ trackBeginEnd.end_y,
4130                 /* .element = */ trackBeginEnd.begin_element,
4131             };
4132 
4133             if (!track_block_get_previous(
4134                     { trackBeginEnd.end_x, trackBeginEnd.end_y, trackBeginEnd.begin_element }, &trackBeginEnd))
4135             {
4136                 trackElement = lastGood;
4137                 break;
4138             }
4139 
4140             moveSlowIt = !moveSlowIt;
4141             if (moveSlowIt)
4142             {
4143                 if (!track_block_get_previous({ slowIt.end_x, slowIt.end_y, slowIt.begin_element }, &slowIt)
4144                     || slowIt.begin_element == trackBeginEnd.begin_element)
4145                 {
4146                     break;
4147                 }
4148             }
4149         } while (initial_map != trackBeginEnd.begin_element);
4150     }
4151     *output = trackElement;
4152 }
4153 
4154 /**
4155  *
4156  *  rct2: 0x00696707
4157  */
StopGuestsQueuing()4158 void Ride::StopGuestsQueuing()
4159 {
4160     for (auto peep : EntityList<Guest>())
4161     {
4162         if (peep->State != PeepState::Queuing)
4163             continue;
4164         if (peep->CurrentRide != id)
4165             continue;
4166 
4167         peep->RemoveFromQueue();
4168         peep->SetState(PeepState::Falling);
4169     }
4170 }
4171 
GetDefaultMode() const4172 RideMode Ride::GetDefaultMode() const
4173 {
4174     return GetRideTypeDescriptor().DefaultMode;
4175 }
4176 
ride_with_colour_config_exists(uint8_t ride_type,const TrackColour * colours)4177 static bool ride_with_colour_config_exists(uint8_t ride_type, const TrackColour* colours)
4178 {
4179     for (auto& ride : GetRideManager())
4180     {
4181         if (ride.type != ride_type)
4182             continue;
4183         if (ride.track_colour[0].main != colours->main)
4184             continue;
4185         if (ride.track_colour[0].additional != colours->additional)
4186             continue;
4187         if (ride.track_colour[0].supports != colours->supports)
4188             continue;
4189 
4190         return true;
4191     }
4192     return false;
4193 }
4194 
NameExists(std::string_view name,ride_id_t excludeRideId)4195 bool Ride::NameExists(std::string_view name, ride_id_t excludeRideId)
4196 {
4197     char buffer[256]{};
4198     for (auto& ride : GetRideManager())
4199     {
4200         if (ride.id != excludeRideId)
4201         {
4202             Formatter ft;
4203             ride.FormatNameTo(ft);
4204             format_string(buffer, 256, STR_STRINGID, ft.Data());
4205             if (name == buffer && ride_has_any_track_elements(&ride))
4206             {
4207                 return true;
4208             }
4209         }
4210     }
4211     return false;
4212 }
4213 
4214 /**
4215  *
4216  *  Based on rct2: 0x006B4776
4217  */
ride_get_random_colour_preset_index(uint8_t ride_type)4218 int32_t ride_get_random_colour_preset_index(uint8_t ride_type)
4219 {
4220     if (ride_type >= std::size(RideTypeDescriptors))
4221     {
4222         return 0;
4223     }
4224 
4225     const track_colour_preset_list* colourPresets = &GetRideTypeDescriptor(ride_type).ColourPresets;
4226 
4227     // 200 attempts to find a colour preset that hasn't already been used in the park for this ride type
4228     for (int32_t i = 0; i < 200; i++)
4229     {
4230         int32_t listIndex = util_rand() % colourPresets->count;
4231         const TrackColour* colours = &colourPresets->list[listIndex];
4232 
4233         if (!ride_with_colour_config_exists(ride_type, colours))
4234         {
4235             return listIndex;
4236         }
4237     }
4238     return 0;
4239 }
4240 
4241 /**
4242  *
4243  *  Based on rct2: 0x006B4776
4244  */
SetColourPreset(uint8_t index)4245 void Ride::SetColourPreset(uint8_t index)
4246 {
4247     const track_colour_preset_list* colourPresets = &GetRideTypeDescriptor().ColourPresets;
4248     TrackColour colours = { COLOUR_BLACK, COLOUR_BLACK, COLOUR_BLACK };
4249     // Stalls save their default colour in the vehicle settings (since they share a common ride type)
4250     if (!IsRide())
4251     {
4252         auto rideEntry = get_ride_entry(subtype);
4253         if (rideEntry != nullptr && rideEntry->vehicle_preset_list->count > 0)
4254         {
4255             auto list = rideEntry->vehicle_preset_list->list[0];
4256             colours = { list.main, list.additional_1, list.additional_2 };
4257         }
4258     }
4259     else if (index < colourPresets->count)
4260     {
4261         colours = colourPresets->list[index];
4262     }
4263     for (int32_t i = 0; i < NUM_COLOUR_SCHEMES; i++)
4264     {
4265         track_colour[i].main = colours.main;
4266         track_colour[i].additional = colours.additional;
4267         track_colour[i].supports = colours.supports;
4268     }
4269     colour_scheme_type = 0;
4270 }
4271 
ride_get_common_price(Ride * forRide)4272 money32 ride_get_common_price(Ride* forRide)
4273 {
4274     for (const auto& ride : GetRideManager())
4275     {
4276         if (ride.type == forRide->type && &ride != forRide)
4277         {
4278             return ride.price[0];
4279         }
4280     }
4281 
4282     return MONEY32_UNDEFINED;
4283 }
4284 
SetNameToDefault()4285 void Ride::SetNameToDefault()
4286 {
4287     char rideNameBuffer[256]{};
4288 
4289     // Increment default name number until we find a unique name
4290     custom_name = {};
4291     default_name_number = 0;
4292     do
4293     {
4294         default_name_number++;
4295         Formatter ft;
4296         FormatNameTo(ft);
4297         format_string(rideNameBuffer, 256, STR_STRINGID, ft.Data());
4298     } while (Ride::NameExists(rideNameBuffer, id));
4299 }
4300 
4301 /**
4302  * This will return the name of the ride, as seen in the New Ride window.
4303  */
get_ride_naming(const uint8_t rideType,rct_ride_entry * rideEntry)4304 RideNaming get_ride_naming(const uint8_t rideType, rct_ride_entry* rideEntry)
4305 {
4306     if (!GetRideTypeDescriptor(rideType).HasFlag(RIDE_TYPE_FLAG_LIST_VEHICLES_SEPARATELY))
4307     {
4308         return GetRideTypeDescriptor(rideType).Naming;
4309     }
4310 
4311     return rideEntry->naming;
4312 }
4313 
4314 /*
4315  * The next eight functions are helpers to access ride data at the offset 10E &
4316  * 110. Known as the turn counts. There are 3 different types (default, banked, sloped)
4317  * and there are 4 counts as follows:
4318  *
4319  * 1 element turns: low 5 bits
4320  * 2 element turns: bits 6-8
4321  * 3 element turns: bits 9-11
4322  * 4 element or more turns: bits 12-15
4323  *
4324  * 4 plus elements only possible on sloped type. Falls back to 3 element
4325  * if by some miracle you manage 4 element none sloped.
4326  */
4327 
increment_turn_count_1_element(Ride * ride,uint8_t type)4328 void increment_turn_count_1_element(Ride* ride, uint8_t type)
4329 {
4330     uint16_t* turn_count;
4331     switch (type)
4332     {
4333         case 0:
4334             turn_count = &ride->turn_count_default;
4335             break;
4336         case 1:
4337             turn_count = &ride->turn_count_banked;
4338             break;
4339         case 2:
4340             turn_count = &ride->turn_count_sloped;
4341             break;
4342         default:
4343             return;
4344     }
4345     uint16_t value = (*turn_count & TURN_MASK_1_ELEMENT) + 1;
4346     *turn_count &= ~TURN_MASK_1_ELEMENT;
4347 
4348     if (value > TURN_MASK_1_ELEMENT)
4349         value = TURN_MASK_1_ELEMENT;
4350     *turn_count |= value;
4351 }
4352 
increment_turn_count_2_elements(Ride * ride,uint8_t type)4353 void increment_turn_count_2_elements(Ride* ride, uint8_t type)
4354 {
4355     uint16_t* turn_count;
4356     switch (type)
4357     {
4358         case 0:
4359             turn_count = &ride->turn_count_default;
4360             break;
4361         case 1:
4362             turn_count = &ride->turn_count_banked;
4363             break;
4364         case 2:
4365             turn_count = &ride->turn_count_sloped;
4366             break;
4367         default:
4368             return;
4369     }
4370     uint16_t value = (*turn_count & TURN_MASK_2_ELEMENTS) + 0x20;
4371     *turn_count &= ~TURN_MASK_2_ELEMENTS;
4372 
4373     if (value > TURN_MASK_2_ELEMENTS)
4374         value = TURN_MASK_2_ELEMENTS;
4375     *turn_count |= value;
4376 }
4377 
increment_turn_count_3_elements(Ride * ride,uint8_t type)4378 void increment_turn_count_3_elements(Ride* ride, uint8_t type)
4379 {
4380     uint16_t* turn_count;
4381     switch (type)
4382     {
4383         case 0:
4384             turn_count = &ride->turn_count_default;
4385             break;
4386         case 1:
4387             turn_count = &ride->turn_count_banked;
4388             break;
4389         case 2:
4390             turn_count = &ride->turn_count_sloped;
4391             break;
4392         default:
4393             return;
4394     }
4395     uint16_t value = (*turn_count & TURN_MASK_3_ELEMENTS) + 0x100;
4396     *turn_count &= ~TURN_MASK_3_ELEMENTS;
4397 
4398     if (value > TURN_MASK_3_ELEMENTS)
4399         value = TURN_MASK_3_ELEMENTS;
4400     *turn_count |= value;
4401 }
4402 
increment_turn_count_4_plus_elements(Ride * ride,uint8_t type)4403 void increment_turn_count_4_plus_elements(Ride* ride, uint8_t type)
4404 {
4405     uint16_t* turn_count;
4406     switch (type)
4407     {
4408         case 0:
4409         case 1:
4410             // Just in case fallback to 3 element turn
4411             increment_turn_count_3_elements(ride, type);
4412             return;
4413         case 2:
4414             turn_count = &ride->turn_count_sloped;
4415             break;
4416         default:
4417             return;
4418     }
4419     uint16_t value = (*turn_count & TURN_MASK_4_PLUS_ELEMENTS) + 0x800;
4420     *turn_count &= ~TURN_MASK_4_PLUS_ELEMENTS;
4421 
4422     if (value > TURN_MASK_4_PLUS_ELEMENTS)
4423         value = TURN_MASK_4_PLUS_ELEMENTS;
4424     *turn_count |= value;
4425 }
4426 
get_turn_count_1_element(Ride * ride,uint8_t type)4427 int32_t get_turn_count_1_element(Ride* ride, uint8_t type)
4428 {
4429     uint16_t* turn_count;
4430     switch (type)
4431     {
4432         case 0:
4433             turn_count = &ride->turn_count_default;
4434             break;
4435         case 1:
4436             turn_count = &ride->turn_count_banked;
4437             break;
4438         case 2:
4439             turn_count = &ride->turn_count_sloped;
4440             break;
4441         default:
4442             return 0;
4443     }
4444 
4445     return (*turn_count) & TURN_MASK_1_ELEMENT;
4446 }
4447 
get_turn_count_2_elements(Ride * ride,uint8_t type)4448 int32_t get_turn_count_2_elements(Ride* ride, uint8_t type)
4449 {
4450     uint16_t* turn_count;
4451     switch (type)
4452     {
4453         case 0:
4454             turn_count = &ride->turn_count_default;
4455             break;
4456         case 1:
4457             turn_count = &ride->turn_count_banked;
4458             break;
4459         case 2:
4460             turn_count = &ride->turn_count_sloped;
4461             break;
4462         default:
4463             return 0;
4464     }
4465 
4466     return ((*turn_count) & TURN_MASK_2_ELEMENTS) >> 5;
4467 }
4468 
get_turn_count_3_elements(Ride * ride,uint8_t type)4469 int32_t get_turn_count_3_elements(Ride* ride, uint8_t type)
4470 {
4471     uint16_t* turn_count;
4472     switch (type)
4473     {
4474         case 0:
4475             turn_count = &ride->turn_count_default;
4476             break;
4477         case 1:
4478             turn_count = &ride->turn_count_banked;
4479             break;
4480         case 2:
4481             turn_count = &ride->turn_count_sloped;
4482             break;
4483         default:
4484             return 0;
4485     }
4486 
4487     return ((*turn_count) & TURN_MASK_3_ELEMENTS) >> 8;
4488 }
4489 
get_turn_count_4_plus_elements(Ride * ride,uint8_t type)4490 int32_t get_turn_count_4_plus_elements(Ride* ride, uint8_t type)
4491 {
4492     uint16_t* turn_count;
4493     switch (type)
4494     {
4495         case 0:
4496         case 1:
4497             return 0;
4498         case 2:
4499             turn_count = &ride->turn_count_sloped;
4500             break;
4501         default:
4502             return 0;
4503     }
4504 
4505     return ((*turn_count) & TURN_MASK_4_PLUS_ELEMENTS) >> 11;
4506 }
4507 
HasSpinningTunnel() const4508 bool Ride::HasSpinningTunnel() const
4509 {
4510     return special_track_elements & RIDE_ELEMENT_TUNNEL_SPLASH_OR_RAPIDS;
4511 }
4512 
HasWaterSplash() const4513 bool Ride::HasWaterSplash() const
4514 {
4515     return special_track_elements & RIDE_ELEMENT_TUNNEL_SPLASH_OR_RAPIDS;
4516 }
4517 
HasRapids() const4518 bool Ride::HasRapids() const
4519 {
4520     return special_track_elements & RIDE_ELEMENT_TUNNEL_SPLASH_OR_RAPIDS;
4521 }
4522 
HasLogReverser() const4523 bool Ride::HasLogReverser() const
4524 {
4525     return special_track_elements & RIDE_ELEMENT_REVERSER_OR_WATERFALL;
4526 }
4527 
HasWaterfall() const4528 bool Ride::HasWaterfall() const
4529 {
4530     return special_track_elements & RIDE_ELEMENT_REVERSER_OR_WATERFALL;
4531 }
4532 
HasWhirlpool() const4533 bool Ride::HasWhirlpool() const
4534 {
4535     return special_track_elements & RIDE_ELEMENT_WHIRLPOOL;
4536 }
4537 
ride_get_helix_sections(Ride * ride)4538 uint8_t ride_get_helix_sections(Ride* ride)
4539 {
4540     // Helix sections stored in the low 5 bits.
4541     return ride->special_track_elements & 0x1F;
4542 }
4543 
IsPoweredLaunched() const4544 bool Ride::IsPoweredLaunched() const
4545 {
4546     return mode == RideMode::PoweredLaunchPasstrough || mode == RideMode::PoweredLaunch
4547         || mode == RideMode::PoweredLaunchBlockSectioned;
4548 }
4549 
IsBlockSectioned() const4550 bool Ride::IsBlockSectioned() const
4551 {
4552     return mode == RideMode::ContinuousCircuitBlockSectioned || mode == RideMode::PoweredLaunchBlockSectioned;
4553 }
4554 
ride_has_any_track_elements(const Ride * ride)4555 bool ride_has_any_track_elements(const Ride* ride)
4556 {
4557     tile_element_iterator it;
4558 
4559     tile_element_iterator_begin(&it);
4560     while (tile_element_iterator_next(&it))
4561     {
4562         if (it.element->GetType() != TILE_ELEMENT_TYPE_TRACK)
4563             continue;
4564         if (it.element->AsTrack()->GetRideIndex() != ride->id)
4565             continue;
4566         if (it.element->IsGhost())
4567             continue;
4568 
4569         return true;
4570     }
4571 
4572     return false;
4573 }
4574 
4575 /**
4576  *
4577  *  rct2: 0x006847BA
4578  */
set_vehicle_type_image_max_sizes(rct_ride_entry_vehicle * vehicle_type,int32_t num_images)4579 void set_vehicle_type_image_max_sizes(rct_ride_entry_vehicle* vehicle_type, int32_t num_images)
4580 {
4581     uint8_t bitmap[200][200] = { 0 };
4582 
4583     rct_drawpixelinfo dpi = {
4584         /*.bits = */ reinterpret_cast<uint8_t*>(bitmap),
4585         /*.x = */ -100,
4586         /*.y = */ -100,
4587         /*.width = */ 200,
4588         /*.height = */ 200,
4589         /*.pitch = */ 0,
4590         /*.zoom_level = */ 0,
4591     };
4592 
4593     for (int32_t i = 0; i < num_images; ++i)
4594     {
4595         gfx_draw_sprite_software(&dpi, ImageId::FromUInt32(vehicle_type->base_image_id + i), { 0, 0 });
4596     }
4597     int32_t al = -1;
4598     for (int32_t i = 99; i != 0; --i)
4599     {
4600         for (int32_t j = 0; j < 200; j++)
4601         {
4602             if (bitmap[j][100 - i] != 0)
4603             {
4604                 al = i;
4605                 break;
4606             }
4607         }
4608 
4609         if (al != -1)
4610             break;
4611 
4612         for (int32_t j = 0; j < 200; j++)
4613         {
4614             if (bitmap[j][100 + i] != 0)
4615             {
4616                 al = i;
4617                 break;
4618             }
4619         }
4620 
4621         if (al != -1)
4622             break;
4623     }
4624 
4625     al++;
4626     int32_t bl = -1;
4627 
4628     for (int32_t i = 99; i != 0; --i)
4629     {
4630         for (int32_t j = 0; j < 200; j++)
4631         {
4632             if (bitmap[100 - i][j] != 0)
4633             {
4634                 bl = i;
4635                 break;
4636             }
4637         }
4638 
4639         if (bl != -1)
4640             break;
4641     }
4642     bl++;
4643 
4644     int32_t bh = -1;
4645 
4646     for (int32_t i = 99; i != 0; --i)
4647     {
4648         for (int32_t j = 0; j < 200; j++)
4649         {
4650             if (bitmap[100 + i][j] != 0)
4651             {
4652                 bh = i;
4653                 break;
4654             }
4655         }
4656 
4657         if (bh != -1)
4658             break;
4659     }
4660     bh++;
4661 
4662     // Moved from object paint
4663 
4664     if (vehicle_type->flags & VEHICLE_ENTRY_FLAG_SPRITE_BOUNDS_INCLUDE_INVERTED_SET)
4665     {
4666         bl += 16;
4667     }
4668 
4669     vehicle_type->sprite_width = al;
4670     vehicle_type->sprite_height_negative = bl;
4671     vehicle_type->sprite_height_positive = bh;
4672 }
4673 
4674 /**
4675  *
4676  *  rct2: 0x006B59C6
4677  */
invalidate_test_results(Ride * ride)4678 void invalidate_test_results(Ride* ride)
4679 {
4680     ride->measurement = {};
4681     ride->excitement = RIDE_RATING_UNDEFINED;
4682     ride->lifecycle_flags &= ~RIDE_LIFECYCLE_TESTED;
4683     ride->lifecycle_flags &= ~RIDE_LIFECYCLE_TEST_IN_PROGRESS;
4684     if (ride->lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK)
4685     {
4686         for (int32_t i = 0; i < ride->num_vehicles; i++)
4687         {
4688             Vehicle* vehicle = GetEntity<Vehicle>(ride->vehicles[i]);
4689             if (vehicle != nullptr)
4690             {
4691                 vehicle->ClearUpdateFlag(VEHICLE_UPDATE_FLAG_TESTING);
4692             }
4693         }
4694     }
4695     window_invalidate_by_number(WC_RIDE, static_cast<uint32_t>(ride->id));
4696 }
4697 
4698 /**
4699  *
4700  *  rct2: 0x006B7481
4701  *
4702  * @param rideIndex (dl)
4703  * @param reliabilityIncreaseFactor (ax)
4704  */
ride_fix_breakdown(Ride * ride,int32_t reliabilityIncreaseFactor)4705 void ride_fix_breakdown(Ride* ride, int32_t reliabilityIncreaseFactor)
4706 {
4707     ride->lifecycle_flags &= ~RIDE_LIFECYCLE_BREAKDOWN_PENDING;
4708     ride->lifecycle_flags &= ~RIDE_LIFECYCLE_BROKEN_DOWN;
4709     ride->lifecycle_flags &= ~RIDE_LIFECYCLE_DUE_INSPECTION;
4710     ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAIN | RIDE_INVALIDATE_RIDE_LIST | RIDE_INVALIDATE_RIDE_MAINTENANCE;
4711 
4712     if (ride->lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK)
4713     {
4714         for (int32_t i = 0; i < ride->num_vehicles; i++)
4715         {
4716             for (Vehicle* vehicle = GetEntity<Vehicle>(ride->vehicles[i]); vehicle != nullptr;
4717                  vehicle = GetEntity<Vehicle>(vehicle->next_vehicle_on_train))
4718             {
4719                 vehicle->ClearUpdateFlag(VEHICLE_UPDATE_FLAG_ZERO_VELOCITY);
4720                 vehicle->ClearUpdateFlag(VEHICLE_UPDATE_FLAG_BROKEN_CAR);
4721                 vehicle->ClearUpdateFlag(VEHICLE_UPDATE_FLAG_BROKEN_TRAIN);
4722             }
4723         }
4724     }
4725 
4726     uint8_t unreliability = 100 - ride->reliability_percentage;
4727     ride->reliability += reliabilityIncreaseFactor * (unreliability / 2);
4728 }
4729 
4730 /**
4731  *
4732  *  rct2: 0x006DE102
4733  */
ride_update_vehicle_colours(Ride * ride)4734 void ride_update_vehicle_colours(Ride* ride)
4735 {
4736     if (ride->type == RIDE_TYPE_SPACE_RINGS || ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_VEHICLE_IS_INTEGRAL))
4737     {
4738         gfx_invalidate_screen();
4739     }
4740 
4741     for (int32_t i = 0; i <= MAX_VEHICLES_PER_RIDE; i++)
4742     {
4743         int32_t carIndex = 0;
4744         VehicleColour colours = {};
4745 
4746         for (Vehicle* vehicle = GetEntity<Vehicle>(ride->vehicles[i]); vehicle != nullptr;
4747              vehicle = GetEntity<Vehicle>(vehicle->next_vehicle_on_train))
4748         {
4749             switch (ride->colour_scheme_type & 3)
4750             {
4751                 case RIDE_COLOUR_SCHEME_ALL_SAME:
4752                     colours = ride->vehicle_colours[0];
4753                     colours.Ternary = ride->vehicle_colours[0].Ternary;
4754                     break;
4755                 case RIDE_COLOUR_SCHEME_DIFFERENT_PER_TRAIN:
4756                     colours = ride->vehicle_colours[i];
4757                     colours.Ternary = ride->vehicle_colours[i].Ternary;
4758                     break;
4759                 case RIDE_COLOUR_SCHEME_DIFFERENT_PER_CAR:
4760                     colours = ride->vehicle_colours[std::min(carIndex, MAX_CARS_PER_TRAIN - 1)];
4761                     colours.Ternary = ride->vehicle_colours[std::min(carIndex, MAX_CARS_PER_TRAIN - 1)].Ternary;
4762                     break;
4763             }
4764 
4765             vehicle->colours.body_colour = colours.Body;
4766             vehicle->colours.trim_colour = colours.Trim;
4767             vehicle->colours_extended = colours.Ternary;
4768             vehicle->Invalidate();
4769             carIndex++;
4770         }
4771     }
4772 }
4773 
4774 /**
4775  *
4776  *  rct2: 0x006DE4CD
4777  * trainLayout: Originally fixed to 0x00F64E38. This no longer postfixes with 255.
4778  */
ride_entry_get_train_layout(int32_t rideEntryIndex,int32_t numCarsPerTrain,uint8_t * trainLayout)4779 void ride_entry_get_train_layout(int32_t rideEntryIndex, int32_t numCarsPerTrain, uint8_t* trainLayout)
4780 {
4781     for (int32_t i = 0; i < numCarsPerTrain; i++)
4782     {
4783         trainLayout[i] = ride_entry_get_vehicle_at_position(rideEntryIndex, numCarsPerTrain, i);
4784     }
4785 }
4786 
ride_entry_get_vehicle_at_position(int32_t rideEntryIndex,int32_t numCarsPerTrain,int32_t position)4787 uint8_t ride_entry_get_vehicle_at_position(int32_t rideEntryIndex, int32_t numCarsPerTrain, int32_t position)
4788 {
4789     rct_ride_entry* rideEntry = get_ride_entry(rideEntryIndex);
4790     if (position == 0 && rideEntry->front_vehicle != 255)
4791     {
4792         return rideEntry->front_vehicle;
4793     }
4794     if (position == 1 && rideEntry->second_vehicle != 255)
4795     {
4796         return rideEntry->second_vehicle;
4797     }
4798     if (position == 2 && rideEntry->third_vehicle != 255)
4799     {
4800         return rideEntry->third_vehicle;
4801     }
4802     if (position == numCarsPerTrain - 1 && rideEntry->rear_vehicle != 255)
4803     {
4804         return rideEntry->rear_vehicle;
4805     }
4806 
4807     return rideEntry->default_vehicle;
4808 }
4809 
4810 // Finds track pieces that a given ride entry has sprites for
ride_entry_get_supported_track_pieces(const rct_ride_entry * rideEntry)4811 uint64_t ride_entry_get_supported_track_pieces(const rct_ride_entry* rideEntry)
4812 {
4813     // clang-format off
4814     static constexpr uint32_t trackPieceRequiredSprites[TRACK_GROUP_COUNT] =
4815     {
4816         VEHICLE_SPRITE_FLAG_FLAT,                                                                                                              // TRACK_FLAT
4817         VEHICLE_SPRITE_FLAG_FLAT,                                                                                                              // TRACK_STRAIGHT
4818         VEHICLE_SPRITE_FLAG_FLAT,                                                                                                              // TRACK_STATION_END
4819         VEHICLE_SPRITE_FLAG_GENTLE_SLOPES,                                                                                                     // TRACK_LIFT_HILL
4820         VEHICLE_SPRITE_FLAG_GENTLE_SLOPES | VEHICLE_SPRITE_FLAG_STEEP_SLOPES,                                                                  // TRACK_LIFT_HILL_STEEP
4821         VEHICLE_SPRITE_FLAG_GENTLE_SLOPES,                                                                                                     // TRACK_LIFT_HILL_CURVE
4822         VEHICLE_SPRITE_FLAG_FLAT_BANKED,                                                                                                       // TRACK_FLAT_ROLL_BANKING
4823         VEHICLE_SPRITE_FLAG_GENTLE_SLOPES | VEHICLE_SPRITE_FLAG_STEEP_SLOPES | VEHICLE_SPRITE_FLAG_VERTICAL_SLOPES,                            // TRACK_VERTICAL_LOOP
4824         VEHICLE_SPRITE_FLAG_FLAT | VEHICLE_SPRITE_FLAG_GENTLE_SLOPES,                                                                          // TRACK_SLOPE
4825         VEHICLE_SPRITE_FLAG_GENTLE_SLOPES | VEHICLE_SPRITE_FLAG_STEEP_SLOPES,                                                                  // TRACK_SLOPE_STEEP
4826         VEHICLE_SPRITE_FLAG_FLAT | VEHICLE_SPRITE_FLAG_GENTLE_SLOPES | VEHICLE_SPRITE_FLAG_STEEP_SLOPES,                                       // TRACK_SLOPE_LONG
4827         VEHICLE_SPRITE_FLAG_GENTLE_SLOPES,                                                                                                     // TRACK_SLOPE_CURVE
4828         VEHICLE_SPRITE_FLAG_STEEP_SLOPES,                                                                                                      // TRACK_SLOPE_CURVE_STEEP
4829         VEHICLE_SPRITE_FLAG_FLAT,                                                                                                              // TRACK_S_BEND
4830         VEHICLE_SPRITE_FLAG_FLAT,                                                                                                              // TRACK_CURVE_VERY_SMALL
4831         VEHICLE_SPRITE_FLAG_FLAT,                                                                                                              // TRACK_CURVE_SMALL
4832         VEHICLE_SPRITE_FLAG_FLAT,                                                                                                              // TRACK_CURVE
4833         VEHICLE_SPRITE_FLAG_FLAT | VEHICLE_SPRITE_FLAG_FLAT_BANKED | VEHICLE_SPRITE_FLAG_INLINE_TWISTS,                                        // TRACK_TWIST
4834         VEHICLE_SPRITE_FLAG_GENTLE_SLOPES | VEHICLE_SPRITE_FLAG_STEEP_SLOPES |  VEHICLE_SPRITE_FLAG_VERTICAL_SLOPES,                           // TRACK_HALF_LOOP
4835         VEHICLE_SPRITE_FLAG_FLAT | VEHICLE_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_BANKED_TRANSITIONS |  VEHICLE_SPRITE_FLAG_CORKSCREWS,              // TRACK_CORKSCREW
4836         VEHICLE_SPRITE_FLAG_FLAT,                                                                                                              // TRACK_TOWER_BASE
4837         VEHICLE_SPRITE_FLAG_FLAT_BANKED,                                                                                                       // TRACK_HELIX_SMALL
4838         VEHICLE_SPRITE_FLAG_FLAT_BANKED,                                                                                                       // TRACK_HELIX_LARGE
4839         VEHICLE_SPRITE_FLAG_FLAT,                                                                                                              // TRACK_HELIX_LARGE_UNBANKED
4840         VEHICLE_SPRITE_FLAG_FLAT,                                                                                                              // TRACK_BRAKES
4841         0,                                                                                                                                     // TRACK_25
4842         VEHICLE_SPRITE_FLAG_FLAT,                                                                                                              // TRACK_ON_RIDE_PHOTO
4843         VEHICLE_SPRITE_FLAG_FLAT,                                                                                                              // TRACK_WATER_SPLASH
4844         VEHICLE_SPRITE_FLAG_STEEP_SLOPES | VEHICLE_SPRITE_FLAG_VERTICAL_SLOPES,                                                                // TRACK_SLOPE_VERTICAL
4845         VEHICLE_SPRITE_FLAG_FLAT | VEHICLE_SPRITE_FLAG_FLAT_BANKED |  VEHICLE_SPRITE_FLAG_INLINE_TWISTS,                                       // TRACK_BARREL_ROLL
4846         VEHICLE_SPRITE_FLAG_GENTLE_SLOPES,                                                                                                     // TRACK_POWERED_LIFT
4847         VEHICLE_SPRITE_FLAG_GENTLE_SLOPES | VEHICLE_SPRITE_FLAG_STEEP_SLOPES |  VEHICLE_SPRITE_FLAG_VERTICAL_SLOPES,                           // TRACK_HALF_LOOP_LARGE
4848         VEHICLE_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_BANKED_TRANSITIONS | VEHICLE_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TURNS,                           // TRACK_SLOPE_CURVE_BANKED
4849         VEHICLE_SPRITE_FLAG_FLAT,                                                                                                              // TRACK_LOG_FLUME_REVERSER
4850         VEHICLE_SPRITE_FLAG_FLAT | VEHICLE_SPRITE_FLAG_FLAT_BANKED |  VEHICLE_SPRITE_FLAG_INLINE_TWISTS,                                       // TRACK_HEARTLINE_ROLL
4851         VEHICLE_SPRITE_FLAG_FLAT,                                                                                                              // TRACK_REVERSER
4852         VEHICLE_SPRITE_FLAG_FLAT,                                                                                                              // TRACK_REVERSE_FREEFALL
4853         VEHICLE_SPRITE_FLAG_FLAT | VEHICLE_SPRITE_FLAG_GENTLE_SLOPES | VEHICLE_SPRITE_FLAG_STEEP_SLOPES | VEHICLE_SPRITE_FLAG_VERTICAL_SLOPES, // TRACK_SLOPE_TO_FLAT
4854         VEHICLE_SPRITE_FLAG_FLAT,                                                                                                              // TRACK_BLOCK_BRAKES
4855         VEHICLE_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TRANSITIONS,                                                                                   // TRACK_SLOPE_ROLL_BANKING
4856         VEHICLE_SPRITE_FLAG_FLAT | VEHICLE_SPRITE_FLAG_GENTLE_SLOPES | VEHICLE_SPRITE_FLAG_STEEP_SLOPES,                                       // TRACK_SLOPE_STEEP_LONG
4857         VEHICLE_SPRITE_FLAG_VERTICAL_SLOPES,                                                                                                   // TRACK_CURVE_VERTICAL
4858         0,                                                                                                                                     // TRACK_42
4859         VEHICLE_SPRITE_FLAG_GENTLE_SLOPES,                                                                                                     // TRACK_LIFT_HILL_CABLE
4860         VEHICLE_SPRITE_FLAG_CURVED_LIFT_HILL,                                                                                                  // TRACK_LIFT_HILL_CURVED
4861         VEHICLE_SPRITE_FLAG_VERTICAL_SLOPES,                                                                                                   // TRACK_QUARTER_LOOP
4862         VEHICLE_SPRITE_FLAG_FLAT,                                                                                                              // TRACK_SPINNING_TUNNEL
4863         VEHICLE_SPRITE_FLAG_FLAT,                                                                                                              // TRACK_ROTATION_CONTROL_TOGGLE
4864         VEHICLE_SPRITE_FLAG_FLAT | VEHICLE_SPRITE_FLAG_FLAT_BANKED |  VEHICLE_SPRITE_FLAG_INLINE_TWISTS,                                       // TRACK_INLINE_TWIST_UNINVERTED
4865         VEHICLE_SPRITE_FLAG_FLAT | VEHICLE_SPRITE_FLAG_FLAT_BANKED |  VEHICLE_SPRITE_FLAG_INLINE_TWISTS,                                       // TRACK_INLINE_TWIST_INVERTED
4866         VEHICLE_SPRITE_FLAG_VERTICAL_SLOPES,                                                                                                   // TRACK_QUARTER_LOOP_UNINVERTED
4867         VEHICLE_SPRITE_FLAG_VERTICAL_SLOPES,                                                                                                   // TRACK_QUARTER_LOOP_INVERTED
4868         VEHICLE_SPRITE_FLAG_GENTLE_SLOPES,                                                                                                     // TRACK_RAPIDS
4869         VEHICLE_SPRITE_FLAG_GENTLE_SLOPES | VEHICLE_SPRITE_FLAG_STEEP_SLOPES |  VEHICLE_SPRITE_FLAG_VERTICAL_SLOPES,                           // TRACK_HALF_LOOP_UNINVERTED
4870         VEHICLE_SPRITE_FLAG_GENTLE_SLOPES | VEHICLE_SPRITE_FLAG_STEEP_SLOPES |  VEHICLE_SPRITE_FLAG_VERTICAL_SLOPES,                           // TRACK_HALF_LOOP_INVERTED
4871         VEHICLE_SPRITE_FLAG_FLAT,                                                                                                              // TRACK_WATERFALL
4872         VEHICLE_SPRITE_FLAG_FLAT,                                                                                                              // TRACK_WHIRLPOOL
4873         VEHICLE_SPRITE_FLAG_FLAT | VEHICLE_SPRITE_FLAG_GENTLE_SLOPES | VEHICLE_SPRITE_FLAG_STEEP_SLOPES,                                       // TRACK_BRAKE_FOR_DROP
4874         VEHICLE_SPRITE_FLAG_FLAT | VEHICLE_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_BANKED_TRANSITIONS |  VEHICLE_SPRITE_FLAG_CORKSCREWS,              // TRACK_CORKSCREW_UNINVERTED
4875         VEHICLE_SPRITE_FLAG_FLAT | VEHICLE_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_BANKED_TRANSITIONS |  VEHICLE_SPRITE_FLAG_CORKSCREWS,              // TRACK_CORKSCREW_INVERTED
4876         VEHICLE_SPRITE_FLAG_FLAT | VEHICLE_SPRITE_FLAG_GENTLE_SLOPES,                                                                          // TRACK_HEARTLINE_TRANSFER
4877         0,                                                                                                                                     // TRACK_MINI_GOLF_HOLE
4878     };
4879     // clang-format on
4880 
4881     // Only check default vehicle; it's assumed the others will have correct sprites if this one does (I've yet to find an
4882     // exception, at least)
4883     auto supportedPieces = std::numeric_limits<uint64_t>::max();
4884     auto defaultVehicle = rideEntry->GetDefaultVehicle();
4885     if (defaultVehicle != nullptr)
4886     {
4887         const auto defaultSpriteFlags = defaultVehicle->sprite_flags;
4888         for (size_t i = 0; i < std::size(trackPieceRequiredSprites); i++)
4889         {
4890             if ((defaultSpriteFlags & trackPieceRequiredSprites[i]) != trackPieceRequiredSprites[i])
4891             {
4892                 supportedPieces &= ~(1ULL << i);
4893             }
4894         }
4895     }
4896     return supportedPieces;
4897 }
4898 
ride_get_smallest_station_length(Ride * ride)4899 static std::optional<int32_t> ride_get_smallest_station_length(Ride* ride)
4900 {
4901     std::optional<int32_t> result;
4902     for (const auto& station : ride->stations)
4903     {
4904         if (!station.Start.IsNull())
4905         {
4906             if (!result.has_value() || station.Length < result.value())
4907             {
4908                 result = station.Length;
4909             }
4910         }
4911     }
4912     return result;
4913 }
4914 
4915 /**
4916  *
4917  *  rct2: 0x006CB3AA
4918  */
ride_get_track_length(Ride * ride)4919 static int32_t ride_get_track_length(Ride* ride)
4920 {
4921     TileElement* tileElement = nullptr;
4922     track_type_t trackType;
4923     CoordsXYZ trackStart;
4924     bool foundTrack = false;
4925 
4926     for (int32_t i = 0; i < MAX_STATIONS && !foundTrack; i++)
4927     {
4928         trackStart = ride->stations[i].GetStart();
4929         if (trackStart.IsNull())
4930             continue;
4931 
4932         tileElement = map_get_first_element_at(trackStart);
4933         if (tileElement == nullptr)
4934             continue;
4935         do
4936         {
4937             if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK)
4938                 continue;
4939 
4940             trackType = tileElement->AsTrack()->GetTrackType();
4941             const auto& ted = GetTrackElementDescriptor(trackType);
4942             if (!(ted.SequenceProperties[0] & TRACK_SEQUENCE_FLAG_ORIGIN))
4943                 continue;
4944 
4945             if (tileElement->GetBaseZ() != trackStart.z)
4946                 continue;
4947 
4948             foundTrack = true;
4949         } while (!foundTrack && !(tileElement++)->IsLastForTile());
4950     }
4951 
4952     if (!foundTrack)
4953         return 0;
4954 
4955     ride_id_t rideIndex = tileElement->AsTrack()->GetRideIndex();
4956 
4957     rct_window* w = window_find_by_class(WC_RIDE_CONSTRUCTION);
4958     if (w != nullptr && _rideConstructionState != RideConstructionState::State0 && _currentRideIndex == rideIndex)
4959     {
4960         ride_construction_invalidate_current_track();
4961     }
4962 
4963     bool moveSlowIt = true;
4964     int32_t result = 0;
4965 
4966     track_circuit_iterator it;
4967     track_circuit_iterator_begin(&it, { trackStart.x, trackStart.y, tileElement });
4968 
4969     track_circuit_iterator slowIt = it;
4970     while (track_circuit_iterator_next(&it))
4971     {
4972         trackType = it.current.element->AsTrack()->GetTrackType();
4973         const auto& ted = GetTrackElementDescriptor(trackType);
4974         result += ted.PieceLength;
4975 
4976         moveSlowIt = !moveSlowIt;
4977         if (moveSlowIt)
4978         {
4979             track_circuit_iterator_next(&slowIt);
4980             if (track_circuit_iterators_match(&it, &slowIt))
4981             {
4982                 return 0;
4983             }
4984         }
4985     }
4986     return result;
4987 }
4988 
4989 /**
4990  *
4991  *  rct2: 0x006DD57D
4992  */
UpdateMaxVehicles()4993 void Ride::UpdateMaxVehicles()
4994 {
4995     if (subtype == OBJECT_ENTRY_INDEX_NULL)
4996         return;
4997 
4998     rct_ride_entry* rideEntry = get_ride_entry(subtype);
4999     if (rideEntry == nullptr)
5000     {
5001         return;
5002     }
5003     rct_ride_entry_vehicle* vehicleEntry;
5004     uint8_t numCarsPerTrain, numVehicles;
5005     int32_t maxNumTrains;
5006 
5007     if (rideEntry->cars_per_flat_ride == 0xFF)
5008     {
5009         int32_t trainLength;
5010         num_cars_per_train = std::max(rideEntry->min_cars_in_train, num_cars_per_train);
5011         MinCarsPerTrain = rideEntry->min_cars_in_train;
5012         MaxCarsPerTrain = rideEntry->max_cars_in_train;
5013 
5014         // Calculate maximum train length based on smallest station length
5015         auto stationNumTiles = ride_get_smallest_station_length(this);
5016         if (!stationNumTiles.has_value())
5017             return;
5018 
5019         auto stationLength = (stationNumTiles.value() * 0x44180) - 0x16B2A;
5020         int32_t maxMass = GetRideTypeDescriptor().MaxMass << 8;
5021         int32_t maxCarsPerTrain = 1;
5022         for (int32_t numCars = rideEntry->max_cars_in_train; numCars > 0; numCars--)
5023         {
5024             trainLength = 0;
5025             int32_t totalMass = 0;
5026             for (int32_t i = 0; i < numCars; i++)
5027             {
5028                 vehicleEntry = &rideEntry->vehicles[ride_entry_get_vehicle_at_position(subtype, numCars, i)];
5029                 trainLength += vehicleEntry->spacing;
5030                 totalMass += vehicleEntry->car_mass;
5031             }
5032 
5033             if (trainLength <= stationLength && totalMass <= maxMass)
5034             {
5035                 maxCarsPerTrain = numCars;
5036                 break;
5037             }
5038         }
5039         int32_t newCarsPerTrain = std::max(proposed_num_cars_per_train, rideEntry->min_cars_in_train);
5040         maxCarsPerTrain = std::max(maxCarsPerTrain, static_cast<int32_t>(rideEntry->min_cars_in_train));
5041         if (!gCheatsDisableTrainLengthLimit)
5042         {
5043             newCarsPerTrain = std::min(maxCarsPerTrain, newCarsPerTrain);
5044         }
5045         MaxCarsPerTrain = maxCarsPerTrain;
5046         MinCarsPerTrain = rideEntry->min_cars_in_train;
5047 
5048         switch (mode)
5049         {
5050             case RideMode::ContinuousCircuitBlockSectioned:
5051             case RideMode::PoweredLaunchBlockSectioned:
5052                 maxNumTrains = std::clamp<int32_t>(num_stations + num_block_brakes - 1, 1, MAX_VEHICLES_PER_RIDE);
5053                 break;
5054             case RideMode::ReverseInclineLaunchedShuttle:
5055             case RideMode::PoweredLaunchPasstrough:
5056             case RideMode::Shuttle:
5057             case RideMode::LimPoweredLaunch:
5058             case RideMode::PoweredLaunch:
5059                 maxNumTrains = 1;
5060                 break;
5061             default:
5062                 // Calculate maximum number of trains
5063                 trainLength = 0;
5064                 for (int32_t i = 0; i < newCarsPerTrain; i++)
5065                 {
5066                     vehicleEntry = &rideEntry->vehicles[ride_entry_get_vehicle_at_position(subtype, newCarsPerTrain, i)];
5067                     trainLength += vehicleEntry->spacing;
5068                 }
5069 
5070                 int32_t totalLength = trainLength / 2;
5071                 if (newCarsPerTrain != 1)
5072                     totalLength /= 2;
5073 
5074                 maxNumTrains = 0;
5075                 do
5076                 {
5077                     maxNumTrains++;
5078                     totalLength += trainLength;
5079                 } while (totalLength <= stationLength);
5080 
5081                 if ((mode != RideMode::StationToStation && mode != RideMode::ContinuousCircuit)
5082                     || !(GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_ALLOW_MORE_VEHICLES_THAN_STATION_FITS)))
5083                 {
5084                     maxNumTrains = std::min(maxNumTrains, int32_t(MAX_VEHICLES_PER_RIDE));
5085                 }
5086                 else
5087                 {
5088                     vehicleEntry = &rideEntry->vehicles[ride_entry_get_vehicle_at_position(subtype, newCarsPerTrain, 0)];
5089                     int32_t poweredMaxSpeed = vehicleEntry->powered_max_speed;
5090 
5091                     int32_t totalSpacing = 0;
5092                     for (int32_t i = 0; i < newCarsPerTrain; i++)
5093                     {
5094                         vehicleEntry = &rideEntry->vehicles[ride_entry_get_vehicle_at_position(subtype, newCarsPerTrain, i)];
5095                         totalSpacing += vehicleEntry->spacing;
5096                     }
5097 
5098                     totalSpacing >>= 13;
5099                     int32_t trackLength = ride_get_track_length(this) / 4;
5100                     if (poweredMaxSpeed > 10)
5101                         trackLength = (trackLength * 3) / 4;
5102                     if (poweredMaxSpeed > 25)
5103                         trackLength = (trackLength * 3) / 4;
5104                     if (poweredMaxSpeed > 40)
5105                         trackLength = (trackLength * 3) / 4;
5106 
5107                     maxNumTrains = 0;
5108                     int32_t length = 0;
5109                     do
5110                     {
5111                         maxNumTrains++;
5112                         length += totalSpacing;
5113                     } while (maxNumTrains < MAX_VEHICLES_PER_RIDE && length < trackLength);
5114                 }
5115                 break;
5116         }
5117         max_trains = maxNumTrains;
5118 
5119         numCarsPerTrain = std::min(proposed_num_cars_per_train, static_cast<uint8_t>(newCarsPerTrain));
5120     }
5121     else
5122     {
5123         max_trains = rideEntry->cars_per_flat_ride;
5124         MinCarsPerTrain = rideEntry->min_cars_in_train;
5125         MaxCarsPerTrain = rideEntry->max_cars_in_train;
5126         numCarsPerTrain = rideEntry->max_cars_in_train;
5127         maxNumTrains = rideEntry->cars_per_flat_ride;
5128     }
5129 
5130     if (gCheatsDisableTrainLengthLimit)
5131     {
5132         maxNumTrains = MAX_VEHICLES_PER_RIDE;
5133     }
5134     numVehicles = std::min(proposed_num_vehicles, static_cast<uint8_t>(maxNumTrains));
5135 
5136     // Refresh new current num vehicles / num cars per vehicle
5137     if (numVehicles != num_vehicles || numCarsPerTrain != num_cars_per_train)
5138     {
5139         num_cars_per_train = numCarsPerTrain;
5140         num_vehicles = numVehicles;
5141         window_invalidate_by_number(WC_RIDE, EnumValue(id));
5142     }
5143 }
5144 
UpdateNumberOfCircuits()5145 void Ride::UpdateNumberOfCircuits()
5146 {
5147     if (!CanHaveMultipleCircuits())
5148     {
5149         num_circuits = 1;
5150     }
5151 }
5152 
SetRideEntry(int32_t rideEntry)5153 void Ride::SetRideEntry(int32_t rideEntry)
5154 {
5155     auto colour = ride_get_unused_preset_vehicle_colour(rideEntry);
5156     auto rideSetVehicleAction = RideSetVehicleAction(id, RideSetVehicleType::RideEntry, rideEntry, colour);
5157     GameActions::Execute(&rideSetVehicleAction);
5158 }
5159 
SetNumVehicles(int32_t numVehicles)5160 void Ride::SetNumVehicles(int32_t numVehicles)
5161 {
5162     auto rideSetVehicleAction = RideSetVehicleAction(id, RideSetVehicleType::NumTrains, numVehicles);
5163     GameActions::Execute(&rideSetVehicleAction);
5164 }
5165 
SetNumCarsPerVehicle(int32_t numCarsPerVehicle)5166 void Ride::SetNumCarsPerVehicle(int32_t numCarsPerVehicle)
5167 {
5168     auto rideSetVehicleAction = RideSetVehicleAction(id, RideSetVehicleType::NumCarsPerTrain, numCarsPerVehicle);
5169     GameActions::Execute(&rideSetVehicleAction);
5170 }
5171 
SetToDefaultInspectionInterval()5172 void Ride::SetToDefaultInspectionInterval()
5173 {
5174     uint8_t defaultInspectionInterval = gConfigGeneral.default_inspection_interval;
5175     if (inspection_interval != defaultInspectionInterval)
5176     {
5177         if (defaultInspectionInterval <= RIDE_INSPECTION_NEVER)
5178         {
5179             set_operating_setting(id, RideSetSetting::InspectionInterval, defaultInspectionInterval);
5180         }
5181     }
5182 }
5183 
5184 /**
5185  *
5186  *  rct2: 0x006B752C
5187  */
Crash(uint8_t vehicleIndex)5188 void Ride::Crash(uint8_t vehicleIndex)
5189 {
5190     Vehicle* vehicle = GetEntity<Vehicle>(vehicles[vehicleIndex]);
5191 
5192     if (!(gScreenFlags & SCREEN_FLAGS_TITLE_DEMO) && vehicle != nullptr)
5193     {
5194         // Open ride window for crashed vehicle
5195         auto intent = Intent(WD_VEHICLE);
5196         intent.putExtra(INTENT_EXTRA_VEHICLE, vehicle);
5197         rct_window* w = context_open_intent(&intent);
5198 
5199         rct_viewport* viewport = window_get_viewport(w);
5200         if (w != nullptr && viewport != nullptr)
5201         {
5202             viewport->flags |= VIEWPORT_FLAG_SOUND_ON;
5203         }
5204     }
5205 
5206     if (gConfigNotifications.ride_crashed)
5207     {
5208         Formatter ft;
5209         FormatNameTo(ft);
5210         News::AddItemToQueue(News::ItemType::Ride, STR_RIDE_HAS_CRASHED, EnumValue(id), ft);
5211     }
5212 }
5213 
ride_reset_all_names()5214 void ride_reset_all_names()
5215 {
5216     for (auto& ride : GetRideManager())
5217     {
5218         ride.SetNameToDefault();
5219     }
5220 }
5221 
5222 // Gets the approximate value of customers per hour for this ride. Multiplies ride_customers_in_last_5_minutes() by 12.
ride_customers_per_hour(const Ride * ride)5223 uint32_t ride_customers_per_hour(const Ride* ride)
5224 {
5225     return ride_customers_in_last_5_minutes(ride) * 12;
5226 }
5227 
5228 // Calculates the number of customers for this ride in the last 5 minutes (or more correctly 9600 game ticks)
ride_customers_in_last_5_minutes(const Ride * ride)5229 uint32_t ride_customers_in_last_5_minutes(const Ride* ride)
5230 {
5231     uint32_t sum = 0;
5232 
5233     for (int32_t i = 0; i < CUSTOMER_HISTORY_SIZE; i++)
5234     {
5235         sum += ride->num_customers[i];
5236     }
5237 
5238     return sum;
5239 }
5240 
ride_get_broken_vehicle(const Ride * ride)5241 Vehicle* ride_get_broken_vehicle(const Ride* ride)
5242 {
5243     uint16_t vehicleIndex = ride->vehicles[ride->broken_vehicle];
5244     Vehicle* vehicle = GetEntity<Vehicle>(vehicleIndex);
5245     if (vehicle != nullptr)
5246     {
5247         return vehicle->GetCar(ride->broken_car);
5248     }
5249     return nullptr;
5250 }
5251 
5252 /**
5253  *
5254  *  rct2: 0x006D235B
5255  */
Delete()5256 void Ride::Delete()
5257 {
5258     custom_name = {};
5259     measurement = {};
5260     type = RIDE_TYPE_NULL;
5261 }
5262 
Renew()5263 void Ride::Renew()
5264 {
5265     // Set build date to current date (so the ride is brand new)
5266     build_date = gDateMonthsElapsed;
5267     reliability = RIDE_INITIAL_RELIABILITY;
5268 }
5269 
GetClassification() const5270 RideClassification Ride::GetClassification() const
5271 {
5272     switch (type)
5273     {
5274         case RIDE_TYPE_FOOD_STALL:
5275         case RIDE_TYPE_1D:
5276         case RIDE_TYPE_DRINK_STALL:
5277         case RIDE_TYPE_1F:
5278         case RIDE_TYPE_SHOP:
5279         case RIDE_TYPE_22:
5280         case RIDE_TYPE_50:
5281         case RIDE_TYPE_52:
5282         case RIDE_TYPE_53:
5283         case RIDE_TYPE_54:
5284             return RideClassification::ShopOrStall;
5285         case RIDE_TYPE_INFORMATION_KIOSK:
5286         case RIDE_TYPE_TOILETS:
5287         case RIDE_TYPE_CASH_MACHINE:
5288         case RIDE_TYPE_FIRST_AID:
5289             return RideClassification::KioskOrFacility;
5290         default:
5291             return RideClassification::Ride;
5292     }
5293 }
5294 
IsRide() const5295 bool Ride::IsRide() const
5296 {
5297     return GetClassification() == RideClassification::Ride;
5298 }
5299 
ride_get_price(const Ride * ride)5300 money16 ride_get_price(const Ride* ride)
5301 {
5302     if (gParkFlags & PARK_FLAGS_NO_MONEY)
5303         return 0;
5304     if (ride->IsRide())
5305     {
5306         if (!park_ride_prices_unlocked())
5307         {
5308             return 0;
5309         }
5310     }
5311     return ride->price[0];
5312 }
5313 
5314 /**
5315  * Return the tile_element of an adjacent station at x,y,z(+-2).
5316  * Returns nullptr if no suitable tile_element is found.
5317  */
get_station_platform(const CoordsXYRangedZ & coords)5318 TileElement* get_station_platform(const CoordsXYRangedZ& coords)
5319 {
5320     bool foundTileElement = false;
5321     TileElement* tileElement = map_get_first_element_at(coords);
5322     if (tileElement != nullptr)
5323     {
5324         do
5325         {
5326             if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK)
5327                 continue;
5328             /* Check if tileElement is a station platform. */
5329             if (!tileElement->AsTrack()->IsStation())
5330                 continue;
5331 
5332             if (coords.baseZ > tileElement->GetBaseZ() || coords.clearanceZ < tileElement->GetBaseZ())
5333             {
5334                 /* The base height if tileElement is not within
5335                  * the z tolerance. */
5336                 continue;
5337             }
5338 
5339             foundTileElement = true;
5340             break;
5341         } while (!(tileElement++)->IsLastForTile());
5342     }
5343     if (!foundTileElement)
5344     {
5345         return nullptr;
5346     }
5347 
5348     return tileElement;
5349 }
5350 
5351 /**
5352  * Check for an adjacent station to x,y,z in direction.
5353  */
check_for_adjacent_station(const CoordsXYZ & stationCoords,uint8_t direction)5354 static bool check_for_adjacent_station(const CoordsXYZ& stationCoords, uint8_t direction)
5355 {
5356     bool found = false;
5357     int32_t adjX = stationCoords.x;
5358     int32_t adjY = stationCoords.y;
5359     for (uint32_t i = 0; i <= RIDE_ADJACENCY_CHECK_DISTANCE; i++)
5360     {
5361         adjX += CoordsDirectionDelta[direction].x;
5362         adjY += CoordsDirectionDelta[direction].y;
5363         TileElement* stationElement = get_station_platform(
5364             { { adjX, adjY, stationCoords.z }, stationCoords.z + 2 * COORDS_Z_STEP });
5365         if (stationElement != nullptr)
5366         {
5367             auto rideIndex = stationElement->AsTrack()->GetRideIndex();
5368             auto ride = get_ride(rideIndex);
5369             if (ride != nullptr && (ride->depart_flags & RIDE_DEPART_SYNCHRONISE_WITH_ADJACENT_STATIONS))
5370             {
5371                 found = true;
5372             }
5373         }
5374     }
5375     return found;
5376 }
5377 
5378 /**
5379  * Return whether ride has at least one adjacent station to it.
5380  */
ride_has_adjacent_station(Ride * ride)5381 bool ride_has_adjacent_station(Ride* ride)
5382 {
5383     bool found = false;
5384 
5385     /* Loop through all of the ride stations, checking for an
5386      * adjacent station on either side. */
5387     for (StationIndex stationNum = 0; stationNum < MAX_STATIONS; stationNum++)
5388     {
5389         auto stationStart = ride->stations[stationNum].GetStart();
5390         if (!stationStart.IsNull())
5391         {
5392             /* Get the map element for the station start. */
5393             TileElement* stationElement = get_station_platform({ stationStart, stationStart.z + 0 });
5394             if (stationElement == nullptr)
5395             {
5396                 continue;
5397             }
5398             /* Check the first side of the station */
5399             int32_t direction = stationElement->GetDirectionWithOffset(1);
5400             found = check_for_adjacent_station(stationStart, direction);
5401             if (found)
5402                 break;
5403             /* Check the other side of the station */
5404             direction = direction_reverse(direction);
5405             found = check_for_adjacent_station(stationStart, direction);
5406             if (found)
5407                 break;
5408         }
5409     }
5410     return found;
5411 }
5412 
ride_has_station_shelter(Ride * ride)5413 bool ride_has_station_shelter(Ride* ride)
5414 {
5415     auto stationObj = ride_get_station_object(ride);
5416     if (network_get_mode() != NETWORK_MODE_NONE)
5417     {
5418         // The server might run in headless mode so no images will be loaded, only check for stations.
5419         return stationObj != nullptr;
5420     }
5421     return stationObj != nullptr && stationObj->BaseImageId != 0;
5422 }
5423 
ride_has_ratings(const Ride * ride)5424 bool ride_has_ratings(const Ride* ride)
5425 {
5426     return ride->excitement != RIDE_RATING_UNDEFINED;
5427 }
5428 
5429 /**
5430  *  Searches for a non-null ride type in a ride entry.
5431  *  If none is found, it will still return RIDE_TYPE_NULL.
5432  */
ride_entry_get_first_non_null_ride_type(const rct_ride_entry * rideEntry)5433 uint8_t ride_entry_get_first_non_null_ride_type(const rct_ride_entry* rideEntry)
5434 {
5435     for (uint8_t i = 0; i < MAX_RIDE_TYPES_PER_RIDE_ENTRY; i++)
5436     {
5437         if (rideEntry->ride_type[i] != RIDE_TYPE_NULL)
5438         {
5439             return rideEntry->ride_type[i];
5440         }
5441     }
5442     return RIDE_TYPE_NULL;
5443 }
5444 
get_booster_speed(uint8_t rideType,int32_t rawSpeed)5445 int32_t get_booster_speed(uint8_t rideType, int32_t rawSpeed)
5446 {
5447     int8_t shiftFactor = GetRideTypeDescriptor(rideType).OperatingSettings.BoosterSpeedFactor;
5448     if (shiftFactor == 0)
5449     {
5450         return rawSpeed;
5451     }
5452     if (shiftFactor > 0)
5453     {
5454         return (rawSpeed << shiftFactor);
5455     }
5456 
5457     // Workaround for an issue with older compilers (GCC 6, Clang 4) which would fail the build
5458     int8_t shiftFactorAbs = std::abs(shiftFactor);
5459     return (rawSpeed >> shiftFactorAbs);
5460 }
5461 
fix_invalid_vehicle_sprite_sizes()5462 void fix_invalid_vehicle_sprite_sizes()
5463 {
5464     for (const auto& ride : GetRideManager())
5465     {
5466         for (auto entityIndex : ride.vehicles)
5467         {
5468             for (Vehicle* vehicle = TryGetEntity<Vehicle>(entityIndex); vehicle != nullptr;
5469                  vehicle = TryGetEntity<Vehicle>(vehicle->next_vehicle_on_train))
5470             {
5471                 auto vehicleEntry = vehicle->Entry();
5472                 if (vehicleEntry == nullptr)
5473                 {
5474                     break;
5475                 }
5476 
5477                 if (vehicle->sprite_width == 0)
5478                 {
5479                     vehicle->sprite_width = vehicleEntry->sprite_width;
5480                 }
5481                 if (vehicle->sprite_height_negative == 0)
5482                 {
5483                     vehicle->sprite_height_negative = vehicleEntry->sprite_height_negative;
5484                 }
5485                 if (vehicle->sprite_height_positive == 0)
5486                 {
5487                     vehicle->sprite_height_positive = vehicleEntry->sprite_height_positive;
5488                 }
5489             }
5490         }
5491     }
5492 }
5493 
ride_entry_has_category(const rct_ride_entry * rideEntry,uint8_t category)5494 bool ride_entry_has_category(const rct_ride_entry* rideEntry, uint8_t category)
5495 {
5496     auto rideType = ride_entry_get_first_non_null_ride_type(rideEntry);
5497     return GetRideTypeDescriptor(rideType).Category == category;
5498 }
5499 
ride_get_entry_index(int32_t rideType,int32_t rideSubType)5500 int32_t ride_get_entry_index(int32_t rideType, int32_t rideSubType)
5501 {
5502     int32_t subType = rideSubType;
5503 
5504     if (subType == OBJECT_ENTRY_INDEX_NULL)
5505     {
5506         auto& objManager = GetContext()->GetObjectManager();
5507         auto& rideEntries = objManager.GetAllRideEntries(rideType);
5508         if (rideEntries.size() > 0)
5509         {
5510             subType = rideEntries[0];
5511             for (auto rideEntryIndex : rideEntries)
5512             {
5513                 auto rideEntry = get_ride_entry(rideEntryIndex);
5514                 if (rideEntry == nullptr)
5515                 {
5516                     return OBJECT_ENTRY_INDEX_NULL;
5517                 }
5518 
5519                 // Can happen in select-by-track-type mode
5520                 if (!ride_entry_is_invented(rideEntryIndex) && !gCheatsIgnoreResearchStatus)
5521                 {
5522                     continue;
5523                 }
5524 
5525                 if (!GetRideTypeDescriptor(rideType).HasFlag(RIDE_TYPE_FLAG_LIST_VEHICLES_SEPARATELY))
5526                 {
5527                     subType = rideEntryIndex;
5528                     break;
5529                 }
5530             }
5531         }
5532     }
5533 
5534     return subType;
5535 }
5536 
ride_get_station_object(const Ride * ride)5537 StationObject* ride_get_station_object(const Ride* ride)
5538 {
5539     auto& objManager = GetContext()->GetObjectManager();
5540     return static_cast<StationObject*>(objManager.GetLoadedObject(ObjectType::Station, ride->entrance_style));
5541 }
5542 
5543 // Normally, a station has at most one entrance and one exit, which are at the same height
5544 // as the station. But in hacked parks, neither can be taken for granted. This code ensures
5545 // that the ride->entrances and ride->exits arrays will point to one of them. There is
5546 // an ever-so-slight chance two entrances/exits for the same station reside on the same tile.
5547 // In cases like this, the one at station height will be considered the "true" one.
5548 // If none exists at that height, newer and higher placed ones take precedence.
determine_ride_entrance_and_exit_locations()5549 void determine_ride_entrance_and_exit_locations()
5550 {
5551     log_verbose("Inspecting ride entrance / exit locations");
5552 
5553     for (auto& ride : GetRideManager())
5554     {
5555         for (StationIndex stationIndex = 0; stationIndex < MAX_STATIONS; stationIndex++)
5556         {
5557             TileCoordsXYZD entranceLoc = ride.stations[stationIndex].Entrance;
5558             TileCoordsXYZD exitLoc = ride.stations[stationIndex].Exit;
5559             bool fixEntrance = false;
5560             bool fixExit = false;
5561 
5562             // Skip if the station has no entrance
5563             if (!entranceLoc.IsNull())
5564             {
5565                 const EntranceElement* entranceElement = map_get_ride_entrance_element_at(entranceLoc.ToCoordsXYZD(), false);
5566 
5567                 if (entranceElement == nullptr || entranceElement->GetRideIndex() != ride.id
5568                     || entranceElement->GetStationIndex() != stationIndex)
5569                 {
5570                     fixEntrance = true;
5571                 }
5572                 else
5573                 {
5574                     ride.stations[stationIndex].Entrance.direction = static_cast<uint8_t>(entranceElement->GetDirection());
5575                 }
5576             }
5577 
5578             if (!exitLoc.IsNull())
5579             {
5580                 const EntranceElement* entranceElement = map_get_ride_exit_element_at(exitLoc.ToCoordsXYZD(), false);
5581 
5582                 if (entranceElement == nullptr || entranceElement->GetRideIndex() != ride.id
5583                     || entranceElement->GetStationIndex() != stationIndex)
5584                 {
5585                     fixExit = true;
5586                 }
5587                 else
5588                 {
5589                     ride.stations[stationIndex].Exit.direction = static_cast<uint8_t>(entranceElement->GetDirection());
5590                 }
5591             }
5592 
5593             if (!fixEntrance && !fixExit)
5594             {
5595                 continue;
5596             }
5597 
5598             // At this point, we know we have a disconnected entrance or exit.
5599             // Search the map to find it. Skip the outer ring of invisible tiles.
5600             bool alreadyFoundEntrance = false;
5601             bool alreadyFoundExit = false;
5602             for (int32_t x = 1; x < MAXIMUM_MAP_SIZE_TECHNICAL - 1; x++)
5603             {
5604                 for (int32_t y = 1; y < MAXIMUM_MAP_SIZE_TECHNICAL - 1; y++)
5605                 {
5606                     TileElement* tileElement = map_get_first_element_at(TileCoordsXY{ x, y });
5607 
5608                     if (tileElement != nullptr)
5609                     {
5610                         do
5611                         {
5612                             if (tileElement->GetType() != TILE_ELEMENT_TYPE_ENTRANCE)
5613                             {
5614                                 continue;
5615                             }
5616                             const EntranceElement* entranceElement = tileElement->AsEntrance();
5617                             if (entranceElement->GetRideIndex() != ride.id)
5618                             {
5619                                 continue;
5620                             }
5621                             if (entranceElement->GetStationIndex() != stationIndex)
5622                             {
5623                                 continue;
5624                             }
5625 
5626                             // The expected height is where entrances and exit reside in non-hacked parks.
5627                             const uint8_t expectedHeight = ride.stations[stationIndex].Height;
5628 
5629                             if (fixEntrance && entranceElement->GetEntranceType() == ENTRANCE_TYPE_RIDE_ENTRANCE)
5630                             {
5631                                 if (alreadyFoundEntrance)
5632                                 {
5633                                     if (ride.stations[stationIndex].Entrance.z == expectedHeight)
5634                                         continue;
5635                                     if (ride.stations[stationIndex].Entrance.z > entranceElement->base_height)
5636                                         continue;
5637                                 }
5638 
5639                                 // Found our entrance
5640                                 TileCoordsXYZD newEntranceLoc = {
5641                                     x,
5642                                     y,
5643                                     entranceElement->base_height,
5644                                     static_cast<uint8_t>(entranceElement->GetDirection()),
5645                                 };
5646                                 ride_set_entrance_location(&ride, stationIndex, newEntranceLoc);
5647                                 alreadyFoundEntrance = true;
5648 
5649                                 log_verbose(
5650                                     "Fixed disconnected entrance of ride %d, station %d to x = %d, y = %d and z = %d.", ride.id,
5651                                     stationIndex, x, y, entranceElement->base_height);
5652                             }
5653                             else if (fixExit && entranceElement->GetEntranceType() == ENTRANCE_TYPE_RIDE_EXIT)
5654                             {
5655                                 if (alreadyFoundExit)
5656                                 {
5657                                     if (ride.stations[stationIndex].Exit.z == expectedHeight)
5658                                         continue;
5659                                     if (ride.stations[stationIndex].Exit.z > entranceElement->base_height)
5660                                         continue;
5661                                 }
5662 
5663                                 // Found our exit
5664                                 ride_set_exit_location(
5665                                     &ride, stationIndex,
5666                                     { x, y, entranceElement->base_height,
5667                                       static_cast<uint8_t>(entranceElement->GetDirection()) });
5668                                 alreadyFoundExit = true;
5669 
5670                                 log_verbose(
5671                                     "Fixed disconnected exit of ride %d, station %d to x = %d, y = %d and z = %d.", ride.id,
5672                                     stationIndex, x, y, entranceElement->base_height);
5673                             }
5674                         } while (!(tileElement++)->IsLastForTile());
5675                     }
5676                 }
5677             }
5678 
5679             if (fixEntrance && !alreadyFoundEntrance)
5680             {
5681                 ride_clear_entrance_location(&ride, stationIndex);
5682                 log_verbose("Cleared disconnected entrance of ride %d, station %d.", ride.id, stationIndex);
5683             }
5684             if (fixExit && !alreadyFoundExit)
5685             {
5686                 ride_clear_exit_location(&ride, stationIndex);
5687                 log_verbose("Cleared disconnected exit of ride %d, station %d.", ride.id, stationIndex);
5688             }
5689         }
5690     }
5691 }
5692 
ride_clear_leftover_entrances(Ride * ride)5693 void ride_clear_leftover_entrances(Ride* ride)
5694 {
5695     tile_element_iterator it;
5696 
5697     tile_element_iterator_begin(&it);
5698     while (tile_element_iterator_next(&it))
5699     {
5700         if (it.element->GetType() == TILE_ELEMENT_TYPE_ENTRANCE
5701             && it.element->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_PARK_ENTRANCE
5702             && it.element->AsEntrance()->GetRideIndex() == ride->id)
5703         {
5704             tile_element_remove(it.element);
5705             tile_element_iterator_restart_for_tile(&it);
5706         }
5707     }
5708 }
5709 
GetName() const5710 std::string Ride::GetName() const
5711 {
5712     Formatter ft;
5713     FormatNameTo(ft);
5714     return format_string(STR_STRINGID, ft.Data());
5715 }
5716 
FormatNameTo(Formatter & ft) const5717 void Ride::FormatNameTo(Formatter& ft) const
5718 {
5719     if (!custom_name.empty())
5720     {
5721         auto str = custom_name.c_str();
5722         ft.Add<rct_string_id>(STR_STRING);
5723         ft.Add<const char*>(str);
5724     }
5725     else
5726     {
5727         auto rideTypeName = GetRideTypeDescriptor().Naming.Name;
5728         if (GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_LIST_VEHICLES_SEPARATELY))
5729         {
5730             auto rideEntry = GetRideEntry();
5731             if (rideEntry != nullptr)
5732             {
5733                 rideTypeName = rideEntry->naming.Name;
5734             }
5735         }
5736         ft.Add<rct_string_id>(1).Add<rct_string_id>(rideTypeName).Add<uint16_t>(default_name_number);
5737     }
5738 }
5739 
GetAvailableModes() const5740 uint64_t Ride::GetAvailableModes() const
5741 {
5742     if (gCheatsShowAllOperatingModes)
5743         return AllRideModesAvailable;
5744 
5745     return GetRideTypeDescriptor().RideModes;
5746 }
5747 
GetRideTypeDescriptor() const5748 const RideTypeDescriptor& Ride::GetRideTypeDescriptor() const
5749 {
5750     return ::GetRideTypeDescriptor(type);
5751 }
5752 
GetNumShelteredSections() const5753 uint8_t Ride::GetNumShelteredSections() const
5754 {
5755     return num_sheltered_sections & ShelteredSectionsBits::NumShelteredSectionsMask;
5756 }
5757 
IncreaseNumShelteredSections()5758 void Ride::IncreaseNumShelteredSections()
5759 {
5760     auto newNumShelteredSections = GetNumShelteredSections();
5761     if (newNumShelteredSections != 0x1F)
5762         newNumShelteredSections++;
5763     num_sheltered_sections &= ~ShelteredSectionsBits::NumShelteredSectionsMask;
5764     num_sheltered_sections |= newNumShelteredSections;
5765 }
5766 
UpdateRideTypeForAllPieces()5767 void Ride::UpdateRideTypeForAllPieces()
5768 {
5769     for (int32_t y = 0; y < MAXIMUM_MAP_SIZE_TECHNICAL; y++)
5770     {
5771         for (int32_t x = 0; x < MAXIMUM_MAP_SIZE_TECHNICAL; x++)
5772         {
5773             auto* tileElement = map_get_first_element_at(TileCoordsXY(x, y));
5774             if (tileElement == nullptr)
5775                 continue;
5776 
5777             do
5778             {
5779                 if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK)
5780                     continue;
5781 
5782                 auto* trackElement = tileElement->AsTrack();
5783                 if (trackElement->GetRideIndex() != id)
5784                     continue;
5785 
5786                 trackElement->SetRideType(type);
5787 
5788             } while (!(tileElement++)->IsLastForTile());
5789         }
5790     }
5791 }
5792