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(¤tElement, ¤tElement, 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